From 85f12f35a4640ae47a906a0644503cf0ddabad6c Mon Sep 17 00:00:00 2001 From: Lintong Jiang Date: Wed, 9 Dec 2020 23:40:56 -0800 Subject: [PATCH] Updated the version check of vSphere CSI to adapt both release and dev manifests Signed-off-by: Lintong Jiang --- pkg/cmd/backupdriver/cli/install/install.go | 15 +- pkg/cmd/datamgr/cli/install/install.go | 8 +- pkg/cmd/utils.go | 80 ++++--- pkg/cmd/utils_test.go | 224 ++++++++++++++++++++ pkg/constants/constants.go | 16 +- 5 files changed, 283 insertions(+), 60 deletions(-) diff --git a/pkg/cmd/backupdriver/cli/install/install.go b/pkg/cmd/backupdriver/cli/install/install.go index 35ef0ce7..079876c2 100644 --- a/pkg/cmd/backupdriver/cli/install/install.go +++ b/pkg/cmd/backupdriver/cli/install/install.go @@ -121,9 +121,6 @@ func (o *InstallOptions) Run(c *cobra.Command, f client.Factory) error { // Start with a few of prerequisite checks before installing backup-driver fmt.Println("The prerequisite checks for backup-driver started") - // Check vSphere CSI driver version - _ = cmd.CheckVSphereCSIDriverVersion(kubeClient) - // Check velero version _ = cmd.CheckVeleroVersion(kubeClient, o.Namespace) @@ -131,10 +128,14 @@ func (o *InstallOptions) Run(c *cobra.Command, f client.Factory) error { o.Image, _ = cmd.CheckPluginImageRepo(kubeClient, o.Namespace, o.Image, constants.BackupDriverForPlugin) // Check cluster flavor for backup-driver - if err := o.CheckClusterFlavorForBackupDriver(); err != nil { + clusterFlavor, err := o.CheckClusterFlavorForBackupDriver() + if err != nil { return err } + // Check vSphere CSI driver version + _ = cmd.CheckVSphereCSIDriverVersion(kubeClient, clusterFlavor) + // Check feature flags for backup-driver if err := o.CheckFeatureFlagsForBackupDriver(kubeClient); err != nil { return err @@ -226,10 +227,10 @@ func (o *InstallOptions) Complete(args []string, f client.Factory) error { return nil } -func (o *InstallOptions) CheckClusterFlavorForBackupDriver() error { +func (o *InstallOptions) CheckClusterFlavorForBackupDriver() (constants.ClusterFlavor, error) { clusterFlavor, err := utils.GetClusterFlavor(nil) if err != nil { - return errors.Wrap(err, "Failed to get cluster flavor for backup-driver") + return constants.Unknown, errors.Wrap(err, "Failed to get cluster flavor for backup-driver") } // Assign master node affinity and host network to Supervisor deployment @@ -239,7 +240,7 @@ func (o *InstallOptions) CheckClusterFlavorForBackupDriver() error { o.HostNetwork = true } - return nil + return clusterFlavor, nil } func (o *InstallOptions) CheckFeatureFlagsForBackupDriver(kubeClient kubernetes.Interface) error { diff --git a/pkg/cmd/datamgr/cli/install/install.go b/pkg/cmd/datamgr/cli/install/install.go index 0fda7f50..0429c9b0 100644 --- a/pkg/cmd/datamgr/cli/install/install.go +++ b/pkg/cmd/datamgr/cli/install/install.go @@ -148,7 +148,7 @@ func (o *InstallOptions) Run(c *cobra.Command, f client.Factory) error { fmt.Println("The prerequisite checks for data-manager started") // Check cluster flavor for data-manager - o.CheckClusterFlavorForDataManager() + clusterFlavor := o.CheckClusterFlavorForDataManager() // Check feature flags for data-manager _ = o.CheckFeatureFlagsForDataManager(kubeClient) @@ -160,7 +160,7 @@ func (o *InstallOptions) Run(c *cobra.Command, f client.Factory) error { } // Check vSphere CSI driver version - _ = cmd.CheckVSphereCSIDriverVersion(kubeClient) + _ = cmd.CheckVSphereCSIDriverVersion(kubeClient, clusterFlavor) // Check velero version _ = cmd.CheckVeleroVersion(kubeClient, o.Namespace) @@ -271,7 +271,7 @@ func (o *InstallOptions) getNumberOfNodes(kubeClient kubernetes.Interface) (int, return len(nodeList.Items), nil } -func (o *InstallOptions) CheckClusterFlavorForDataManager() { +func (o *InstallOptions) CheckClusterFlavorForDataManager() constants.ClusterFlavor { clusterFlavor, _ := utils.GetClusterFlavor(nil) // In case of Guest or Supervisor cluster, skip installing data manager @@ -279,6 +279,8 @@ func (o *InstallOptions) CheckClusterFlavorForDataManager() { fmt.Printf("The Cluster Flavor: %s. Skipping data manager installation.\n", clusterFlavor) o.SkipInstall = true } + + return clusterFlavor } func (o *InstallOptions) CheckFeatureFlagsForDataManager(kubeClient kubernetes.Interface) error { diff --git a/pkg/cmd/utils.go b/pkg/cmd/utils.go index 1dd5e76b..220e5b88 100644 --- a/pkg/cmd/utils.go +++ b/pkg/cmd/utils.go @@ -58,7 +58,7 @@ func GetVersionFromImage(containers []v1.Container, imageName string) string { var tag = "" for _, container := range containers { if strings.Contains(container.Image, imageName) { - tag = strings.Split(container.Image, ":")[1] + tag = utils.GetComponentFromImage(container.Image, constants.ImageVersionComponent) break } } @@ -195,55 +195,56 @@ func CompareVersion(currentVersion string, minVersion string) int { current, _ := version.NewVersion(currentVersion) minimum, _ := version.NewVersion(minVersion) + if current == nil || minimum == nil { + return -1 + } return current.Compare(minimum) } -func CheckCSIVersion(containers []v1.Container) (bool, bool, error) { - isVersionOK := false +func CheckCSIVersion(containers []v1.Container) error { csi_driver_version := GetVersionFromImage(containers, "cloud-provider-vsphere/csi/release/driver") if csi_driver_version == "" { - csi_driver_version = GetVersionFromImage(containers, "cloudnativestorage/vsphere-csi") + csi_driver_version = GetVersionFromImage(containers, "cloud-provider-vsphere/csi/ci/driver") if csi_driver_version != "" { - fmt.Printf("Got pre-relase version %s from container cloudnativestorage/vsphere-csi, setting version to min version %s\n", - csi_driver_version, constants.CsiMinVersion) + fmt.Printf("Got a prerelease version %s from container cloud-provider-vsphere/csi/ci/driver. Ignored it\n", + csi_driver_version) csi_driver_version = constants.CsiMinVersion } } + csi_syncer_version := GetVersionFromImage(containers, "cloud-provider-vsphere/csi/release/syncer") if csi_syncer_version == "" { - csi_syncer_version = GetVersionFromImage(containers, "cloudnativestorage/syncer") + csi_syncer_version = GetVersionFromImage(containers, "cloud-provider-vsphere/csi/ci/syncer") if csi_syncer_version != "" { - fmt.Printf("Got pre-relase version %s from container cloudnativestorage/syncer, setting version to min version %s\n", - csi_syncer_version, constants.CsiMinVersion) + fmt.Printf("Got a prerelease version %s from container cloud-provider-vsphere/csi/ci/syncer. Ignored it\n", + csi_syncer_version) csi_syncer_version = constants.CsiMinVersion } } - if CompareVersion(csi_driver_version, constants.CsiMinVersion) >= 0 && CompareVersion(csi_syncer_version, constants.CsiMinVersion) >= 0 { - isVersionOK = true - } - return true, isVersionOK, nil -} -func CheckCSIInstalled(kubeClient kubernetes.Interface) (bool, bool, error) { - statefulsetList, err := kubeClient.AppsV1().StatefulSets("kube-system").List(context.TODO(), metav1.ListOptions{}) - if err != nil { - return false, false, err + if csi_driver_version == "" || csi_syncer_version == "" { + return errors.New("Expected CSI driver/syncer images not found") } - for _, item := range statefulsetList.Items { - if item.GetName() == "vsphere-csi-controller" { - return CheckCSIVersion(item.Spec.Template.Spec.Containers) - } + + if CompareVersion(csi_driver_version, constants.CsiMinVersion) < 0 || CompareVersion(csi_syncer_version, constants.CsiMinVersion) < 0 { + return errors.Errorf("The version of vSphere CSI controller is below the minimum requirement (%s)", constants.CsiMinVersion) } - deploymentList, err := kubeClient.AppsV1().Deployments("kube-system").List(context.TODO(), metav1.ListOptions{}) - if err != nil { - return false, false, err + + return nil +} + +func CheckCSIInstalled(kubeClient kubernetes.Interface) error { + csiStatefulset, err := kubeClient.AppsV1().StatefulSets(constants.KubeSystemNamespace).Get(context.TODO(), constants.VSphereCSIController, metav1.GetOptions{}) + if err == nil { + return CheckCSIVersion(csiStatefulset.Spec.Template.Spec.Containers) } - for _, item := range deploymentList.Items { - if item.Name == "vsphere-csi-controller" { - return CheckCSIVersion(item.Spec.Template.Spec.Containers) - } + + csiDeployment, err := kubeClient.AppsV1().Deployments(constants.KubeSystemNamespace).Get(context.TODO(), constants.VSphereCSIController, metav1.GetOptions{}) + if err == nil { + return CheckCSIVersion(csiDeployment.Spec.Template.Spec.Containers) } - return false, false, nil + + return errors.Errorf("vSphere CSI controller, %s, is required by velero-plugin-for-vsphere. Please make sure the vSphere CSI controller is installed in the cluster", constants.VSphereCSIController) } func BuildConfig(master, kubeConfig string, f client.Factory) (*rest.Config, error) { @@ -291,20 +292,15 @@ func GetCompatibleRepoAndTagFromPluginImage(kubeClient kubernetes.Interface, nam return resultImage, nil } -func CheckVSphereCSIDriverVersion(kubeClient kubernetes.Interface) error { - isCSIInstalled, isVersionOk, err := CheckCSIInstalled(kubeClient) - if err != nil { - fmt.Println("CSI driver check failed") - isCSIInstalled = false - isVersionOk = false +func CheckVSphereCSIDriverVersion(kubeClient kubernetes.Interface, clusterFlavor constants.ClusterFlavor) error { + if clusterFlavor != constants.VSphere { + fmt.Println("Skipped the version check of CSI driver if it is not in a Vanilla cluster") + return nil } - if !isCSIInstalled { - fmt.Println("Velero Plug-in for vSphere requires vSphere CSI/CNS and vSphere 6.7U3 to function. Please install the vSphere CSI/CNS driver") - } - - if !isVersionOk { - fmt.Printf("vSphere CSI driver version is prior to %s. Velero Plug-in for vSphere requires CSI driver version to be %s or above\n", constants.CsiMinVersion, constants.CsiMinVersion) + err := CheckCSIInstalled(kubeClient) + if err != nil { + fmt.Printf("Failed the version check of CSI driver. Error: %v\n", err) } return err diff --git a/pkg/cmd/utils_test.go b/pkg/cmd/utils_test.go index 08847ac2..dbd506a1 100644 --- a/pkg/cmd/utils_test.go +++ b/pkg/cmd/utils_test.go @@ -25,6 +25,7 @@ import ( 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" kubeclientfake "k8s.io/client-go/kubernetes/fake" "strconv" "testing" @@ -582,3 +583,226 @@ func TestCheckPluginImageRepo(t *testing.T) { }) } } + +func TestCheckVSphereCSIDriverVersion(t *testing.T) { + tests := []struct { + name string + runtimeObjs []runtime.Object + clusterFlavor constants.ClusterFlavor + expectedError error + }{ + { + name: "Positive Case in CSI v2.0.1 Vanilla", + runtimeObjs: []runtime.Object{ + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "kube-system", + Name: "vsphere-csi-controller", + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "vsphere-csi-controller", + Image: "gcr.io/cloud-provider-vsphere/csi/release/driver:v2.0.1", + }, + { + Name: "vsphere-syncer", + Image: "gcr.io/cloud-provider-vsphere/csi/release/syncer:v2.0.1", + }, + }, + }, + }, + }, + }, + }, + clusterFlavor: constants.VSphere, + expectedError: nil, + }, + { + name: "Positive Case in CSI v2.0.1 Vanilla with Customized Registry Endpoint", + runtimeObjs: []runtime.Object{ + &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "kube-system", + Name: "vsphere-csi-controller", + }, + Spec: appsv1.StatefulSetSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "vsphere-csi-controller", + Image: "xyz.io:9999/cloud-provider-vsphere/csi/release/driver:v2.0.1", + }, + { + Name: "vsphere-syncer", + Image: "xyz.io:9999/cloud-provider-vsphere/csi/release/syncer:v2.0.1", + }, + }, + }, + }, + }, + }, + }, + clusterFlavor: constants.VSphere, + expectedError: nil, + }, + { + name: "Positive Case in CSI v2.1.0 dev Vanilla", + runtimeObjs: []runtime.Object{ + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "kube-system", + Name: "vsphere-csi-controller", + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "vsphere-csi-controller", + Image: "gcr.io/cloud-provider-vsphere/csi/ci/driver:latest", + }, + { + Name: "vsphere-syncer", + Image: "gcr.io/cloud-provider-vsphere/csi/ci/syncer:latest", + }, + }, + }, + }, + }, + }, + }, + clusterFlavor: constants.VSphere, + expectedError: nil, + }, + { + name: "Negative Case in CSI v2.0.1 Vanilla with Unexpected Images", + runtimeObjs: []runtime.Object{ + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "kube-system", + Name: "vsphere-csi-controller", + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "vsphere-csi-controller", + Image: "gcr.io/cloud-provider-vsphere/csi/xyz/driver:v2.0.1", + }, + { + Name: "vsphere-syncer", + Image: "gcr.io/cloud-provider-vsphere/csi/ci/syncer:v2.0.1", + }, + }, + }, + }, + }, + }, + }, + clusterFlavor: constants.VSphere, + expectedError: errors.New("Expected CSI driver/syncer images not found"), + }, + { + name: "Negative Case in CSI v2.0.1 Vanilla with Unexpected Deployment Name", + runtimeObjs: []runtime.Object{ + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "kube-system", + Name: "vsphere-csi-driver", + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "vsphere-csi-controller", + Image: "gcr.io/cloud-provider-vsphere/csi/release/driver:v2.0.1", + }, + { + Name: "vsphere-syncer", + Image: "gcr.io/cloud-provider-vsphere/csi/release/syncer:v2.0.1", + }, + }, + }, + }, + }, + }, + }, + clusterFlavor: constants.VSphere, + expectedError: errors.Errorf("vSphere CSI controller, %s, is required by velero-plugin-for-vsphere. Please make sure the vSphere CSI controller is installed in the cluster", constants.VSphereCSIController), + }, + { + name: "Negative Case in CSI v1.0.1 Vanilla with Unexpected Deployment Name", + runtimeObjs: []runtime.Object{ + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "kube-system", + Name: "vsphere-csi-controller", + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "vsphere-csi-controller", + Image: "gcr.io/cloud-provider-vsphere/csi/release/driver:v1.0.1", + }, + { + Name: "vsphere-syncer", + Image: "gcr.io/cloud-provider-vsphere/csi/release/syncer:v1.0.1", + }, + }, + }, + }, + }, + }, + }, + clusterFlavor: constants.VSphere, + expectedError: errors.Errorf("The version of vSphere CSI controller is below the minimum requirement (%s)", constants.CsiMinVersion), + }, + { + name: "Positive case in CSI driver Guest", + runtimeObjs: []runtime.Object{ + &appsv1.Deployment{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: "vmware-system-csi", + Name: "vsphere-csi-controller", + }, + Spec: appsv1.DeploymentSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "vsphere-csi-controller", + Image: "abc.io/vsphere-csi:v0.0.1.alpha_abc.79-7ecdcb2", + }, + { + Name: "vsphere-syncer", + Image: "abc.io/syncer:v0.0.1.alpha_abc.79-7ecdcb2", + }, + }, + }, + }, + }, + }, + }, + clusterFlavor: constants.TkgGuest, + expectedError: nil, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + kubeClient := kubeclientfake.NewSimpleClientset(test.runtimeObjs...) + actualError := CheckVSphereCSIDriverVersion(kubeClient, test.clusterFlavor) + assert.Equal(t, test.expectedError == nil, actualError == nil) + if actualError != nil { + assert.Equal(t, test.expectedError.Error(), actualError.Error()) + } + }) + } +} diff --git a/pkg/constants/constants.go b/pkg/constants/constants.go index b361cb13..dd60e8b2 100644 --- a/pkg/constants/constants.go +++ b/pkg/constants/constants.go @@ -99,13 +99,13 @@ const ( ) const ( - DataManagerForPlugin string = "data-manager-for-plugin" - BackupDriverForPlugin string = "backup-driver" - BackupDriverNamespace string = "velero-vsphere-plugin-backupdriver" - + DataManagerForPlugin string = "data-manager-for-plugin" + BackupDriverForPlugin string = "backup-driver" + BackupDriverNamespace string = "velero-vsphere-plugin-backupdriver" VeleroPluginForVsphere string = "velero-plugin-for-vsphere" - - VeleroDeployment string = "velero" + VeleroDeployment string = "velero" + VSphereCSIController = "vsphere-csi-controller" + KubeSystemNamespace = "kube-system" ) const ( @@ -309,8 +309,8 @@ const ( const ( ImageRepositoryComponent = "Repository" - ImageContainerComponent = "Container" - ImageVersionComponent = "Version" + ImageContainerComponent = "Container" + ImageVersionComponent = "Version" ) const (