From 0b3650274e6d93e70a45d7aa93c0f91a44b14eed Mon Sep 17 00:00:00 2001 From: Sirish Bathina Date: Fri, 16 Oct 2020 10:57:04 -1000 Subject: [PATCH] UTs for k8s checks (#17) * UTs for k8s checks * more tests --- go.mod | 1 + pkg/kubestr/kubernetes_checks.go | 49 ++-- pkg/kubestr/kubernetes_checks_test.go | 160 +++++++++++ pkg/kubestr/kubestr.go | 10 +- pkg/kubestr/storage_provisioners.go | 111 ++++---- pkg/kubestr/storage_provisioners_test.go | 327 +++++++++++++++++++++++ 6 files changed, 592 insertions(+), 66 deletions(-) create mode 100644 pkg/kubestr/kubernetes_checks_test.go create mode 100644 pkg/kubestr/storage_provisioners_test.go diff --git a/go.mod b/go.mod index 8455fc7..7cfeeac 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/kanisterio/kanister v0.0.0-00010101000000-000000000000 github.com/pkg/errors v0.9.1 github.com/spf13/cobra v1.0.0 + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 k8s.io/api v0.19.0 k8s.io/apimachinery v0.19.0 k8s.io/client-go v0.19.0 diff --git a/pkg/kubestr/kubernetes_checks.go b/pkg/kubestr/kubernetes_checks.go index 37be1f7..5e9b1ea 100644 --- a/pkg/kubestr/kubernetes_checks.go +++ b/pkg/kubestr/kubernetes_checks.go @@ -5,6 +5,7 @@ import ( "strconv" "github.com/pkg/errors" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" version "k8s.io/apimachinery/pkg/version" ) @@ -23,15 +24,15 @@ const ( func (p *Kubestr) KubernetesChecks() []*TestOutput { var result []*TestOutput result = append(result, p.validateK8sVersion()) - result = append(result, p.getRBAC()) - result = append(result, p.getAggregatedLayer()) + result = append(result, p.validateRBAC()) + result = append(result, p.validateAggregatedLayer()) return result } // validateK8sVersion validates the clusters K8s version func (p *Kubestr) validateK8sVersion() *TestOutput { testName := "Kubernetes Version Check" - version, err := p.getK8sVersion() + version, err := p.validateK8sVersionHelper() if err != nil { return makeTestOutput(testName, StatusError, err.Error(), nil) } @@ -39,14 +40,14 @@ func (p *Kubestr) validateK8sVersion() *TestOutput { } // getK8sVersion fetches the k8s vesion -func (p *Kubestr) getK8sVersion() (*version.Info, error) { +func (p *Kubestr) validateK8sVersionHelper() (*version.Info, error) { version, err := p.cli.Discovery().ServerVersion() if err != nil { return nil, err } majorStr := version.Major - if string(majorStr[len(majorStr)-1]) == "+" { + if len(majorStr) > 1 && string(majorStr[len(majorStr)-1]) == "+" { majorStr = majorStr[:len(majorStr)-1] } major, err := strconv.Atoi(majorStr) @@ -55,7 +56,7 @@ func (p *Kubestr) getK8sVersion() (*version.Info, error) { } minorStr := version.Minor - if string(minorStr[len(minorStr)-1]) == "+" { + if len(minorStr) > 1 && string(minorStr[len(minorStr)-1]) == "+" { minorStr = minorStr[:len(minorStr)-1] } minor, err := strconv.Atoi(minorStr) @@ -69,33 +70,49 @@ func (p *Kubestr) getK8sVersion() (*version.Info, error) { return version, nil } -// getRBAC runs the Rbac test -func (p *Kubestr) getRBAC() *TestOutput { +func (p *Kubestr) validateRBAC() *TestOutput { testName := "RBAC Check" //fmt.Println(" Checking if Kubernetes RBAC is enabled:") - serverGroups, err := p.cli.Discovery().ServerGroups() + group, err := p.validateRBACHelper() if err != nil { return makeTestOutput(testName, StatusError, err.Error(), nil) } + return makeTestOutput(testName, StatusOK, "Kubernetes RBAC is enabled", *group) +} + +// getRBAC runs the Rbac test +func (p *Kubestr) validateRBACHelper() (*v1.APIGroup, error) { + serverGroups, err := p.cli.Discovery().ServerGroups() + if err != nil { + return nil, err + } for _, group := range serverGroups.Groups { if group.Name == RbacGroupName { - return makeTestOutput(testName, StatusOK, "Kubernetes RBAC is enabled", group) + return &group, nil } } - return makeTestOutput(testName, StatusError, "Kubernetes RBAC is not enabled", nil) + return nil, fmt.Errorf("Kubernetes RBAC is not enabled") +} + +func (p *Kubestr) validateAggregatedLayer() *TestOutput { + testName := "Aggregated Layer Check" + resourceList, err := p.validateAggregatedLayerHelper() + if err != nil { + makeTestOutput(testName, StatusError, err.Error(), nil) + } + return makeTestOutput(testName, StatusOK, "The Kubernetes Aggregated Layer is enabled", resourceList) } // getAggregatedLayer checks the aggregated API layer -func (p *Kubestr) getAggregatedLayer() *TestOutput { - testName := "Aggregated Layer Check" +func (p *Kubestr) validateAggregatedLayerHelper() (*v1.APIResourceList, error) { _, serverResources, err := p.cli.Discovery().ServerGroupsAndResources() if err != nil { - return makeTestOutput(testName, StatusError, err.Error(), nil) + return nil, err } for _, resourceList := range serverResources { if resourceList.GroupVersion == "apiregistration.k8s.io/v1" || resourceList.GroupVersion == "apiregistration.k8s.io/v1beta1" { - return makeTestOutput(testName, StatusOK, "The Kubernetes Aggregated Layer is enabled", resourceList) + return resourceList, nil } } - return makeTestOutput(testName, StatusError, "Can not detect the Aggregated Layer. Is it enabled?", nil) + return nil, fmt.Errorf("Can not detect the Aggregated Layer. Is it enabled?") } diff --git a/pkg/kubestr/kubernetes_checks_test.go b/pkg/kubestr/kubernetes_checks_test.go new file mode 100644 index 0000000..58bf79b --- /dev/null +++ b/pkg/kubestr/kubernetes_checks_test.go @@ -0,0 +1,160 @@ +package kubestr + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + version "k8s.io/apimachinery/pkg/version" + discoveryfake "k8s.io/client-go/discovery/fake" + "k8s.io/client-go/kubernetes/fake" + + . "gopkg.in/check.v1" +) + +func Test(t *testing.T) { TestingT(t) } + +type K8sChecksTestSuite struct{} + +var _ = Suite(&K8sChecksTestSuite{}) + +func (s *K8sChecksTestSuite) TestGetK8sVersion(c *C) { + for _, tc := range []struct { + ver *version.Info + checker Checker + out *version.Info + }{ + { + ver: &version.Info{Major: "1", Minor: "17", GitVersion: "v1.17"}, + checker: IsNil, + out: &version.Info{Major: "1", Minor: "17", GitVersion: "v1.17"}, + }, + { + ver: &version.Info{Major: "1", Minor: "11", GitVersion: "v1.11"}, + checker: NotNil, + out: &version.Info{Major: "1", Minor: "11", GitVersion: "v1.11"}, + }, + { + ver: &version.Info{Major: "1", Minor: "", GitVersion: "v1."}, + checker: NotNil, + out: nil, + }, + { + ver: &version.Info{Major: "", Minor: "11", GitVersion: "v."}, + checker: NotNil, + out: nil, + }, + } { + cli := fake.NewSimpleClientset() + cli.Discovery().(*discoveryfake.FakeDiscovery).FakedServerVersion = tc.ver + p := &Kubestr{cli: cli} + out, err := p.validateK8sVersionHelper() + c.Assert(out, DeepEquals, tc.out) + c.Check(err, tc.checker) + } +} + +func (s *K8sChecksTestSuite) TestValidateRBAC(c *C) { + for _, tc := range []struct { + resources []*metav1.APIResourceList + checker Checker + out *v1.APIGroup + }{ + { + resources: []*metav1.APIResourceList{ + { + GroupVersion: "/////", + }, + }, + checker: NotNil, + out: nil, + }, + { + resources: []*metav1.APIResourceList{ + { + GroupVersion: "rbac.authorization.k8s.io/v1", + }, + }, + checker: IsNil, + out: &v1.APIGroup{ + Name: "rbac.authorization.k8s.io", + Versions: []v1.GroupVersionForDiscovery{ + {GroupVersion: "rbac.authorization.k8s.io/v1", Version: "v1"}, + }, + PreferredVersion: v1.GroupVersionForDiscovery{GroupVersion: "rbac.authorization.k8s.io/v1", Version: "v1"}, + }, + }, + { + resources: []*metav1.APIResourceList{ + { + GroupVersion: "notrbac.authorization.k8s.io/v1", + }, + }, + checker: NotNil, + out: nil, + }, + } { + cli := fake.NewSimpleClientset() + cli.Discovery().(*discoveryfake.FakeDiscovery).Resources = tc.resources + p := &Kubestr{cli: cli} + out, err := p.validateRBACHelper() + c.Assert(out, DeepEquals, tc.out) + c.Check(err, tc.checker) + } +} + +func (s *K8sChecksTestSuite) TestValidateAggregatedLayer(c *C) { + for _, tc := range []struct { + resources []*metav1.APIResourceList + checker Checker + out *metav1.APIResourceList + }{ + { + resources: []*metav1.APIResourceList{ + { + GroupVersion: "/////", + }, + }, + checker: NotNil, + out: nil, + }, + { + resources: []*metav1.APIResourceList{ + { + GroupVersion: "apiregistration.k8s.io/v1", + }, + }, + checker: IsNil, + out: &metav1.APIResourceList{ + GroupVersion: "apiregistration.k8s.io/v1", + }, + }, + { + resources: []*metav1.APIResourceList{ + { + GroupVersion: "apiregistration.k8s.io/v1beta1", + }, + }, + checker: IsNil, + out: &metav1.APIResourceList{ + GroupVersion: "apiregistration.k8s.io/v1beta1", + }, + }, + { + resources: []*metav1.APIResourceList{ + { + GroupVersion: "notapiregistration.k8s.io/v1", + }, + }, + checker: NotNil, + out: nil, + }, + } { + cli := fake.NewSimpleClientset() + cli.Discovery().(*discoveryfake.FakeDiscovery).Resources = tc.resources + p := &Kubestr{cli: cli} + out, err := p.validateAggregatedLayerHelper() + c.Assert(out, DeepEquals, tc.out) + c.Check(err, tc.checker) + } +} diff --git a/pkg/kubestr/kubestr.go b/pkg/kubestr/kubestr.go index ddf1c6a..aec11ac 100644 --- a/pkg/kubestr/kubestr.go +++ b/pkg/kubestr/kubestr.go @@ -14,6 +14,7 @@ import ( type Kubestr struct { cli kubernetes.Interface dynCli dynamic.Interface + sdsfgValidator snapshotDataSourceFG storageClassList *sv1.StorageClassList volumeSnapshotClassList *unstructured.UnstructuredList } @@ -38,7 +39,14 @@ func NewKubestr() (*Kubestr, error) { if err != nil { return nil, err } - return &Kubestr{cli: cli, dynCli: dynCli}, nil + return &Kubestr{ + cli: cli, + dynCli: dynCli, + sdsfgValidator: &snapshotDataSourceFGValidator{ + cli: cli, + dynCli: dynCli, + }, + }, nil } // getDynCli loads the config and returns a dynamic CLI diff --git a/pkg/kubestr/storage_provisioners.go b/pkg/kubestr/storage_provisioners.go index e30dd10..d4d1cd4 100644 --- a/pkg/kubestr/storage_provisioners.go +++ b/pkg/kubestr/storage_provisioners.go @@ -15,6 +15,8 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" unstructured "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" ) const ( @@ -122,6 +124,8 @@ func (v *Provisioner) Print() { v.CSIDriver.Print(" ") case strings.HasPrefix(v.ProvisionerName, "kubernetes.io"): fmt.Println(" This is an in tree provisioner.") + case strings.Contains(v.ProvisionerName, "csi"): + fmt.Println(" This might be a CSI Driver. But it is not publicly listed.") default: fmt.Println(" Unknown driver type.") } @@ -225,7 +229,7 @@ func (p *Kubestr) processProvisioner(ctx context.Context, provisioner string) (* for _, vsc := range vscs.Items { if p.getDriverNameFromUVSC(vsc, csiSnapshotGroupVersion.GroupVersion) == provisioner { retProvisioner.VolumeSnapshotClasses = append(retProvisioner.VolumeSnapshotClasses, - p.validateVolumeSnapshotClass(vsc, provisioner, csiSnapshotGroupVersion.GroupVersion)) + p.validateVolumeSnapshotClass(vsc, csiSnapshotGroupVersion.GroupVersion)) } } } @@ -247,7 +251,7 @@ func (p *Kubestr) hasCSIDriverObject(ctx context.Context, provisioner string) bo } func (p *Kubestr) isK8sVersionCSISnapshotCapable(ctx context.Context) (bool, error) { - k8sVersion, err := p.getK8sVersion() + k8sVersion, err := p.validateK8sVersionHelper() if err != nil { return false, err } @@ -260,52 +264,7 @@ func (p *Kubestr) isK8sVersionCSISnapshotCapable(ctx context.Context) (bool, err return false, err } if minor < 17 && k8sVersion.Major == "1" { - return p.validateVolumeSnapshotDataSourceFeatureGate(ctx) - } - return true, nil -} - -func (p *Kubestr) validateVolumeSnapshotDataSourceFeatureGate(ctx context.Context) (bool, error) { - ns := getPodNamespace() - - // deletes if exists. If it doesn't exist, this is a noop - err := kanvolume.DeletePVC(p.cli, ns, FeatureGateTestPVCName) - if err != nil { - return false, errors.Wrap(err, "Error deleting VolumeSnapshotDataSource feature-gate validation pvc") - } - // defer delete - defer func() { - _ = kanvolume.DeletePVC(p.cli, ns, FeatureGateTestPVCName) - }() - - // create PVC - snapshotKind := "VolumeSnapshot" - snapshotAPIGroup := "snapshot.storage.k8s.io" - pvc := &v1.PersistentVolumeClaim{ - ObjectMeta: metav1.ObjectMeta{ - Name: FeatureGateTestPVCName, - }, - Spec: v1.PersistentVolumeClaimSpec{ - AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, - DataSource: &v1.TypedLocalObjectReference{ - APIGroup: &snapshotAPIGroup, - Kind: snapshotKind, - Name: "fakeSnap", - }, - Resources: v1.ResourceRequirements{ - Requests: v1.ResourceList{ - v1.ResourceStorage: resource.MustParse("1Gi"), - }, - }, - }, - } - - pvcRes, err := p.cli.CoreV1().PersistentVolumeClaims(ns).Create(ctx, pvc, metav1.CreateOptions{}) - if err != nil { - return false, errors.Wrap(err, "Error creating VolumeSnapshotDataSource feature-gate validation pvc") - } - if pvcRes.Spec.DataSource == nil { - return false, nil + return p.sdsfgValidator.validate(ctx) } return true, nil } @@ -320,7 +279,7 @@ func (p *Kubestr) validateStorageClass(provisioner string, storageClass sv1.Stor } // validateVolumeSnapshotClass validates the VolumeSnapshotClass -func (p *Kubestr) validateVolumeSnapshotClass(vsc unstructured.Unstructured, provisionerName string, groupVersion string) *VSCInfo { +func (p *Kubestr) validateVolumeSnapshotClass(vsc unstructured.Unstructured, groupVersion string) *VSCInfo { retVSC := &VSCInfo{ Name: vsc.GetName(), Raw: vsc, @@ -414,3 +373,57 @@ func (p *Kubestr) getCSIGroupVersion() *metav1.GroupVersionForDiscovery { } return nil } + +type snapshotDataSourceFG interface { + validate(ctx context.Context) (bool, error) +} + +type snapshotDataSourceFGValidator struct { + cli kubernetes.Interface + dynCli dynamic.Interface +} + +func (s *snapshotDataSourceFGValidator) validate(ctx context.Context) (bool, error) { + ns := getPodNamespace() + + // deletes if exists. If it doesn't exist, this is a noop + err := kanvolume.DeletePVC(s.cli, ns, FeatureGateTestPVCName) + if err != nil { + return false, errors.Wrap(err, "Error deleting VolumeSnapshotDataSource feature-gate validation pvc") + } + // defer delete + defer func() { + _ = kanvolume.DeletePVC(s.cli, ns, FeatureGateTestPVCName) + }() + + // create PVC + snapshotKind := "VolumeSnapshot" + snapshotAPIGroup := "snapshot.storage.k8s.io" + pvc := &v1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Name: FeatureGateTestPVCName, + }, + Spec: v1.PersistentVolumeClaimSpec{ + AccessModes: []v1.PersistentVolumeAccessMode{v1.ReadWriteOnce}, + DataSource: &v1.TypedLocalObjectReference{ + APIGroup: &snapshotAPIGroup, + Kind: snapshotKind, + Name: "fakeSnap", + }, + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + v1.ResourceStorage: resource.MustParse("1Gi"), + }, + }, + }, + } + + pvcRes, err := s.cli.CoreV1().PersistentVolumeClaims(ns).Create(ctx, pvc, metav1.CreateOptions{}) + if err != nil { + return false, errors.Wrap(err, "Error creating VolumeSnapshotDataSource feature-gate validation pvc") + } + if pvcRes.Spec.DataSource == nil { + return false, nil + } + return true, nil +} diff --git a/pkg/kubestr/storage_provisioners_test.go b/pkg/kubestr/storage_provisioners_test.go new file mode 100644 index 0000000..694eac4 --- /dev/null +++ b/pkg/kubestr/storage_provisioners_test.go @@ -0,0 +1,327 @@ +package kubestr + +import ( + "context" + "fmt" + + "github.com/kanisterio/kanister/pkg/kube/snapshot/apis/v1alpha1" + . "gopkg.in/check.v1" + scv1 "k8s.io/api/storage/v1" + "k8s.io/api/storage/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + version "k8s.io/apimachinery/pkg/version" + discoveryfake "k8s.io/client-go/discovery/fake" + fakedynamic "k8s.io/client-go/dynamic/fake" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/fake" +) + +type ProvisionerTestSuite struct{} + +var _ = Suite(&ProvisionerTestSuite{}) + +func (s *ProvisionerTestSuite) TestHasCSIDriverObject(c *C) { + ctx := context.Background() + for _, tc := range []struct { + cli kubernetes.Interface + provisionerName string + hasDriver bool + }{ + { + cli: fake.NewSimpleClientset(), + provisionerName: "provisioner", + hasDriver: false, + }, + { + cli: fake.NewSimpleClientset(&v1beta1.CSIDriverList{ + Items: []v1beta1.CSIDriver{ + { + ObjectMeta: metav1.ObjectMeta{ + Name: "drivername", + }, + }, + }}), + provisionerName: "drivername", + hasDriver: true, + }, + } { + p := &Kubestr{cli: tc.cli} + hasDriver := p.hasCSIDriverObject(ctx, tc.provisionerName) + c.Assert(hasDriver, Equals, tc.hasDriver) + } +} + +func (s *ProvisionerTestSuite) TestIsK8sVersionCSISnapshotCapable(c *C) { + ctx := context.Background() + for _, tc := range []struct { + ver *version.Info + checker Checker + capable bool + sdsfg snapshotDataSourceFG + }{ + { + ver: &version.Info{Major: "1", Minor: "", GitVersion: "v1.17"}, + checker: NotNil, + capable: false, + }, + { + ver: &version.Info{Major: "1", Minor: "15+", GitVersion: "v1.15+"}, + checker: NotNil, + capable: false, + sdsfg: &fakeSDSFGValidator{err: fmt.Errorf("someerror"), cap: false}, + }, + { + ver: &version.Info{Major: "1", Minor: "15+", GitVersion: "v1.15+"}, + checker: IsNil, + capable: true, + sdsfg: &fakeSDSFGValidator{err: nil, cap: true}, + }, + { + ver: &version.Info{Major: "1", Minor: "17", GitVersion: "v1.17"}, + checker: IsNil, + capable: true, + }, + } { + cli := fake.NewSimpleClientset() + cli.Discovery().(*discoveryfake.FakeDiscovery).FakedServerVersion = tc.ver + p := &Kubestr{cli: cli, sdsfgValidator: tc.sdsfg} + cap, err := p.isK8sVersionCSISnapshotCapable(ctx) + c.Check(err, tc.checker) + c.Assert(cap, Equals, tc.capable) + } +} + +type fakeSDSFGValidator struct { + err error + cap bool +} + +func (f *fakeSDSFGValidator) validate(ctx context.Context) (bool, error) { + return f.cap, f.err +} + +func (s *ProvisionerTestSuite) TestValidateVolumeSnapshotClass(c *C) { + for _, tc := range []struct { + vsc unstructured.Unstructured + groupVersion string + out *VSCInfo + }{ + { + vsc: unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "vsc1", + }, + "snapshotter": "something", + }, + }, + groupVersion: "snapshot.storage.k8s.io/v1alpha1", + out: &VSCInfo{ + Name: "vsc1", + }, + }, + { // failure + vsc: unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "vsc1", + }, + "notsnapshotter": "something", + }, + }, + groupVersion: "snapshot.storage.k8s.io/v1alpha1", + out: &VSCInfo{ + Name: "vsc1", + StatusList: []Status{ + makeStatus(StatusError, fmt.Sprintf("VolumeSnapshotClass (%s) missing 'snapshotter' field", "vsc1"), nil), + }, + }, + }, + { + vsc: unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "vsc1", + }, + "driver": "something", + }, + }, + groupVersion: "snapshot.storage.k8s.io/v1beta1", + out: &VSCInfo{ + Name: "vsc1", + }, + }, + { // failure + vsc: unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": "vsc1", + }, + "notdriver": "something", + }, + }, + groupVersion: "snapshot.storage.k8s.io/v1beta1", + out: &VSCInfo{ + Name: "vsc1", + StatusList: []Status{ + makeStatus(StatusError, fmt.Sprintf("VolumeSnapshotClass (%s) missing 'driver' field", "vsc1"), nil), + }, + }, + }, + } { + p := &Kubestr{} + out := p.validateVolumeSnapshotClass(tc.vsc, tc.groupVersion) + c.Assert(out.Name, Equals, tc.out.Name) + c.Assert(len(out.StatusList), Equals, len(tc.out.StatusList)) + } +} + +func (s *ProvisionerTestSuite) TestLoadStorageClassesAndProvisioners(c *C) { + ctx := context.Background() + p := &Kubestr{cli: fake.NewSimpleClientset( + &scv1.StorageClass{ObjectMeta: metav1.ObjectMeta{Name: "sc1"}, Provisioner: "provisioner1"}, + &scv1.StorageClass{ObjectMeta: metav1.ObjectMeta{Name: "sc2"}, Provisioner: "provisioner2"}, + )} + scs, err := p.loadStorageClasses(ctx) + c.Assert(err, IsNil) + c.Assert(len(scs.Items), Equals, 2) + c.Assert(scs, Equals, p.storageClassList) + + // reload has the same + p.cli = fake.NewSimpleClientset() + scs, err = p.loadStorageClasses(ctx) + c.Assert(err, IsNil) + c.Assert(len(scs.Items), Equals, 2) + c.Assert(scs, Equals, p.storageClassList) + + // proviosners uses loaded list + provisioners, err := p.provisionerList(ctx) + c.Assert(err, IsNil) + c.Assert(len(provisioners), Equals, 2) +} + +func (s *ProvisionerTestSuite) TestLoadVolumeSnaphsotClasses(c *C) { + ctx := context.Background() + p := &Kubestr{dynCli: fakedynamic.NewSimpleDynamicClient(runtime.NewScheme(), &unstructured.Unstructured{ + Object: map[string]interface{}{ + "apiVersion": fmt.Sprintf("%s/%s", v1alpha1.GroupName, v1alpha1.Version), + "kind": "VolumeSnapshotClass", + "metadata": map[string]interface{}{ + "name": "theVSC", + }, + "snapshotter": "somesnapshotter", + "deletionPolicy": "Delete", + }, + })} + vsc, err := p.loadVolumeSnapshotClasses(ctx, v1alpha1.Version) + c.Assert(err, IsNil) + c.Assert(len(vsc.Items), Equals, 1) + c.Assert(vsc, Equals, p.volumeSnapshotClassList) + + // reload has the same + p.dynCli = fakedynamic.NewSimpleDynamicClient(runtime.NewScheme()) + vsc, err = p.loadVolumeSnapshotClasses(ctx, v1alpha1.Version) + c.Assert(err, IsNil) + c.Assert(len(vsc.Items), Equals, 1) + c.Assert(vsc, Equals, p.volumeSnapshotClassList) +} + +func (s *ProvisionerTestSuite) TestGetCSIGroupVersion(c *C) { + for _, tc := range []struct { + resources []*metav1.APIResourceList + out *metav1.GroupVersionForDiscovery + }{ + { + resources: []*metav1.APIResourceList{ + { + GroupVersion: "/////", + }, + }, + out: nil, + }, + { + resources: []*metav1.APIResourceList{ + { + GroupVersion: "snapshot.storage.k8s.io/v1beta1", + }, + { + GroupVersion: "snapshot.storage.k8s.io/v1apha1", + }, + }, + out: &metav1.GroupVersionForDiscovery{ + GroupVersion: "snapshot.storage.k8s.io/v1beta1", + Version: "v1beta1", + }, + }, + { + resources: []*metav1.APIResourceList{ + { + GroupVersion: "NOTsnapshot.storage.k8s.io/v1beta1", + }, + }, + out: nil, + }, + } { + cli := fake.NewSimpleClientset() + cli.Discovery().(*discoveryfake.FakeDiscovery).Resources = tc.resources + p := &Kubestr{cli: cli} + out := p.getCSIGroupVersion() + c.Assert(out, DeepEquals, tc.out) + } +} + +func (s *ProvisionerTestSuite) TestGetDriverNameFromUVSC(c *C) { + for _, tc := range []struct { + vsc unstructured.Unstructured + version string + out string + }{ + { // alpha success + vsc: unstructured.Unstructured{ + Object: map[string]interface{}{ + "snapshotter": "drivername", + }, + }, + version: "snapshot.storage.k8s.io/v1alpha1", + out: "drivername", + }, + { // key missing + vsc: unstructured.Unstructured{ + Object: map[string]interface{}{}, + }, + version: "snapshot.storage.k8s.io/v1alpha1", + out: "", + }, + { // beta success + vsc: unstructured.Unstructured{ + Object: map[string]interface{}{ + "driver": "drivername", + }, + }, + version: "snapshot.storage.k8s.io/v1beta1", + out: "drivername", + }, + { // key missing + vsc: unstructured.Unstructured{ + Object: map[string]interface{}{}, + }, + version: "snapshot.storage.k8s.io/v1beta1", + out: "", + }, + { // type conversion + vsc: unstructured.Unstructured{ + Object: map[string]interface{}{ + "driver": int64(1), + }, + }, + version: "snapshot.storage.k8s.io/v1beta1", + out: "", + }, + } { + p := &Kubestr{} + out := p.getDriverNameFromUVSC(tc.vsc, tc.version) + c.Assert(out, Equals, tc.out) + } +}