diff --git a/README.md b/README.md index a8a8d9003..1cdd61f15 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ end - [Cleanup](#cleanup) - [Metrics](#metrics) - [Known Limitations](#known-limitations) + * [Dynamic Device Discovery](#dynamic-device-discovery) * [Unsupported Device Types](#unsupported-device-types) * [Single LVMCluster support](#single-lvmcluster-support) * [Upgrades from v 4.10 and v4.11](#upgrades-from-v-410-and-v411) @@ -369,6 +370,16 @@ LVMS provides [TopoLVM metrics](https://github.com/topolvm/topolvm/blob/v0.21.0/ ## Known Limitations +### Dynamic Device Discovery + +When a `DeviceSelector` isn't configured for a device class, LVMS operates dynamically, continuously monitoring attached devices on the node and adding them to the volume group if they're unused and supported. However, this approach presents several potential issues: + +- LVMS may inadvertently add a device to the volume group that wasn't intended for LVMS. +- Removing devices could disrupt the volume group. +- LVMS lacks awareness of volume group changes that could lead to data loss, potentially necessitating manual node remediation. + +Given these considerations, it's advised against using LVMS in dynamic discovery mode for production environments. + ### Unsupported Device Types Here is a list of the types of devices that are excluded by LVMS. To get more information about the devices on your machine and to check if they fall under any of these filters, run: diff --git a/api/v1alpha1/lvmcluster_webhook.go b/api/v1alpha1/lvmcluster_webhook.go index 8fd4e7d8e..c149b72b7 100644 --- a/api/v1alpha1/lvmcluster_webhook.go +++ b/api/v1alpha1/lvmcluster_webhook.go @@ -93,7 +93,8 @@ func (v *lvmClusterValidator) ValidateCreate(ctx context.Context, obj runtime.Ob return warnings, err } - err = v.verifyPathsAreNotEmpty(l) + pathWarnings, err := v.verifyPathsAreNotEmpty(l) + warnings = append(warnings, pathWarnings...) if err != nil { return warnings, err } @@ -129,7 +130,8 @@ func (v *lvmClusterValidator) ValidateUpdate(_ context.Context, old, new runtime return warnings, err } - err = v.verifyPathsAreNotEmpty(l) + pathWarnings, err := v.verifyPathsAreNotEmpty(l) + warnings = append(warnings, pathWarnings...) if err != nil { return warnings, err } @@ -298,23 +300,25 @@ func (v *lvmClusterValidator) verifyDeviceClass(l *LVMCluster) (admission.Warnin return nil, nil } -func (v *lvmClusterValidator) verifyPathsAreNotEmpty(l *LVMCluster) error { +func (v *lvmClusterValidator) verifyPathsAreNotEmpty(l *LVMCluster) (admission.Warnings, error) { var deviceClassesWithoutPaths []string for _, deviceClass := range l.Spec.Storage.DeviceClasses { if deviceClass.DeviceSelector != nil { if len(deviceClass.DeviceSelector.Paths) == 0 && len(deviceClass.DeviceSelector.OptionalPaths) == 0 { - return ErrPathsOrOptionalPathsMandatoryWithNonNilDeviceSelector + return nil, ErrPathsOrOptionalPathsMandatoryWithNonNilDeviceSelector } } else { deviceClassesWithoutPaths = append(deviceClassesWithoutPaths, deviceClass.Name) } } if len(l.Spec.Storage.DeviceClasses) > 1 && len(deviceClassesWithoutPaths) > 0 { - return fmt.Errorf("%w. Please specify device path(s) under deviceSelector.paths for %s deviceClass(es)", ErrEmptyPathsWithMultipleDeviceClasses, strings.Join(deviceClassesWithoutPaths, `,`)) + return nil, fmt.Errorf("%w. Please specify device path(s) under deviceSelector.paths for %s deviceClass(es)", ErrEmptyPathsWithMultipleDeviceClasses, strings.Join(deviceClassesWithoutPaths, `,`)) + } else if len(l.Spec.Storage.DeviceClasses) == 1 && len(deviceClassesWithoutPaths) == 1 { + return admission.Warnings{fmt.Sprintf("no device path(s) under deviceSelector.paths was specified for the %s deviceClass, LVMS will actively monitor and dynamically utilize any supported unused devices. This is not recommended for production environments. Please refer to the limitations outlined in the product documentation for further details.", deviceClassesWithoutPaths[0])}, nil } - return nil + return nil, nil } func (v *lvmClusterValidator) verifyAbsolutePath(l *LVMCluster) error { diff --git a/api/v1alpha1/lvmvolumegroupnodestatus_types.go b/api/v1alpha1/lvmvolumegroupnodestatus_types.go index 781a507b0..53d11c4c3 100644 --- a/api/v1alpha1/lvmvolumegroupnodestatus_types.go +++ b/api/v1alpha1/lvmvolumegroupnodestatus_types.go @@ -22,6 +22,13 @@ import ( // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. +type DeviceDiscoveryPolicy string + +const ( + DeviceDiscoveryPolicyPreConfigured DeviceDiscoveryPolicy = "PreConfigured" + DeviceDiscoveryPolicyRuntimeDynamic DeviceDiscoveryPolicy = "RuntimeDynamic" +) + // LVMVolumeGroupNodeStatusSpec defines the desired state of LVMVolumeGroupNodeStatus type LVMVolumeGroupNodeStatusSpec struct { // NodeStatus contains the per node status of the VG @@ -53,6 +60,15 @@ type VGStatus struct { // Excluded contains the per node status of applied device exclusions that were picked up via selector, // but were not used for other reasons. Excluded []ExcludedDevice `json:"excluded,omitempty"` + // DeviceDiscoveryPolicy is a field to indicate whether the devices are discovered + // at runtime or preconfigured through a DeviceSelector + // If set to DeviceDiscoveryPolicyPreConfigured, the devices are preconfigured through a DeviceSelector. + // If set to DeviceDiscoveryPolicyRuntimeDynamic, the devices will be added to the VG if they are present at runtime. + // By default, the value is set to RuntimeDynamic. + // +kubebuilder:validation:Enum=None;RuntimeDynamic + // +kubebuilder:default=RuntimeDynamic + // +kubebuilder:validation:Required + DeviceDiscoveryPolicy DeviceDiscoveryPolicy `json:"deviceDiscoveryPolicy,omitempty"` } type ExcludedDevice struct { diff --git a/bundle/manifests/lvm.topolvm.io_lvmclusters.yaml b/bundle/manifests/lvm.topolvm.io_lvmclusters.yaml index 74ea3ad7e..efe8a5fae 100644 --- a/bundle/manifests/lvm.topolvm.io_lvmclusters.yaml +++ b/bundle/manifests/lvm.topolvm.io_lvmclusters.yaml @@ -301,6 +301,18 @@ spec: description: NodeStatus defines the observed state of the deviceclass on the node properties: + deviceDiscoveryPolicy: + default: RuntimeDynamic + description: |- + DeviceDiscoveryPolicy is a field to indicate whether the devices are discovered + at runtime or preconfigured through a DeviceSelector + If set to DeviceDiscoveryPolicyPreConfigured, the devices are preconfigured through a DeviceSelector. + If set to DeviceDiscoveryPolicyRuntimeDynamic, the devices will be added to the VG if they are present at runtime. + By default, the value is set to RuntimeDynamic. + enum: + - None + - RuntimeDynamic + type: string devices: description: Devices is the list of devices used by the volume group diff --git a/bundle/manifests/lvm.topolvm.io_lvmvolumegroupnodestatuses.yaml b/bundle/manifests/lvm.topolvm.io_lvmvolumegroupnodestatuses.yaml index 6c6d3b71d..a36751cbd 100644 --- a/bundle/manifests/lvm.topolvm.io_lvmvolumegroupnodestatuses.yaml +++ b/bundle/manifests/lvm.topolvm.io_lvmvolumegroupnodestatuses.yaml @@ -45,6 +45,18 @@ spec: description: NodeStatus contains the per node status of the VG items: properties: + deviceDiscoveryPolicy: + default: RuntimeDynamic + description: |- + DeviceDiscoveryPolicy is a field to indicate whether the devices are discovered + at runtime or preconfigured through a DeviceSelector + If set to DeviceDiscoveryPolicyPreConfigured, the devices are preconfigured through a DeviceSelector. + If set to DeviceDiscoveryPolicyRuntimeDynamic, the devices will be added to the VG if they are present at runtime. + By default, the value is set to RuntimeDynamic. + enum: + - None + - RuntimeDynamic + type: string devices: description: Devices is the list of devices used by the volume group diff --git a/config/crd/bases/lvm.topolvm.io_lvmclusters.yaml b/config/crd/bases/lvm.topolvm.io_lvmclusters.yaml index 1ce76b3b2..9a7c0b2c7 100644 --- a/config/crd/bases/lvm.topolvm.io_lvmclusters.yaml +++ b/config/crd/bases/lvm.topolvm.io_lvmclusters.yaml @@ -297,6 +297,18 @@ spec: description: NodeStatus defines the observed state of the deviceclass on the node properties: + deviceDiscoveryPolicy: + default: RuntimeDynamic + description: |- + DeviceDiscoveryPolicy is a field to indicate whether the devices are discovered + at runtime or preconfigured through a DeviceSelector + If set to DeviceDiscoveryPolicyPreConfigured, the devices are preconfigured through a DeviceSelector. + If set to DeviceDiscoveryPolicyRuntimeDynamic, the devices will be added to the VG if they are present at runtime. + By default, the value is set to RuntimeDynamic. + enum: + - None + - RuntimeDynamic + type: string devices: description: Devices is the list of devices used by the volume group diff --git a/config/crd/bases/lvm.topolvm.io_lvmvolumegroupnodestatuses.yaml b/config/crd/bases/lvm.topolvm.io_lvmvolumegroupnodestatuses.yaml index c69f4971e..f8fc67b47 100644 --- a/config/crd/bases/lvm.topolvm.io_lvmvolumegroupnodestatuses.yaml +++ b/config/crd/bases/lvm.topolvm.io_lvmvolumegroupnodestatuses.yaml @@ -45,6 +45,18 @@ spec: description: NodeStatus contains the per node status of the VG items: properties: + deviceDiscoveryPolicy: + default: RuntimeDynamic + description: |- + DeviceDiscoveryPolicy is a field to indicate whether the devices are discovered + at runtime or preconfigured through a DeviceSelector + If set to DeviceDiscoveryPolicyPreConfigured, the devices are preconfigured through a DeviceSelector. + If set to DeviceDiscoveryPolicyRuntimeDynamic, the devices will be added to the VG if they are present at runtime. + By default, the value is set to RuntimeDynamic. + enum: + - None + - RuntimeDynamic + type: string devices: description: Devices is the list of devices used by the volume group diff --git a/internal/controllers/vgmanager/status.go b/internal/controllers/vgmanager/status.go index 8b4dcf3fb..2db9cc364 100644 --- a/internal/controllers/vgmanager/status.go +++ b/internal/controllers/vgmanager/status.go @@ -81,6 +81,12 @@ func (r *Reconciler) setVolumeGroupFailedStatus(ctx context.Context, vg *lvmv1al func (r *Reconciler) setVolumeGroupStatus(ctx context.Context, vg *lvmv1alpha1.LVMVolumeGroup, status *lvmv1alpha1.VGStatus) (bool, error) { logger := log.FromContext(ctx).WithValues("VolumeGroup", client.ObjectKeyFromObject(vg)) + if vg.Spec.DeviceSelector == nil { + status.DeviceDiscoveryPolicy = lvmv1alpha1.DeviceDiscoveryPolicyRuntimeDynamic + } else { + status.DeviceDiscoveryPolicy = lvmv1alpha1.DeviceDiscoveryPolicyPreConfigured + } + // Get LVMVolumeGroupNodeStatus and set the relevant VGStatus nodeStatus := r.getLVMVolumeGroupNodeStatus()