Files
my-rook-config/pkg/operator/nfs/controller_test.go
T

273 lines
8.1 KiB
Go

/*
Copyright 2018 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 (
"context"
"path"
"reflect"
"testing"
nfsv1alpha1 "github.com/rook/rook/pkg/apis/nfs.rook.io/v1alpha1"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/record"
"k8s.io/utils/pointer"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
type resourceGenerator interface {
WithExports(exportName, serverAccessMode, serverSquashType, pvcName string) resourceGenerator
WithState(state nfsv1alpha1.NFSServerState) resourceGenerator
Generate() *nfsv1alpha1.NFSServer
}
type resource struct {
name string
namespace string
exports []nfsv1alpha1.ExportsSpec
state nfsv1alpha1.NFSServerState
}
func newCustomResource(namespacedName types.NamespacedName) resourceGenerator {
return &resource{
name: namespacedName.Name,
namespace: namespacedName.Namespace,
}
}
func (r *resource) WithExports(exportName, serverAccessMode, serverSquashType, pvcName string) resourceGenerator {
r.exports = append(r.exports, nfsv1alpha1.ExportsSpec{
Name: exportName,
Server: nfsv1alpha1.ServerSpec{
AccessMode: serverAccessMode,
Squash: serverSquashType,
},
PersistentVolumeClaim: corev1.PersistentVolumeClaimVolumeSource{
ClaimName: pvcName,
},
})
return r
}
func (r *resource) WithState(state nfsv1alpha1.NFSServerState) resourceGenerator {
r.state = state
return r
}
func (r *resource) Generate() *nfsv1alpha1.NFSServer {
return &nfsv1alpha1.NFSServer{
ObjectMeta: metav1.ObjectMeta{
Name: r.name,
Namespace: r.namespace,
},
Spec: nfsv1alpha1.NFSServerSpec{
Replicas: 1,
Exports: r.exports,
},
Status: nfsv1alpha1.NFSServerStatus{
State: r.state,
},
}
}
func TestNFSServerReconciler_Reconcile(t *testing.T) {
expectedServerFunc := func(scheme *runtime.Scheme, cr *nfsv1alpha1.NFSServer) *appsv1.StatefulSet {
sts := newStatefulSetForNFSServer(cr)
sts.Spec.Selector = &metav1.LabelSelector{
MatchLabels: newLabels(cr),
}
_ = controllerutil.SetControllerReference(cr, sts, scheme)
volumes := []corev1.Volume{
{
Name: cr.Name,
VolumeSource: corev1.VolumeSource{
ConfigMap: &corev1.ConfigMapVolumeSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: cr.Name,
},
Items: []corev1.KeyToPath{
{
Key: cr.Name,
Path: cr.Name,
},
},
DefaultMode: pointer.Int32Ptr(corev1.ConfigMapVolumeSourceDefaultMode),
},
},
},
}
volumeMounts := []corev1.VolumeMount{
{
Name: cr.Name,
MountPath: nfsConfigMapPath,
},
}
for _, export := range cr.Spec.Exports {
shareName := export.Name
claimName := export.PersistentVolumeClaim.ClaimName
volumes = append(volumes, corev1.Volume{
Name: shareName,
VolumeSource: corev1.VolumeSource{
PersistentVolumeClaim: &corev1.PersistentVolumeClaimVolumeSource{
ClaimName: claimName,
},
},
})
volumeMounts = append(volumeMounts, corev1.VolumeMount{
Name: shareName,
MountPath: path.Join("/", claimName),
})
}
sts.Status.ReadyReplicas = int32(cr.Spec.Replicas)
sts.Spec.Template.Spec.Volumes = volumes
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",
Namespace: "nfs-server",
},
}
type args struct {
req ctrl.Request
}
tests := []struct {
name string
args args
cr *nfsv1alpha1.NFSServer
want ctrl.Result
wantErr bool
}{
{
name: "Reconcile NFS Server Should Set Initializing State when State is Empty",
args: args{
req: rr,
},
cr: newCustomResource(rr.NamespacedName).WithExports("share1", "ReadWrite", "none", "test-claim").Generate(),
want: reconcile.Result{Requeue: true},
},
{
name: "Reconcile NFS Server Shouldn't Requeue when State is Error",
args: args{
req: rr,
},
cr: newCustomResource(rr.NamespacedName).WithExports("share1", "ReadWrite", "none", "test-claim").WithState(nfsv1alpha1.StateError).Generate(),
want: reconcile.Result{Requeue: false},
},
{
name: "Reconcile NFS Server Should Error on Duplicate Export",
args: args{
req: rr,
},
cr: newCustomResource(rr.NamespacedName).WithExports("share1", "ReadWrite", "none", "test-claim").WithExports("share1", "ReadWrite", "none", "test-claim").WithState(nfsv1alpha1.StateInitializing).Generate(),
wantErr: true,
},
{
name: "Reconcile NFS Server With Single Export",
args: args{
req: rr,
},
cr: newCustomResource(rr.NamespacedName).WithExports("share1", "ReadWrite", "none", "test-claim").WithState(nfsv1alpha1.StateInitializing).Generate(),
},
{
name: "Reconcile NFS Server With Multiple Export",
args: args{
req: rr,
},
cr: newCustomResource(rr.NamespacedName).WithExports("share1", "ReadWrite", "none", "test-claim").WithExports("share2", "ReadOnly", "none", "another-test-claim").WithState(nfsv1alpha1.StateInitializing).Generate(),
},
}
for _, tt := range tests {
t.Run(tt.name, func(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,
expectedServer,
expectedServerService,
}
expectedServer.GetObjectKind().SetGroupVersionKind(appsv1.SchemeGroupVersion.WithKind("StatefulSet"))
expectedServerService.GetObjectKind().SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("Service"))
fc := fake.NewClientBuilder().WithRuntimeObjects(objs...).Build()
fr := record.NewFakeRecorder(2)
r := &NFSServerReconciler{
Client: fc,
Scheme: scheme,
Log: logger,
Recorder: fr,
}
got, err := r.Reconcile(context.TODO(), tt.args.req)
if (err != nil) != tt.wantErr {
t.Errorf("NFSServerReconciler.Reconcile() error = %v, wantErr %v", err, tt.wantErr)
return
}
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)
}
})
}
}