diff --git a/cluster/examples/kubernetes/nfs/nfs.yaml b/cluster/examples/kubernetes/nfs/nfs.yaml index f88702627..e6648dc06 100644 --- a/cluster/examples/kubernetes/nfs/nfs.yaml +++ b/cluster/examples/kubernetes/nfs/nfs.yaml @@ -9,6 +9,54 @@ metadata: name: rook-nfs-server namespace: rook-nfs --- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: rook-nfs-provisioner-runner +rules: + - apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get", "list", "watch", "create", "delete"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list", "watch", "update"] + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "update", "patch"] + - apiGroups: [""] + resources: ["services", "endpoints"] + verbs: ["get"] + - apiGroups: ["extensions"] + resources: ["podsecuritypolicies"] + resourceNames: ["nfs-provisioner"] + verbs: ["use"] + - apiGroups: [""] + resources: ["endpoints"] + verbs: ["get", "list", "watch", "create", "update", "patch"] + - apiGroups: + - nfs.rook.io + resources: + - "*" + verbs: + - "*" +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: rook-nfs-provisioner-runner +subjects: + - kind: ServiceAccount + name: rook-nfs-server + # replace with namespace where provisioner is deployed + namespace: rook-nfs +roleRef: + kind: ClusterRole + name: rook-nfs-provisioner-runner + apiGroup: rbac.authorization.k8s.io +--- # A default storageclass must be present apiVersion: v1 kind: PersistentVolumeClaim diff --git a/cluster/examples/kubernetes/nfs/provisioner.yaml b/cluster/examples/kubernetes/nfs/provisioner.yaml deleted file mode 100644 index 5e8bb89ee..000000000 --- a/cluster/examples/kubernetes/nfs/provisioner.yaml +++ /dev/null @@ -1,86 +0,0 @@ -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: rook-nfs-provisioner-runner -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "create", "delete"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["create", "update", "patch"] - - apiGroups: [""] - resources: ["services", "endpoints"] - verbs: ["get"] - - apiGroups: ["extensions"] - resources: ["podsecuritypolicies"] - resourceNames: ["nfs-provisioner"] - verbs: ["use"] - - apiGroups: [""] - resources: ["endpoints"] - verbs: ["get", "list", "watch", "create", "update", "patch"] - - apiGroups: - - nfs.rook.io - resources: - - "*" - verbs: - - "*" ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: run-nfs-provisioner -subjects: - - kind: ServiceAccount - name: rook-nfs-provisioner - # replace with namespace where provisioner is deployed - namespace: rook-nfs-system -roleRef: - kind: ClusterRole - name: rook-nfs-provisioner-runner - apiGroup: rbac.authorization.k8s.io ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: rook-nfs-provisioner - namespace: rook-nfs-system ---- -kind: Deployment -apiVersion: apps/v1 -metadata: - name: rook-nfs-provisioner - namespace: rook-nfs-system -spec: - selector: - matchLabels: - app: rook-nfs-provisioner - replicas: 1 - strategy: - type: Recreate - template: - metadata: - labels: - app: rook-nfs-provisioner - spec: - serviceAccount: rook-nfs-provisioner - containers: - - name: rook-nfs-provisioner - image: rook/nfs:master - imagePullPolicy: IfNotPresent - args: ["nfs", "provisioner","--provisioner=rook.io/nfs-provisioner"] - env: - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace diff --git a/cluster/examples/kubernetes/nfs/sc.yaml b/cluster/examples/kubernetes/nfs/sc.yaml index 2f25f3c7c..2ad62ed6b 100644 --- a/cluster/examples/kubernetes/nfs/sc.yaml +++ b/cluster/examples/kubernetes/nfs/sc.yaml @@ -8,6 +8,6 @@ parameters: exportName: share1 nfsServerName: rook-nfs nfsServerNamespace: rook-nfs -provisioner: rook.io/nfs-provisioner +provisioner: nfs.rook.io/rook-nfs-provisioner reclaimPolicy: Delete volumeBindingMode: Immediate diff --git a/pkg/operator/nfs/controller.go b/pkg/operator/nfs/controller.go index fc0c16942..a957be0f6 100644 --- a/pkg/operator/nfs/controller.go +++ b/pkg/operator/nfs/controller.go @@ -19,6 +19,7 @@ package nfs import ( "context" "fmt" + "path" "strings" "time" @@ -158,8 +159,8 @@ func (r *NFSServerReconciler) reconcileNFSServerConfig(ctx context.Context, cr * nfsGaneshaConfig := ` EXPORT { Export_Id = ` + fmt.Sprintf("%v", id) + `; - Path = /` + claimName + `; - Pseudo = /` + claimName + `; + Path = ` + path.Join("/", claimName) + `; + Pseudo = ` + path.Join("/", claimName) + `; Protocols = 4; Transports = TCP; Sectype = sys; @@ -287,12 +288,16 @@ func (r *NFSServerReconciler) reconcileNFSServer(ctx context.Context, cr *nfsv1a volumeMounts = append(volumeMounts, corev1.VolumeMount{ Name: shareName, - MountPath: "/" + claimName, + MountPath: path.Join("/", claimName), }) } sts.Spec.Template.Spec.Volumes = volumes - sts.Spec.Template.Spec.Containers[0].VolumeMounts = volumeMounts + for i, container := range sts.Spec.Template.Spec.Containers { + if container.Name == "nfs-server" || container.Name == "nfs-provisioner" { + sts.Spec.Template.Spec.Containers[i].VolumeMounts = volumeMounts + } + } return nil }) diff --git a/pkg/operator/nfs/controller_test.go b/pkg/operator/nfs/controller_test.go index d79946877..7030c6383 100644 --- a/pkg/operator/nfs/controller_test.go +++ b/pkg/operator/nfs/controller_test.go @@ -17,6 +17,8 @@ limitations under the License. package nfs import ( + "context" + "path" "reflect" "testing" @@ -92,7 +94,7 @@ func (r *resource) Generate() *nfsv1alpha1.NFSServer { } func TestNFSServerReconciler_Reconcile(t *testing.T) { - expectedStatefulSet := func(scheme *runtime.Scheme, cr *nfsv1alpha1.NFSServer) *appsv1.StatefulSet { + expectedServerFunc := func(scheme *runtime.Scheme, cr *nfsv1alpha1.NFSServer) *appsv1.StatefulSet { sts := newStatefulSetForNFSServer(cr) sts.Spec.Selector = &metav1.LabelSelector{ MatchLabels: newLabels(cr), @@ -137,16 +139,26 @@ func TestNFSServerReconciler_Reconcile(t *testing.T) { volumeMounts = append(volumeMounts, corev1.VolumeMount{ Name: shareName, - MountPath: "/" + claimName, + MountPath: path.Join("/", claimName), }) } sts.Status.ReadyReplicas = int32(cr.Spec.Replicas) sts.Spec.Template.Spec.Volumes = volumes - sts.Spec.Template.Spec.Containers[0].VolumeMounts = volumeMounts + for i, container := range sts.Spec.Template.Spec.Containers { + if container.Name == "nfs-server" || container.Name == "nfs-provisioner" { + sts.Spec.Template.Spec.Containers[i].VolumeMounts = volumeMounts + } + } return sts } + expectedServerServiceFunc := func(scheme *runtime.Scheme, cr *nfsv1alpha1.NFSServer) *corev1.Service { + svc := newServiceForNFSServer(cr) + _ = controllerutil.SetControllerReference(cr, svc, scheme) + return svc + } + rr := reconcile.Request{ NamespacedName: types.NamespacedName{ Name: "nfs-server", @@ -208,11 +220,18 @@ func TestNFSServerReconciler_Reconcile(t *testing.T) { scheme := clientgoscheme.Scheme scheme.AddKnownTypes(nfsv1alpha1.SchemeGroupVersion, tt.cr) + expectedServer := expectedServerFunc(scheme, tt.cr) + expectedServerService := expectedServerServiceFunc(scheme, tt.cr) + objs := []runtime.Object{ tt.cr, - expectedStatefulSet(scheme, tt.cr), + expectedServer, + expectedServerService, } + expectedServer.GetObjectKind().SetGroupVersionKind(appsv1.SchemeGroupVersion.WithKind("StatefulSet")) + expectedServerService.GetObjectKind().SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Service")) + fc := fake.NewFakeClient(objs...) fr := record.NewFakeRecorder(2) @@ -230,6 +249,24 @@ func TestNFSServerReconciler_Reconcile(t *testing.T) { if !reflect.DeepEqual(got, tt.want) { t.Errorf("NFSServerReconciler.Reconcile() = %v, want %v", got, tt.want) } + + gotServer := &appsv1.StatefulSet{} + if err := fc.Get(context.Background(), tt.args.req.NamespacedName, gotServer); err != nil { + t.Errorf("NFSServerReconciler.Reconcile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotServer, expectedServer) { + t.Errorf("NFSServerReconciler.Reconcile() = %v, want %v", gotServer, expectedServer) + } + + gotServerService := &corev1.Service{} + if err := fc.Get(context.Background(), tt.args.req.NamespacedName, gotServerService); err != nil { + t.Errorf("NFSServerReconciler.Reconcile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(gotServerService, expectedServerService) { + t.Errorf("NFSServerReconciler.Reconcile() = %v, want %v", gotServerService, expectedServerService) + } }) } } diff --git a/pkg/operator/nfs/provisioner.go b/pkg/operator/nfs/provisioner.go index c255bc684..598470b33 100644 --- a/pkg/operator/nfs/provisioner.go +++ b/pkg/operator/nfs/provisioner.go @@ -18,11 +18,14 @@ package nfs import ( "fmt" + "os" + "path" + "strings" "github.com/pkg/errors" rookclient "github.com/rook/rook/pkg/client/clientset/versioned" v1 "k8s.io/api/core/v1" - storage "k8s.io/api/storage/v1" + storagev1 "k8s.io/api/storage/v1" "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/pkg/apis/core/v1/helper" "sigs.k8s.io/sig-storage-lib-external-provisioner/controller" @@ -30,50 +33,87 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -type nfsProvisioner struct { +const ( + nfsServerNameSCParam = "nfsServerName" + nfsServerNamespaceSCParam = "nfsServerNamespace" + exportNameSCParam = "exportName" +) + +var ( + mountPath = "/" +) + +type Provisioner struct { client kubernetes.Interface rookClient rookclient.Interface } -var _ controller.Provisioner = &nfsProvisioner{} +var _ controller.Provisioner = &Provisioner{} // NewNFSProvisioner returns an instance of nfsProvisioner -func NewNFSProvisioner(clientset kubernetes.Interface, rookClientset rookclient.Interface) *nfsProvisioner { - return &nfsProvisioner{clientset, rookClientset} +func NewNFSProvisioner(clientset kubernetes.Interface, rookClientset rookclient.Interface) *Provisioner { + return &Provisioner{clientset, rookClientset} } -func (p *nfsProvisioner) Provision(options controller.ProvisionOptions) (*v1.PersistentVolume, error) { +func (p *Provisioner) Provision(options controller.ProvisionOptions) (*v1.PersistentVolume, error) { logger.Infof("nfs provisioner: ProvisionOptions %v", options) - // Get the storage class for this volume. - storageClass, err := p.getClassForPVC(options.PVC) - if err != nil { - return nil, err - } - if options.PVC.Spec.Selector != nil { return nil, fmt.Errorf("claim Selector is not supported") } - nfsServerName, present := storageClass.Parameters["nfsServerName"] - if !present { - return nil, errors.Errorf("NFS share Path not found in the storageclass: %v", storageClass.GetName()) - } - - nfsNamespace, present := storageClass.Parameters["nfsServerNamespace"] - if !present { - return nil, errors.Errorf("NFS share Path not found in the storageclass: %v", storageClass.GetName()) - } - - exportName, present := storageClass.Parameters["exportName"] - if !present { - return nil, errors.Errorf("NFS share Path not found in the storageclass: %v", storageClass.GetName()) - } - - nfsVolumeSource, err := p.getNFSVolumeSource(nfsServerName, nfsNamespace, exportName) + sc, err := p.storageClassForPVC(options.PVC) if err != nil { return nil, err } + + serverName, present := sc.Parameters[nfsServerNameSCParam] + if !present { + return nil, errors.Errorf("NFS share Path not found in the storageclass: %v", sc.GetName()) + } + + serverNamespace, present := sc.Parameters[nfsServerNamespaceSCParam] + if !present { + return nil, errors.Errorf("NFS share Path not found in the storageclass: %v", sc.GetName()) + } + + exportName, present := sc.Parameters[exportNameSCParam] + if !present { + return nil, errors.Errorf("NFS share Path not found in the storageclass: %v", sc.GetName()) + } + + nfsserver, err := p.rookClient.NfsV1alpha1().NFSServers(serverNamespace).Get(serverName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + nfsserversvc, err := p.client.CoreV1().Services(serverNamespace).Get(serverName, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + var ( + exportPath string + found bool + ) + + for _, export := range nfsserver.Spec.Exports { + if export.Name == exportName { + exportPath = path.Join(mountPath, export.PersistentVolumeClaim.ClaimName) + found = true + } + } + + if !found { + return nil, fmt.Errorf("No export name from storageclass is match with NFSServer %s in namespace %s", nfsserver.Name, nfsserver.Namespace) + } + + pvName := strings.Join([]string{options.PVC.Namespace, options.PVC.Name, options.PVName}, "-") + fullPath := path.Join(exportPath, pvName) + if err := os.MkdirAll(fullPath, 0700); err != nil { + return nil, errors.New("unable to create directory to provision new pv: " + err.Error()) + } + pv := &v1.PersistentVolume{ ObjectMeta: metav1.ObjectMeta{ Name: options.PVName, @@ -86,19 +126,68 @@ func (p *nfsProvisioner) Provision(options controller.ProvisionOptions) (*v1.Per v1.ResourceName(v1.ResourceStorage): options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)], }, PersistentVolumeSource: v1.PersistentVolumeSource{ - NFS: nfsVolumeSource, + NFS: &v1.NFSVolumeSource{ + Server: nfsserversvc.Spec.ClusterIP, + Path: fullPath, + ReadOnly: false, + }, }, }, } + return pv, nil } -func (p *nfsProvisioner) Delete(volume *v1.PersistentVolume) error { - return nil +func (p *Provisioner) Delete(volume *v1.PersistentVolume) error { + nfsPath := volume.Spec.PersistentVolumeSource.NFS.Path + pvName := path.Base(nfsPath) + + sc, err := p.storageClassForPV(volume) + if err != nil { + return err + } + + serverName, present := sc.Parameters[nfsServerNameSCParam] + if !present { + return errors.Errorf("NFS share Path not found in the storageclass: %v", sc.GetName()) + } + + serverNamespace, present := sc.Parameters[nfsServerNamespaceSCParam] + if !present { + return errors.Errorf("NFS share Path not found in the storageclass: %v", sc.GetName()) + } + + exportName, present := sc.Parameters[exportNameSCParam] + if !present { + return errors.Errorf("NFS share Path not found in the storageclass: %v", sc.GetName()) + } + + nfsserver, err := p.rookClient.NfsV1alpha1().NFSServers(serverNamespace).Get(serverName, metav1.GetOptions{}) + if err != nil { + return err + } + + var ( + exportPath string + found bool + ) + + for _, export := range nfsserver.Spec.Exports { + if export.Name == exportName { + exportPath = path.Join(mountPath, export.PersistentVolumeClaim.ClaimName) + found = true + } + } + + if !found { + return fmt.Errorf("No export name from storageclass is match with NFSServer %s in namespace %s", nfsserver.Name, nfsserver.Namespace) + } + + fullPath := path.Join(exportPath, pvName) + return os.RemoveAll(fullPath) } -// getClassForPV returns StorageClass -func (p *nfsProvisioner) getClassForPV(pv *v1.PersistentVolume) (*storage.StorageClass, error) { +func (p *Provisioner) storageClassForPV(pv *v1.PersistentVolume) (*storagev1.StorageClass, error) { if p.client == nil { return nil, fmt.Errorf("Cannot get kube client") } @@ -106,15 +195,11 @@ func (p *nfsProvisioner) getClassForPV(pv *v1.PersistentVolume) (*storage.Storag if className == "" { return nil, fmt.Errorf("Volume has no storage class") } - class, err := p.client.StorageV1().StorageClasses().Get(className, metav1.GetOptions{}) - if err != nil { - return nil, err - } - return class, nil + + return p.client.StorageV1().StorageClasses().Get(className, metav1.GetOptions{}) } -// getClassForPVC returns StorageClass -func (p *nfsProvisioner) getClassForPVC(pvc *v1.PersistentVolumeClaim) (*storage.StorageClass, error) { +func (p *Provisioner) storageClassForPVC(pvc *v1.PersistentVolumeClaim) (*storagev1.StorageClass, error) { if p.client == nil { return nil, fmt.Errorf("Cannot get kube client") } @@ -122,31 +207,6 @@ func (p *nfsProvisioner) getClassForPVC(pvc *v1.PersistentVolumeClaim) (*storage if className == "" { return nil, fmt.Errorf("Volume has no storage class") } - class, err := p.client.StorageV1().StorageClasses().Get(className, metav1.GetOptions{}) - if err != nil { - return nil, err - } - return class, nil -} -// getNFSVolumeSource returns the nfs source configuration for the pv -func (p *nfsProvisioner) getNFSVolumeSource(nfsServerName, namespace, exportName string) (*v1.NFSVolumeSource, error) { - nfsServer, err := p.rookClient.NfsV1alpha1().NFSServers(namespace).Get(nfsServerName, metav1.GetOptions{}) - if err != nil { - return nil, err - } - nfsService, err := p.client.CoreV1().Services(namespace).Get(nfsServerName, metav1.GetOptions{}) - if err != nil { - return nil, err - } - for _, export := range nfsServer.Spec.Exports { - if export.Name == exportName { - return &v1.NFSVolumeSource{ - ReadOnly: false, - Server: nfsService.Spec.ClusterIP, - Path: "/" + export.PersistentVolumeClaim.ClaimName, - }, nil - } - } - return nil, errors.Errorf("exportName not Found") + return p.client.StorageV1().StorageClasses().Get(className, metav1.GetOptions{}) } diff --git a/pkg/operator/nfs/provisioner_test.go b/pkg/operator/nfs/provisioner_test.go new file mode 100644 index 000000000..f1da2e4fb --- /dev/null +++ b/pkg/operator/nfs/provisioner_test.go @@ -0,0 +1,221 @@ +/* +Copyright 2019 The Rook Authors. All rights reserved. + +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 nfs + +import ( + "os" + "reflect" + "testing" + + rookclient "github.com/rook/rook/pkg/client/clientset/versioned" + rookclientfake "github.com/rook/rook/pkg/client/clientset/versioned/fake" + corev1 "k8s.io/api/core/v1" + storagev1 "k8s.io/api/storage/v1" + apiresource "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + k8sclientfake "k8s.io/client-go/kubernetes/fake" + "sigs.k8s.io/sig-storage-lib-external-provisioner/controller" +) + +func init() { + mountPath = "/tmp/test-rook-nfs" +} + +func newDummyStorageClass(name string, nfsServerNamespacedName types.NamespacedName, reclaimPolicy corev1.PersistentVolumeReclaimPolicy) *storagev1.StorageClass { + return &storagev1.StorageClass{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Parameters: map[string]string{ + nfsServerNameSCParam: nfsServerNamespacedName.Name, + nfsServerNamespaceSCParam: nfsServerNamespacedName.Namespace, + exportNameSCParam: name, + }, + ReclaimPolicy: &reclaimPolicy, + } +} + +func newDummyPVC(name, namespace string, capacity apiresource.Quantity, storageClassName string) *corev1.PersistentVolumeClaim { + return &corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + }, + Spec: corev1.PersistentVolumeClaimSpec{ + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceName(corev1.ResourceStorage): capacity, + }, + }, + StorageClassName: &storageClassName, + }, + } +} + +func newDummyPV(name, scName, expectedPath string, expectedCapacity apiresource.Quantity, expectedReclaimPolicy corev1.PersistentVolumeReclaimPolicy) *corev1.PersistentVolume { + return &corev1.PersistentVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: corev1.PersistentVolumeSpec{ + PersistentVolumeReclaimPolicy: expectedReclaimPolicy, + Capacity: corev1.ResourceList{ + corev1.ResourceName(corev1.ResourceStorage): expectedCapacity, + }, + PersistentVolumeSource: corev1.PersistentVolumeSource{ + NFS: &corev1.NFSVolumeSource{ + Path: expectedPath, + }, + }, + StorageClassName: scName, + }, + } +} + +func TestProvisioner_Provision(t *testing.T) { + if err := os.MkdirAll(mountPath, 0755); err != nil { + t.Error("error creating test provisioner directory") + } + + defer os.RemoveAll(mountPath) + + nfsserver := newCustomResource(types.NamespacedName{Name: "test-nfsserver", Namespace: "test-nfsserver"}).WithExports("share-1", "ReadWrite", "none", "test-claim").Generate() + + type fields struct { + client kubernetes.Interface + rookClient rookclient.Interface + } + type args struct { + options controller.ProvisionOptions + } + tests := []struct { + name string + fields fields + args args + want *corev1.PersistentVolume + wantErr bool + }{ + { + name: "success create volume", + fields: fields{ + client: k8sclientfake.NewSimpleClientset( + newServiceForNFSServer(nfsserver), + newDummyStorageClass("share-1", types.NamespacedName{Name: nfsserver.Name, Namespace: nfsserver.Namespace}, corev1.PersistentVolumeReclaimDelete), + ), + rookClient: rookclientfake.NewSimpleClientset( + nfsserver, + ), + }, + args: args{ + options: controller.ProvisionOptions{ + StorageClass: newDummyStorageClass("share-1", types.NamespacedName{Name: nfsserver.Name, Namespace: nfsserver.Namespace}, corev1.PersistentVolumeReclaimDelete), + PVName: "share-1-pvc", + PVC: newDummyPVC("share-1-pvc", "default", apiresource.MustParse("1Mi"), "share-1"), + }, + }, + want: newDummyPV("share-1-pvc", "", "/tmp/test-rook-nfs/test-claim/default-share-1-pvc-share-1-pvc", apiresource.MustParse("1Mi"), corev1.PersistentVolumeReclaimDelete), + }, + { + name: "no matching export", + fields: fields{ + client: k8sclientfake.NewSimpleClientset( + newServiceForNFSServer(nfsserver), + newDummyStorageClass("foo", types.NamespacedName{Name: nfsserver.Name, Namespace: nfsserver.Namespace}, corev1.PersistentVolumeReclaimDelete), + ), + rookClient: rookclientfake.NewSimpleClientset( + nfsserver, + ), + }, + args: args{ + options: controller.ProvisionOptions{ + StorageClass: newDummyStorageClass("foo", types.NamespacedName{Name: nfsserver.Name, Namespace: nfsserver.Namespace}, corev1.PersistentVolumeReclaimDelete), + PVName: "share-1-pvc", + PVC: newDummyPVC("share-1-pvc", "default", apiresource.MustParse("1Mi"), "foo"), + }, + }, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Provisioner{ + client: tt.fields.client, + rookClient: tt.fields.rookClient, + } + got, err := p.Provision(tt.args.options) + if (err != nil) != tt.wantErr { + t.Errorf("Provisioner.Provision() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("Provisioner.Provision() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestProvisioner_Delete(t *testing.T) { + if err := os.MkdirAll(mountPath, 0755); err != nil { + t.Error("error creating test provisioner directory") + } + + defer os.RemoveAll(mountPath) + + nfsserver := newCustomResource(types.NamespacedName{Name: "test-nfsserver", Namespace: "test-nfsserver"}).WithExports("share-1", "ReadWrite", "none", "test-claim").Generate() + type fields struct { + client kubernetes.Interface + rookClient rookclient.Interface + } + type args struct { + volume *corev1.PersistentVolume + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + { + name: "success delete volume", + fields: fields{ + client: k8sclientfake.NewSimpleClientset( + newServiceForNFSServer(nfsserver), + newDummyStorageClass("share-1", types.NamespacedName{Name: nfsserver.Name, Namespace: nfsserver.Namespace}, corev1.PersistentVolumeReclaimDelete), + ), + rookClient: rookclientfake.NewSimpleClientset( + nfsserver, + ), + }, + args: args{ + volume: newDummyPV("share-1-pvc", "share-1", "/tmp/test-rook-nfs/test-claim/default-share-1-pvc-share-1-pvc", apiresource.MustParse("1Mi"), corev1.PersistentVolumeReclaimDelete), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := &Provisioner{ + client: tt.fields.client, + rookClient: tt.fields.rookClient, + } + if err := p.Delete(tt.args.volume); (err != nil) != tt.wantErr { + t.Errorf("Provisioner.Delete() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/pkg/operator/nfs/spec.go b/pkg/operator/nfs/spec.go index ca2b0f0b6..3a3ccd904 100644 --- a/pkg/operator/nfs/spec.go +++ b/pkg/operator/nfs/spec.go @@ -67,6 +67,7 @@ func newServiceForNFSServer(cr *nfsv1alpha1.NFSServer) *corev1.Service { } func newStatefulSetForNFSServer(cr *nfsv1alpha1.NFSServer) *appsv1.StatefulSet { + privilaged := true replicas := int32(cr.Spec.Replicas) return &appsv1.StatefulSet{ ObjectMeta: metav1.ObjectMeta{ @@ -87,10 +88,9 @@ func newStatefulSetForNFSServer(cr *nfsv1alpha1.NFSServer) *appsv1.StatefulSet { ServiceAccountName: "rook-nfs-server", Containers: []corev1.Container{ { - ImagePullPolicy: "IfNotPresent", - Name: cr.Name, - Image: "rook/nfs:master", - Args: []string{"nfs", "server", "--ganeshaConfigPath=" + nfsConfigMapPath + "/" + cr.Name}, + Name: "nfs-server", + Image: "rook/nfs:master", + Args: []string{"nfs", "server", "--ganeshaConfigPath=" + nfsConfigMapPath + "/" + cr.Name}, Ports: []corev1.ContainerPort{ { Name: "nfs-port", @@ -110,6 +110,16 @@ func newStatefulSetForNFSServer(cr *nfsv1alpha1.NFSServer) *appsv1.StatefulSet { }, }, }, + { + Name: "nfs-provisioner", + Image: "rook/nfs:master", + Args: []string{"nfs", "provisioner", "--provisioner=" + "nfs.rook.io/" + cr.Name + "-provisioner"}, + TerminationMessagePath: "/dev/termination-log", + TerminationMessagePolicy: corev1.TerminationMessageReadFile, + SecurityContext: &corev1.SecurityContext{ + Privileged: &privilaged, + }, + }, }, }, }, diff --git a/tests/framework/installer/nfs_manifests.go b/tests/framework/installer/nfs_manifests.go index 16a8138f9..1f134820e 100644 --- a/tests/framework/installer/nfs_manifests.go +++ b/tests/framework/installer/nfs_manifests.go @@ -320,93 +320,6 @@ spec: valueFrom: fieldRef: fieldPath: metadata.namespace ---- -kind: ClusterRole -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: rook-nfs-provisioner-runner -rules: - - apiGroups: [""] - resources: ["persistentvolumes"] - verbs: ["get", "list", "watch", "create", "delete"] - - apiGroups: [""] - resources: ["persistentvolumeclaims"] - verbs: ["get", "list", "watch", "update"] - - apiGroups: ["storage.k8s.io"] - resources: ["storageclasses"] - verbs: ["get", "list", "watch"] - - apiGroups: [""] - resources: ["events"] - verbs: ["create", "update", "patch"] - - apiGroups: [""] - resources: ["services", "endpoints"] - verbs: ["get"] - - apiGroups: ["extensions"] - resources: ["podsecuritypolicies"] - resourceNames: ["nfs-provisioner"] - verbs: ["use"] - - apiGroups: [""] - resources: ["endpoints"] - verbs: ["get", "list", "watch", "create", "update", "patch"] - - apiGroups: - - nfs.rook.io - resources: - - "*" - verbs: - - "*" ---- -kind: ClusterRoleBinding -apiVersion: rbac.authorization.k8s.io/v1 -metadata: - name: run-nfs-provisioner -subjects: - - kind: ServiceAccount - name: rook-nfs-provisioner - # replace with namespace where provisioner is deployed - namespace: ` + namespace + ` -roleRef: - kind: ClusterRole - name: rook-nfs-provisioner-runner - apiGroup: rbac.authorization.k8s.io ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: rook-nfs-provisioner - namespace: ` + namespace + ` ---- -kind: Deployment -apiVersion: apps/v1 -metadata: - name: rook-nfs-provisioner - namespace: ` + namespace + ` -spec: - selector: - matchLabels: - app: rook-nfs-provisioner - replicas: 1 - strategy: - type: Recreate - template: - metadata: - labels: - app: rook-nfs-provisioner - spec: - serviceAccount: rook-nfs-provisioner - containers: - - name: rook-nfs-provisioner - image: rook/nfs:master - imagePullPolicy: IfNotPresent - args: ["nfs", "provisioner","--provisioner=rook.io/nfs-provisioner"] - env: - - name: POD_NAME - valueFrom: - fieldRef: - fieldPath: metadata.name - - name: POD_NAMESPACE - valueFrom: - fieldRef: - fieldPath: metadata.namespace ` } @@ -462,7 +375,7 @@ parameters: exportName: nfs-share nfsServerName: ` + namespace + ` nfsServerNamespace: ` + namespace + ` -provisioner: rook.io/nfs-provisioner +provisioner: nfs.rook.io/` + namespace + `-provisioner reclaimPolicy: Delete volumeBindingMode: Immediate --- @@ -476,7 +389,7 @@ parameters: exportName: nfs-share1 nfsServerName: ` + namespace + ` nfsServerNamespace: ` + namespace + ` -provisioner: rook.io/nfs-provisioner +provisioner: nfs.rook.io/` + namespace + `-provisioner reclaimPolicy: Delete volumeBindingMode: Immediate --- @@ -515,6 +428,53 @@ metadata: name: rook-nfs-server namespace: ` + namespace + ` --- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: rook-nfs-provisioner-runner +rules: + - apiGroups: [""] + resources: ["persistentvolumes"] + verbs: ["get", "list", "watch", "create", "delete"] + - apiGroups: [""] + resources: ["persistentvolumeclaims"] + verbs: ["get", "list", "watch", "update"] + - apiGroups: ["storage.k8s.io"] + resources: ["storageclasses"] + verbs: ["get", "list", "watch"] + - apiGroups: [""] + resources: ["events"] + verbs: ["create", "update", "patch"] + - apiGroups: [""] + resources: ["services", "endpoints"] + verbs: ["get"] + - apiGroups: ["extensions"] + resources: ["podsecuritypolicies"] + resourceNames: ["nfs-provisioner"] + verbs: ["use"] + - apiGroups: [""] + resources: ["endpoints"] + verbs: ["get", "list", "watch", "create", "update", "patch"] + - apiGroups: + - nfs.rook.io + resources: + - "*" + verbs: + - "*" +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: run-nfs-provisioner +subjects: + - kind: ServiceAccount + name: rook-nfs-server + namespace: ` + namespace + ` +roleRef: + kind: ClusterRole + name: rook-nfs-provisioner-runner + apiGroup: rbac.authorization.k8s.io +--- apiVersion: v1 kind: PersistentVolumeClaim metadata: diff --git a/tests/integration/nfs_test.go b/tests/integration/nfs_test.go index 51964e4e4..17d98d27d 100644 --- a/tests/integration/nfs_test.go +++ b/tests/integration/nfs_test.go @@ -68,7 +68,7 @@ func (suite *NfsSuite) TearDownSuite() { } func (suite *NfsSuite) Setup() { - suite.namespace = "nfs-ns" + suite.namespace = "rook-nfs" suite.systemNamespace = installer.SystemNamespace(suite.namespace) suite.instanceCount = 1