diff --git a/api/v1/storagecluster_types.go b/api/v1/storagecluster_types.go index c809d32834..bfe86b679b 100644 --- a/api/v1/storagecluster_types.go +++ b/api/v1/storagecluster_types.go @@ -109,22 +109,6 @@ type StorageClusterSpec struct { // DefaultStorageProfile is the default storage profile to use for // the storageclassrequest as StorageProfile is optional. DefaultStorageProfile string `json:"defaultStorageProfile,omitempty"` - - StorageProfiles []StorageProfile `json:"storageProfiles,omitempty"` -} - -// StorageProfile is the storage profile to use for the storageclassrequest. -type StorageProfile struct { - // +kubebuilder:validation:Required - // Name of the storage profile. - Name string `json:"name"` - // +kubebuilder:validation:Required - // DeviceClass is the deviceclass name. - DeviceClass string `json:"deviceClass"` - // configurations to use for cephfilesystem. - SharedFilesystemConfiguration SharedFilesystemConfigurationSpec `json:"sharedFilesystemConfiguration,omitempty"` - // configurations to use for profile specific blockpool. - BlockPoolConfiguration BlockPoolConfigurationSpec `json:"blockPoolConfiguration,omitempty"` } type SharedFilesystemConfigurationSpec struct { diff --git a/api/v1/storageprofile_types.go b/api/v1/storageprofile_types.go new file mode 100644 index 0000000000..03ab58e45e --- /dev/null +++ b/api/v1/storageprofile_types.go @@ -0,0 +1,94 @@ +/* +Copyright 2020 Red Hat OpenShift Container Storage. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// StorageProfileSpec defines the desired state of StorageProfile +// +kubebuilder:validation:XValidation:rule="!has(oldSelf.deviceClass) || has(self.deviceClass)", message="deviceClass is required once set" +// +kubebuilder:validation:XValidation:rule="!has(oldSelf.sharedFilesystemConfiguration) || has(self.sharedFilesystemConfiguration)", message="sharedFilesystemConfiguration is required once set" +// +kubebuilder:validation:XValidation:rule="!has(oldSelf.blockPoolConfiguration) || has(self.blockPoolConfiguration)", message="blockPoolConfiguration is required once set" +type StorageProfileSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // +kubebuilder:validation:Optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="DeviceClass is immutable" + // +kubebuilder:validation:MaxLength=512 + // DeviceClass is the deviceclass name. + DeviceClass string `json:"deviceClass"` + + // +kubebuilder:validation:Optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="SharedFilesystemConfiguration is immutable" + // configurations to use for cephfilesystem. + SharedFilesystemConfiguration SharedFilesystemConfigurationSpec `json:"sharedFilesystemConfiguration,omitempty"` + + // +kubebuilder:validation:Optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="BlockPoolConfiguration is immutable" + // configurations to use for profile specific blockpool. + BlockPoolConfiguration BlockPoolConfigurationSpec `json:"blockPoolConfiguration,omitempty"` +} + +// StorageProfileStatus defines the observed state of StorageProfile +type StorageProfileStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + + // Phase describes the Phase of StorageProfile + // This is used by OLM UI to provide status information + // to the user + Phase StorageProfilePhase `json:"phase,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// StorageProfile is the Schema for the storageprofiles API +type StorageProfile struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec StorageProfileSpec `json:"spec,omitempty"` + Status StorageProfileStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// StorageProfileList contains a list of StorageProfile +type StorageProfileList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []StorageProfile `json:"items"` +} + +// StorageProfilePhase stores a StorageProfile reconciliation phase +type StorageProfilePhase string + +const ( + StorageProfilePhaseFailed StorageProfilePhase = "Failed" + StorageProfilePhaseReady StorageProfilePhase = "Ready" + StorageProfilePhaseRejected StorageProfilePhase = "Rejected" +) + +func init() { + SchemeBuilder.Register(&StorageProfile{}, &StorageProfileList{}) +} diff --git a/api/v1/zz_generated.deepcopy.go b/api/v1/zz_generated.deepcopy.go index 6458abf1bd..b41ea9e70b 100644 --- a/api/v1/zz_generated.deepcopy.go +++ b/api/v1/zz_generated.deepcopy.go @@ -792,13 +792,6 @@ func (in *StorageClusterSpec) DeepCopyInto(out *StorageClusterSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.StorageProfiles != nil { - in, out := &in.StorageProfiles, &out.StorageProfiles - *out = make([]StorageProfile, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageClusterSpec. @@ -898,8 +891,10 @@ func (in *StorageDeviceSetConfig) DeepCopy() *StorageDeviceSetConfig { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StorageProfile) DeepCopyInto(out *StorageProfile) { *out = *in - in.SharedFilesystemConfiguration.DeepCopyInto(&out.SharedFilesystemConfiguration) - in.BlockPoolConfiguration.DeepCopyInto(&out.BlockPoolConfiguration) + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + out.Status = in.Status } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageProfile. @@ -912,6 +907,78 @@ func (in *StorageProfile) DeepCopy() *StorageProfile { return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *StorageProfile) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StorageProfileList) DeepCopyInto(out *StorageProfileList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]StorageProfile, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageProfileList. +func (in *StorageProfileList) DeepCopy() *StorageProfileList { + if in == nil { + return nil + } + out := new(StorageProfileList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *StorageProfileList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StorageProfileSpec) DeepCopyInto(out *StorageProfileSpec) { + *out = *in + in.SharedFilesystemConfiguration.DeepCopyInto(&out.SharedFilesystemConfiguration) + in.BlockPoolConfiguration.DeepCopyInto(&out.BlockPoolConfiguration) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageProfileSpec. +func (in *StorageProfileSpec) DeepCopy() *StorageProfileSpec { + if in == nil { + return nil + } + out := new(StorageProfileSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *StorageProfileStatus) DeepCopyInto(out *StorageProfileStatus) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new StorageProfileStatus. +func (in *StorageProfileStatus) DeepCopy() *StorageProfileStatus { + if in == nil { + return nil + } + out := new(StorageProfileStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in TopologyLabelValues) DeepCopyInto(out *TopologyLabelValues) { { diff --git a/config/crd/bases/ocs.openshift.io_storageclusters.yaml b/config/crd/bases/ocs.openshift.io_storageclusters.yaml index ff6cb3c312..49415d5d18 100644 --- a/config/crd/bases/ocs.openshift.io_storageclusters.yaml +++ b/config/crd/bases/ocs.openshift.io_storageclusters.yaml @@ -6173,38 +6173,6 @@ spec: - name type: object type: array - storageProfiles: - items: - description: StorageProfile is the storage profile to use for the - storageclassrequest. - properties: - blockPoolConfiguration: - description: configurations to use for profile specific blockpool. - properties: - parameters: - additionalProperties: - type: string - type: object - type: object - deviceClass: - description: DeviceClass is the deviceclass name. - type: string - name: - description: Name of the storage profile. - type: string - sharedFilesystemConfiguration: - description: configurations to use for cephfilesystem. - properties: - parameters: - additionalProperties: - type: string - type: object - type: object - required: - - deviceClass - - name - type: object - type: array version: description: Version specifies the version of StorageCluster type: string diff --git a/config/crd/bases/ocs.openshift.io_storageprofiles.yaml b/config/crd/bases/ocs.openshift.io_storageprofiles.yaml new file mode 100644 index 0000000000..2b8614f771 --- /dev/null +++ b/config/crd/bases/ocs.openshift.io_storageprofiles.yaml @@ -0,0 +1,87 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: storageprofiles.ocs.openshift.io +spec: + group: ocs.openshift.io + names: + kind: StorageProfile + listKind: StorageProfileList + plural: storageprofiles + singular: storageprofile + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: StorageProfile is the Schema for the storageprofiles API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: StorageProfileSpec defines the desired state of StorageProfile + properties: + blockPoolConfiguration: + description: configurations to use for profile specific blockpool. + properties: + parameters: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-validations: + - message: BlockPoolConfiguration is immutable + rule: self == oldSelf + deviceClass: + description: DeviceClass is the deviceclass name. + maxLength: 512 + type: string + x-kubernetes-validations: + - message: DeviceClass is immutable + rule: self == oldSelf + sharedFilesystemConfiguration: + description: configurations to use for cephfilesystem. + properties: + parameters: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-validations: + - message: SharedFilesystemConfiguration is immutable + rule: self == oldSelf + type: object + x-kubernetes-validations: + - message: deviceClass is required once set + rule: '!has(oldSelf.deviceClass) || has(self.deviceClass)' + - message: sharedFilesystemConfiguration is required once set + rule: '!has(oldSelf.sharedFilesystemConfiguration) || has(self.sharedFilesystemConfiguration)' + - message: blockPoolConfiguration is required once set + rule: '!has(oldSelf.blockPoolConfiguration) || has(self.blockPoolConfiguration)' + status: + description: StorageProfileStatus defines the observed state of StorageProfile + properties: + phase: + description: Phase describes the Phase of StorageProfile This is used + by OLM UI to provide status information to the user + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 73fc7d6944..32087bfd72 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -6,6 +6,7 @@ resources: - bases/ocs.openshift.io_storageclusters.yaml - bases/ocs.openshift.io_storageconsumers.yaml - bases/ocs.openshift.io_storageclassrequests.yaml +- bases/ocs.openshift.io_storageprofiles.yaml # +kubebuilder:scaffold:crdkustomizeresource # patchesStrategicMerge: @@ -15,6 +16,7 @@ resources: #- patches/webhook_in_storageclusters.yaml #- patches/webhook_in_storageconsumers.yaml #- patches/webhook_in_storageclassrequestss.yaml +#- patches/webhook_in_storageprofiles.yaml # +kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. @@ -23,6 +25,7 @@ resources: #- patches/cainjection_in_storageclusters.yaml #- patches/cainjection_in_storageconsumers.yaml #- patches/cainjection_in_storageclassrequests.yaml +#- patches/cainjection_in_storageprofiles.yaml # +kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_storageprofiles.yaml b/config/crd/patches/cainjection_in_storageprofiles.yaml new file mode 100644 index 0000000000..e3d618b24c --- /dev/null +++ b/config/crd/patches/cainjection_in_storageprofiles.yaml @@ -0,0 +1,8 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) + name: storageprofiles.ocs.openshift.io diff --git a/config/crd/patches/webhook_in_storageprofiles.yaml b/config/crd/patches/webhook_in_storageprofiles.yaml new file mode 100644 index 0000000000..a08411cf13 --- /dev/null +++ b/config/crd/patches/webhook_in_storageprofiles.yaml @@ -0,0 +1,17 @@ +# The following patch enables conversion webhook for CRD +# CRD conversion requires k8s 1.13 or later. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: storageprofiles.ocs.openshift.io +spec: + conversion: + strategy: Webhook + webhookClientConfig: + # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, + # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) + caBundle: Cg== + service: + namespace: system + name: webhook-service + path: /convert diff --git a/config/rbac/storageprofile_editor_role.yaml b/config/rbac/storageprofile_editor_role.yaml new file mode 100644 index 0000000000..17c3bffb52 --- /dev/null +++ b/config/rbac/storageprofile_editor_role.yaml @@ -0,0 +1,24 @@ +# permissions for end users to edit storageprofiles. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: storageprofile-editor-role +rules: +- apiGroups: + - ocs.openshift.io + resources: + - storageprofiles + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - ocs.openshift.io + resources: + - storageprofiles/status + verbs: + - get diff --git a/config/rbac/storageprofile_viewer_role.yaml b/config/rbac/storageprofile_viewer_role.yaml new file mode 100644 index 0000000000..a276c77ed3 --- /dev/null +++ b/config/rbac/storageprofile_viewer_role.yaml @@ -0,0 +1,20 @@ +# permissions for end users to view storageprofiles. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: storageprofile-viewer-role +rules: +- apiGroups: + - ocs.openshift.io + resources: + - storageprofiles + verbs: + - get + - list + - watch +- apiGroups: + - ocs.openshift.io + resources: + - storageprofiles/status + verbs: + - get diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index 521fead1da..bff9db7c1e 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -3,4 +3,5 @@ resources: - ocs_v1_ocsinitialization.yaml - ocs_v1_storagecluster.yaml - ocs_v1alpha1_storageconsumer.yaml +- ocs_v1_storageprofile.yaml # +kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/ocs_v1_storageprofile.yaml b/config/samples/ocs_v1_storageprofile.yaml new file mode 100644 index 0000000000..56869bd2cb --- /dev/null +++ b/config/samples/ocs_v1_storageprofile.yaml @@ -0,0 +1,12 @@ +apiVersion: ocs.openshift.io/v1 +kind: StorageProfile +metadata: + labels: + app.kubernetes.io/name: storageprofile + app.kubernetes.io/instance: storageprofile-sample + app.kubernetes.io/part-of: ocs-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: ocs-operator + name: storageprofile-sample +spec: + # TODO(user): Add fields here diff --git a/controllers/storageclassrequest/storageclassrequest_controller.go b/controllers/storageclassrequest/storageclassrequest_controller.go index a7d216c0a7..b81c516928 100644 --- a/controllers/storageclassrequest/storageclassrequest_controller.go +++ b/controllers/storageclassrequest/storageclassrequest_controller.go @@ -61,7 +61,6 @@ type StorageClassRequestReconciler struct { cephClientProvisioner *rookCephv1.CephClient cephClientNode *rookCephv1.CephClient cephResourcesByName map[string]*v1alpha1.CephResourcesSpec - storageProfile *v1.StorageProfile } // +kubebuilder:rbac:groups=ocs.openshift.io,resources=storageclassrequests,verbs=get;list;watch;create;update;patch;delete @@ -223,16 +222,16 @@ func (r *StorageClassRequestReconciler) reconcilePhases() (reconcile.Result, err profileName = r.storageCluster.Spec.DefaultStorageProfile } - for i := range r.storageCluster.Spec.StorageProfiles { - profile := &r.storageCluster.Spec.StorageProfiles[i] - if profile.Name == profileName { - r.storageProfile = profile - break - } + // Fetch StorageProfile by name in the StorageCluster's namespace + storageProfile := v1.StorageProfile{ + ObjectMeta: metav1.ObjectMeta{ + Name: profileName, + Namespace: r.storageCluster.Namespace, + }, } - if r.storageProfile == nil { - return reconcile.Result{}, fmt.Errorf("no storage profile definition found for storage profile %s", profileName) + if err := r.get(&storageProfile); err != nil { + return reconcile.Result{}, fmt.Errorf("no storage profile CR found for storage profile %s", profileName) } r.cephClientProvisioner = &rookCephv1.CephClient{} @@ -262,7 +261,7 @@ func (r *StorageClassRequestReconciler) reconcilePhases() (reconcile.Result, err return reconcile.Result{}, err } - if err := r.reconcileCephBlockPool(); err != nil { + if err := r.reconcileCephBlockPool(&storageProfile); err != nil { return reconcile.Result{}, err } @@ -275,7 +274,7 @@ func (r *StorageClassRequestReconciler) reconcilePhases() (reconcile.Result, err return reconcile.Result{}, err } - if err := r.reconcileCephFilesystemSubVolumeGroup(); err != nil { + if err := r.reconcileCephFilesystemSubVolumeGroup(&storageProfile); err != nil { return reconcile.Result{}, err } } @@ -297,7 +296,7 @@ func (r *StorageClassRequestReconciler) reconcilePhases() (reconcile.Result, err return reconcile.Result{}, nil } -func (r *StorageClassRequestReconciler) reconcileCephBlockPool() error { +func (r *StorageClassRequestReconciler) reconcileCephBlockPool(storageProfile *v1.StorageProfile) error { failureDomain := r.storageCluster.Status.FailureDomain @@ -305,7 +304,7 @@ func (r *StorageClassRequestReconciler) reconcileCephBlockPool() error { if err := r.own(r.cephBlockPool); err != nil { return err } - deviceClass := r.storageProfile.DeviceClass + deviceClass := storageProfile.Spec.DeviceClass deviceSetList := r.storageCluster.Spec.StorageDeviceSets var deviceSet *v1.StorageDeviceSet for i := range deviceSetList { @@ -331,7 +330,7 @@ func (r *StorageClassRequestReconciler) reconcileCephBlockPool() error { Size: 3, ReplicasPerFailureDomain: 1, }, - Parameters: r.storageProfile.BlockPoolConfiguration.Parameters, + Parameters: storageProfile.Spec.BlockPoolConfiguration.Parameters, }, } return nil @@ -361,7 +360,7 @@ func (r *StorageClassRequestReconciler) reconcileCephBlockPool() error { return nil } -func (r *StorageClassRequestReconciler) reconcileCephFilesystemSubVolumeGroup() error { +func (r *StorageClassRequestReconciler) reconcileCephFilesystemSubVolumeGroup(storageProfile *v1.StorageProfile) error { cephFilesystem := rookCephv1.CephFilesystem{ ObjectMeta: metav1.ObjectMeta{ @@ -377,7 +376,7 @@ func (r *StorageClassRequestReconciler) reconcileCephFilesystemSubVolumeGroup() if err := r.own(r.cephFilesystemSubVolumeGroup); err != nil { return err } - deviceClass := r.storageProfile.DeviceClass + deviceClass := storageProfile.Spec.DeviceClass dataPool := &rookCephv1.NamedPoolSpec{} for i := range cephFilesystem.Spec.DataPools { if cephFilesystem.Spec.DataPools[i].DeviceClass == deviceClass { diff --git a/controllers/storagecluster/cephfilesystem.go b/controllers/storagecluster/cephfilesystem.go index 241239ce01..2a90be4255 100644 --- a/controllers/storagecluster/cephfilesystem.go +++ b/controllers/storagecluster/cephfilesystem.go @@ -11,6 +11,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/reconcile" ) @@ -19,59 +20,83 @@ type ocsCephFilesystems struct{} // newCephFilesystemInstances returns the cephFilesystem instances that should be created // on first run. -func (r *StorageClusterReconciler) newCephFilesystemInstances(initData *ocsv1.StorageCluster) ([]*cephv1.CephFilesystem, error) { +func (r *StorageClusterReconciler) newCephFilesystemInstances(initStorageCluster *ocsv1.StorageCluster) ([]*cephv1.CephFilesystem, error) { ret := &cephv1.CephFilesystem{ ObjectMeta: metav1.ObjectMeta{ - Name: generateNameForCephFilesystem(initData), - Namespace: initData.Namespace, + Name: generateNameForCephFilesystem(initStorageCluster), + Namespace: initStorageCluster.Namespace, }, Spec: cephv1.FilesystemSpec{ MetadataPool: cephv1.PoolSpec{ - Replicated: generateCephReplicatedSpec(initData, "metadata"), - FailureDomain: initData.Status.FailureDomain, + Replicated: generateCephReplicatedSpec(initStorageCluster, "metadata"), + FailureDomain: initStorageCluster.Status.FailureDomain, }, MetadataServer: cephv1.MetadataServerSpec{ ActiveCount: 1, ActiveStandby: true, - Placement: getPlacement(initData, "mds"), - Resources: defaults.GetDaemonResources("mds", initData.Spec.Resources), + Placement: getPlacement(initStorageCluster, "mds"), + Resources: defaults.GetDaemonResources("mds", initStorageCluster.Spec.Resources), // set PriorityClassName for the MDS pods PriorityClassName: openshiftUserCritical, }, }, } - if initData.Spec.StorageProfiles == nil { - // standalone deployment will not have storageProfile, we need to - // define default dataPool, if storageProfile is set this will be - // overridden. + // not in provider mode + if !initStorageCluster.Spec.AllowRemoteStorageConsumers { + // standalone deployment that isn't in provider cluster will not + // have storageProfile, we need to define default dataPool, if + // storageProfile is set this will be overridden. ret.Spec.DataPools = []cephv1.NamedPoolSpec{ { PoolSpec: cephv1.PoolSpec{ - DeviceClass: generateDeviceClass(initData), - Replicated: generateCephReplicatedSpec(initData, "data"), - FailureDomain: initData.Status.FailureDomain, + DeviceClass: generateDeviceClass(initStorageCluster), + Replicated: generateCephReplicatedSpec(initStorageCluster, "data"), + FailureDomain: initStorageCluster.Status.FailureDomain, }, }, } } else { + // Load all StorageProfile objects in the StorageCluster's namespace + storageProfiles := &ocsv1.StorageProfileList{} + err := r.Client.List(r.ctx, storageProfiles, client.InNamespace(initStorageCluster.GetNamespace())) + if err != nil { + r.Log.Error(err, "unable to list StorageProfile objects") + } // set deviceClass and parameters from storageProfile - for i := range initData.Spec.StorageProfiles { - deviceClass := initData.Spec.StorageProfiles[i].DeviceClass - parameters := initData.Spec.StorageProfiles[i].SharedFilesystemConfiguration.Parameters + for i := range storageProfiles.Items { + storageProfile := storageProfiles.Items[i] + spSpec := &storageProfile.Spec + deviceClass := spSpec.DeviceClass + if len(deviceClass) == 0 { + r.Log.Error(nil, "Storage profile has an empty device class. Skipping.", "StorageProfile", klog.KRef(storageProfile.Namespace, storageProfile.Name)) + storageProfile.Status.Phase = ocsv1.StorageProfilePhaseRejected + if updateErr := r.Client.Status().Update(r.ctx, &storageProfile); updateErr != nil { + r.Log.Error(updateErr, "Could not update StorageProfile.", "StorageProfile", klog.KRef(storageProfile.Namespace, storageProfile.Name)) + return nil, updateErr + } + continue + } else { + storageProfile.Status.Phase = "" + if updateErr := r.Client.Status().Update(r.ctx, &storageProfile); updateErr != nil { + r.Log.Error(updateErr, "Could not update StorageProfile.", "StorageProfile", klog.KRef(storageProfile.Namespace, storageProfile.Name)) + return nil, updateErr + } + } + parameters := spSpec.SharedFilesystemConfiguration.Parameters ret.Spec.DataPools = append(ret.Spec.DataPools, cephv1.NamedPoolSpec{ Name: deviceClass, PoolSpec: cephv1.PoolSpec{ - Replicated: generateCephReplicatedSpec(initData, "data"), + Replicated: generateCephReplicatedSpec(initStorageCluster, "data"), DeviceClass: deviceClass, Parameters: parameters, - FailureDomain: initData.Status.FailureDomain, + FailureDomain: initStorageCluster.Status.FailureDomain, }, }) } } - err := controllerutil.SetControllerReference(initData, ret, r.Scheme) + err := controllerutil.SetControllerReference(initStorageCluster, ret, r.Scheme) if err != nil { r.Log.Error(err, "Unable to set Controller Reference for CephFileSystem.", "CephFileSystem", klog.KRef(ret.Namespace, ret.Name)) return nil, err diff --git a/controllers/storagecluster/storagecluster_controller.go b/controllers/storagecluster/storagecluster_controller.go index 5f6c280f1d..a35bab8628 100644 --- a/controllers/storagecluster/storagecluster_controller.go +++ b/controllers/storagecluster/storagecluster_controller.go @@ -121,7 +121,7 @@ func (r *StorageClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { } enqueueStorageClusterRequest := handler.EnqueueRequestsFromMapFunc( - func(context context.Context, obj client.Object) []reconcile.Request { + func(_ context.Context, obj client.Object) []reconcile.Request { ocinit, ok := obj.(*ocsv1.OCSInitialization) if !ok { @@ -155,6 +155,36 @@ func (r *StorageClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { }, ) + enqueueFromStorageProfile := handler.EnqueueRequestsFromMapFunc( + func(_ context.Context, obj client.Object) []reconcile.Request { + // only storage profile is being watched + _ = obj.(*ocsv1.StorageProfile) + + // Get the StorageCluster object + scList := &ocsv1.StorageClusterList{} + err := r.Client.List(r.ctx, scList, client.InNamespace(obj.GetNamespace()), client.Limit(1)) + if err != nil { + r.Log.Error(err, "Unable to list StorageCluster objects") + return []reconcile.Request{} + } + + if len(scList.Items) == 0 { + return []reconcile.Request{} + } + + sc := scList.Items[0] + // Return name and namespace of StorageCluster + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: sc.Name, + Namespace: sc.Namespace, + }, + }, + } + }, + ) + builder := ctrl.NewControllerManagedBy(mgr). For(&ocsv1.StorageCluster{}, builder.WithPredicates(scPredicate)). Owns(&cephv1.CephCluster{}). @@ -163,6 +193,7 @@ func (r *StorageClusterReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&corev1.Service{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). Owns(&corev1.ConfigMap{}, builder.WithPredicates(predicate.GenerationChangedPredicate{})). Watches(&ocsv1.OCSInitialization{}, enqueueStorageClusterRequest). + Watches(&ocsv1.StorageProfile{}, enqueueFromStorageProfile). Watches( &extv1.CustomResourceDefinition{ ObjectMeta: metav1.ObjectMeta{ diff --git a/deploy/csv-templates/crds/ocs/ocs.openshift.io_storageclusters.yaml b/deploy/csv-templates/crds/ocs/ocs.openshift.io_storageclusters.yaml index ff6cb3c312..49415d5d18 100644 --- a/deploy/csv-templates/crds/ocs/ocs.openshift.io_storageclusters.yaml +++ b/deploy/csv-templates/crds/ocs/ocs.openshift.io_storageclusters.yaml @@ -6173,38 +6173,6 @@ spec: - name type: object type: array - storageProfiles: - items: - description: StorageProfile is the storage profile to use for the - storageclassrequest. - properties: - blockPoolConfiguration: - description: configurations to use for profile specific blockpool. - properties: - parameters: - additionalProperties: - type: string - type: object - type: object - deviceClass: - description: DeviceClass is the deviceclass name. - type: string - name: - description: Name of the storage profile. - type: string - sharedFilesystemConfiguration: - description: configurations to use for cephfilesystem. - properties: - parameters: - additionalProperties: - type: string - type: object - type: object - required: - - deviceClass - - name - type: object - type: array version: description: Version specifies the version of StorageCluster type: string diff --git a/deploy/csv-templates/crds/ocs/ocs.openshift.io_storageprofiles.yaml b/deploy/csv-templates/crds/ocs/ocs.openshift.io_storageprofiles.yaml new file mode 100644 index 0000000000..2b8614f771 --- /dev/null +++ b/deploy/csv-templates/crds/ocs/ocs.openshift.io_storageprofiles.yaml @@ -0,0 +1,87 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + creationTimestamp: null + name: storageprofiles.ocs.openshift.io +spec: + group: ocs.openshift.io + names: + kind: StorageProfile + listKind: StorageProfileList + plural: storageprofiles + singular: storageprofile + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: StorageProfile is the Schema for the storageprofiles API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: StorageProfileSpec defines the desired state of StorageProfile + properties: + blockPoolConfiguration: + description: configurations to use for profile specific blockpool. + properties: + parameters: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-validations: + - message: BlockPoolConfiguration is immutable + rule: self == oldSelf + deviceClass: + description: DeviceClass is the deviceclass name. + maxLength: 512 + type: string + x-kubernetes-validations: + - message: DeviceClass is immutable + rule: self == oldSelf + sharedFilesystemConfiguration: + description: configurations to use for cephfilesystem. + properties: + parameters: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-validations: + - message: SharedFilesystemConfiguration is immutable + rule: self == oldSelf + type: object + x-kubernetes-validations: + - message: deviceClass is required once set + rule: '!has(oldSelf.deviceClass) || has(self.deviceClass)' + - message: sharedFilesystemConfiguration is required once set + rule: '!has(oldSelf.sharedFilesystemConfiguration) || has(self.sharedFilesystemConfiguration)' + - message: blockPoolConfiguration is required once set + rule: '!has(oldSelf.blockPoolConfiguration) || has(self.blockPoolConfiguration)' + status: + description: StorageProfileStatus defines the observed state of StorageProfile + properties: + phase: + description: Phase describes the Phase of StorageProfile This is used + by OLM UI to provide status information to the user + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/deploy/csv-templates/ocs-operator.csv.yaml.in b/deploy/csv-templates/ocs-operator.csv.yaml.in index e9803a7040..423e7fed16 100644 --- a/deploy/csv-templates/ocs-operator.csv.yaml.in +++ b/deploy/csv-templates/ocs-operator.csv.yaml.in @@ -59,6 +59,21 @@ metadata: ] } }, + { + "apiVersion": "ocs.openshift.io/v1", + "kind": "StorageProfile", + "metadata": { + "labels": { + "app.kubernetes.io/created-by": "ocs-operator", + "app.kubernetes.io/instance": "storageprofile-sample", + "app.kubernetes.io/managed-by": "kustomize", + "app.kubernetes.io/name": "storageprofile", + "app.kubernetes.io/part-of": "ocs-operator" + }, + "name": "storageprofile-sample" + }, + "spec": null + }, { "apiVersion": "ocs.openshift.io/v1alpha1", "kind": "StorageConsumer", @@ -110,6 +125,9 @@ spec: kind: StorageConsumer name: storageconsumers.ocs.openshift.io version: v1alpha1 + - kind: StorageProfile + name: storageprofiles.ocs.openshift.io + version: v1 description: | **Red Hat OpenShift Container Storage** deploys three operators. diff --git a/deploy/ocs-operator/manifests/ocs-operator.clusterserviceversion.yaml b/deploy/ocs-operator/manifests/ocs-operator.clusterserviceversion.yaml index f315e9ea3f..a7d5a4f826 100644 --- a/deploy/ocs-operator/manifests/ocs-operator.clusterserviceversion.yaml +++ b/deploy/ocs-operator/manifests/ocs-operator.clusterserviceversion.yaml @@ -1563,7 +1563,7 @@ metadata: operatorframework.io/suggested-namespace: openshift-storage operators.openshift.io/infrastructure-features: '["disconnected"]' operators.operatorframework.io/builder: operator-sdk-v1.25.4 - operators.operatorframework.io/internal-objects: '["ocsinitializations.ocs.openshift.io","storageclassrequests.ocs.openshift.io","storageconsumers.ocs.openshift.io","cephclusters.ceph.rook.io","cephobjectstores.ceph.rook.io","cephobjectstoreusers.ceph.rook.io","cephnfses.ceph.rook.io","cephclients.ceph.rook.io","cephfilesystems.ceph.rook.io","cephfilesystemmirrors.ceph.rook.io","cephrbdmirrors.ceph.rook.io","cephobjectrealms.ceph.rook.io","cephobjectzonegroups.ceph.rook.io","cephobjectzones.ceph.rook.io","cephbucketnotifications.ceph.rook.io","cephbuckettopics.ceph.rook.io","cephfilesystemsubvolumegroups.ceph.rook.io","cephblockpoolradosnamespaces.ceph.rook.io","cephcosidrivers.ceph.rook.io"]' + operators.operatorframework.io/internal-objects: '["ocsinitializations.ocs.openshift.io","storageclassrequests.ocs.openshift.io","storageconsumers.ocs.openshift.io","storageprofiles.ocs.openshift.io","cephclusters.ceph.rook.io","cephobjectstores.ceph.rook.io","cephobjectstoreusers.ceph.rook.io","cephnfses.ceph.rook.io","cephclients.ceph.rook.io","cephfilesystems.ceph.rook.io","cephfilesystemmirrors.ceph.rook.io","cephrbdmirrors.ceph.rook.io","cephobjectrealms.ceph.rook.io","cephobjectzonegroups.ceph.rook.io","cephobjectzones.ceph.rook.io","cephbucketnotifications.ceph.rook.io","cephbuckettopics.ceph.rook.io","cephfilesystemsubvolumegroups.ceph.rook.io","cephblockpoolradosnamespaces.ceph.rook.io","cephcosidrivers.ceph.rook.io"]' operators.operatorframework.io/operator-type: non-standalone operators.operatorframework.io/project_layout: go.kubebuilder.io/v2 repository: https://github.com/red-hat-storage/ocs-operator @@ -1610,6 +1610,9 @@ spec: kind: StorageConsumer name: storageconsumers.ocs.openshift.io version: v1alpha1 + - kind: StorageProfile + name: storageprofiles.ocs.openshift.io + version: v1 - description: Represents a Ceph cluster. displayName: Ceph Cluster kind: CephCluster diff --git a/deploy/ocs-operator/manifests/storagecluster.crd.yaml b/deploy/ocs-operator/manifests/storagecluster.crd.yaml index f6685f033e..a23a49b075 100644 --- a/deploy/ocs-operator/manifests/storagecluster.crd.yaml +++ b/deploy/ocs-operator/manifests/storagecluster.crd.yaml @@ -6172,38 +6172,6 @@ spec: - name type: object type: array - storageProfiles: - items: - description: StorageProfile is the storage profile to use for the - storageclassrequest. - properties: - blockPoolConfiguration: - description: configurations to use for profile specific blockpool. - properties: - parameters: - additionalProperties: - type: string - type: object - type: object - deviceClass: - description: DeviceClass is the deviceclass name. - type: string - name: - description: Name of the storage profile. - type: string - sharedFilesystemConfiguration: - description: configurations to use for cephfilesystem. - properties: - parameters: - additionalProperties: - type: string - type: object - type: object - required: - - deviceClass - - name - type: object - type: array version: description: Version specifies the version of StorageCluster type: string diff --git a/deploy/ocs-operator/manifests/storageprofile.crd.yaml b/deploy/ocs-operator/manifests/storageprofile.crd.yaml new file mode 100644 index 0000000000..ddd22d5732 --- /dev/null +++ b/deploy/ocs-operator/manifests/storageprofile.crd.yaml @@ -0,0 +1,86 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.9.2 + name: storageprofiles.ocs.openshift.io +spec: + group: ocs.openshift.io + names: + kind: StorageProfile + listKind: StorageProfileList + plural: storageprofiles + singular: storageprofile + scope: Namespaced + versions: + - name: v1 + schema: + openAPIV3Schema: + description: StorageProfile is the Schema for the storageprofiles API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: StorageProfileSpec defines the desired state of StorageProfile + properties: + blockPoolConfiguration: + description: configurations to use for profile specific blockpool. + properties: + parameters: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-validations: + - message: BlockPoolConfiguration is immutable + rule: self == oldSelf + deviceClass: + description: DeviceClass is the deviceclass name. + maxLength: 512 + type: string + x-kubernetes-validations: + - message: DeviceClass is immutable + rule: self == oldSelf + sharedFilesystemConfiguration: + description: configurations to use for cephfilesystem. + properties: + parameters: + additionalProperties: + type: string + type: object + type: object + x-kubernetes-validations: + - message: SharedFilesystemConfiguration is immutable + rule: self == oldSelf + type: object + x-kubernetes-validations: + - message: deviceClass is required once set + rule: '!has(oldSelf.deviceClass) || has(self.deviceClass)' + - message: sharedFilesystemConfiguration is required once set + rule: '!has(oldSelf.sharedFilesystemConfiguration) || has(self.sharedFilesystemConfiguration)' + - message: blockPoolConfiguration is required once set + rule: '!has(oldSelf.blockPoolConfiguration) || has(self.blockPoolConfiguration)' + status: + description: StorageProfileStatus defines the observed state of StorageProfile + properties: + phase: + description: Phase describes the Phase of StorageProfile This is used + by OLM UI to provide status information to the user + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {}