From dd770901fe01ac78223c10c11be00852b6de179d Mon Sep 17 00:00:00 2001 From: Teddy Andrieux Date: Mon, 22 Mar 2021 18:29:36 +0100 Subject: [PATCH] storage-operator,salt: Add support for LVM volumes Add a new volume type `LVMLV` that represent an LVM LogicalVolume that will be created on a specified LVM VolumeGroup with a specified Size then this LVM LogicalVolume is used as a classical rawBlockDevice volume Fixes: #1997 --- salt/_modules/metalk8s_volumes.py | 91 ++++++++++++++++--- ...rage.metalk8s.scality.com_volumes_crd.yaml | 16 ++++ .../pkg/apis/storage/v1alpha1/volume_types.go | 19 +++- 3 files changed, 111 insertions(+), 15 deletions(-) diff --git a/salt/_modules/metalk8s_volumes.py b/salt/_modules/metalk8s_volumes.py index d110b3491c..5e5c99f46c 100644 --- a/salt/_modules/metalk8s_volumes.py +++ b/salt/_modules/metalk8s_volumes.py @@ -368,7 +368,7 @@ def __init__(self, volume): # Detect which kind of device we have: a real disk, only a partition or # an LVM volume. self._partition = self._get_partition(_device_name(self.path)) - if self._get_lvm_path() is not None: + if _get_lvm_path(self.path) is not None: self._kind = DeviceType.LVM elif self._partition is not None: self._kind = DeviceType.PARTITION @@ -378,7 +378,7 @@ def __init__(self, volume): @property def persistent_path(self): if self._kind == DeviceType.LVM: - return self._get_lvm_path() + return _get_lvm_path(self.path) return "/dev/disk/by-partuuid/{}".format(self.uuid) @property @@ -425,18 +425,6 @@ def prepare(self, force=False): else: prepare_block(self.path, name, self.uuid) - def _get_lvm_path(self): - """Return the persistent path for a LVM volume. - - If the backing storage device is not an LVM volume, return None. - """ - name = _device_name(self.path) - for symlink in glob.glob("/dev/disk/by-id/dm-uuid-LVM-*"): - realpath = os.path.realpath(symlink) - if os.path.basename(realpath) == name: - return symlink - return None - @staticmethod def _get_partition(device_name): part_re = r"(?:(?:h|s|v|xv)d[a-z]|nvme\d+n\d+p)(?P\d+)$" @@ -450,6 +438,63 @@ class DeviceType: LVM = 3 +class LVMLV(RawBlockDevice): + @property + def size(self): + return _quantity_to_bytes(self.get("spec.LVMLV.size")) + + @property + def vg_name(self): + return self.get("spec.LVMLV.vgName") + + @property + def lv_name(self): + return self.get("metadata.name") + + @property + def path(self): + return "/dev/{}/{}".format(self.vg_name, self.lv_name) + + @property + def exists(self): + lv_info = __salt__["lvm.lvdisplay"](lvname=self.path, quiet=True) + + return lv_info and lv_info.get(self.path) + + def create(self): + try: + __salt__["lvm.lvcreate"]( + lvname=self.lv_name, vgname=self.vg_name, size="{}b".format(self.size) + ) + except Exception as exc: + raise CommandExecutionError( + "cannot create LVM LV {} in VG {}".format(self.lv_name, self.vg_name) + ) from exc + + def clean_up(self): + # We do not remove the LV, too dangerous + log.info( + "Volume get clean up but the backing LV '%s' does not get removed", + self.path, + ) + return + + +class LVMLVBlock(LVMLV): + @property + def persistent_path(self): + return _get_lvm_path(self.path) + + @property + def is_prepared(self): + # Nothing to prepare on LVM + return True + + def prepare(self, force=False): + # Nothing to prepare on LVM + return + + # }}} # Helpers {{{ @@ -470,6 +515,11 @@ def _get_volume(name): return SparseLoopDevice(volume) else: return SparseLoopDeviceBlock(volume) + elif "LVMLV" in volume["spec"]: + if mode == "Filesystem": + return LVMLV(volume) + else: + return LVMLVBlock(volume) else: raise ValueError("unsupported Volume type for Volume {}".format(name)) @@ -539,6 +589,19 @@ def _quantity_to_bytes(quantity): return size * UNIT_FACTOR[unit] +def _get_lvm_path(path): + """Return the persistent path for a LVM volume. + + If the backing storage device is not an LVM volume, return None. + """ + name = _device_name(path) + for symlink in glob.glob("/dev/disk/by-id/dm-uuid-LVM-*"): + realpath = os.path.realpath(symlink) + if os.path.basename(realpath) == name: + return symlink + return None + + @contextlib.contextmanager def _open_fd(*args, **kwargs): fd = os.open(*args, **kwargs) diff --git a/storage-operator/deploy/crds/storage.metalk8s.scality.com_volumes_crd.yaml b/storage-operator/deploy/crds/storage.metalk8s.scality.com_volumes_crd.yaml index 8e290e2939..d77dc3d23e 100644 --- a/storage-operator/deploy/crds/storage.metalk8s.scality.com_volumes_crd.yaml +++ b/storage-operator/deploy/crds/storage.metalk8s.scality.com_volumes_crd.yaml @@ -58,6 +58,22 @@ spec: required: - devicePath type: object + LVMLV: + properties: + vgName: + description: Name of the LVM VolumeGroup to create the LVM LogicalVolume to back the PersistentVolume. + type: string + size: + anyOf: + - type: integer + - type: string + description: Size of the created LVM LogicalVolume backing the PersistentVolume + pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$ + x-kubernetes-int-or-string: true + required: + - vgName + - size + type: object sparseLoopDevice: properties: size: diff --git a/storage-operator/pkg/apis/storage/v1alpha1/volume_types.go b/storage-operator/pkg/apis/storage/v1alpha1/volume_types.go index 309f935ecd..45b2fb3fc7 100644 --- a/storage-operator/pkg/apis/storage/v1alpha1/volume_types.go +++ b/storage-operator/pkg/apis/storage/v1alpha1/volume_types.go @@ -23,9 +23,18 @@ type RawBlockDeviceVolumeSource struct { DevicePath string `json:"devicePath"` } +type LVMLVSource struct { + // Name of the LVM VolumeGroup on the node to create the LVM LogicalVolume to back + // the PersistentVolume + VgName string `json:"vgName"` + // Size of the created LVM LogicalVolume backing the PersistentVolume + Size resource.Quantity `json:"size"` +} + type VolumeSource struct { SparseLoopDevice *SparseLoopDeviceVolumeSource `json:"sparseLoopDevice,omitempty"` RawBlockDevice *RawBlockDeviceVolumeSource `json:"rawBlockDevice,omitempty"` + LVMLV *LVMLVSource `json:"LVMLV,omitempty"` } // VolumeSpec defines the desired state of Volume @@ -263,7 +272,8 @@ func (self *Volume) SetTerminatingStatus(job string) { func (self *Volume) IsValid() error { // Check if a type is specified. if self.Spec.SparseLoopDevice == nil && - self.Spec.RawBlockDevice == nil { + self.Spec.RawBlockDevice == nil && + self.Spec.LVMLV == nil { return errors.New("volume type not found in Volume Spec") } // Check if the size is strictly positive. @@ -274,6 +284,13 @@ func (self *Volume) IsValid() error { self.Spec.SparseLoopDevice.Size.String(), ) } + } else if self.Spec.LVMLV != nil { + if self.Spec.LVMLV.Size.Sign() <= 0 { + return fmt.Errorf( + "invalid LVMLV size (should be greater than 0): %s", + self.Spec.LVMLV.Size.String(), + ) + } } // Default to Filesystem when mode is not specified.