From 57bbb0c0d1dd2fdb1c5a5e5e2003a8ab723cb660 Mon Sep 17 00:00:00 2001 From: Aly Nathoo <98403872+anathoodell@users.noreply.github.com> Date: Thu, 19 Dec 2024 18:47:42 -0500 Subject: [PATCH] Powerflex secret zone validation (#833) * ZoneValidation() to PowerFlex PreChecks that calls into drivers.ExtractZones. Fixed up existing unit-tests to pass around a proper secret, rather than the "csm" value currently used, as this causes the ExtractZones() to fail on parsing during unit-testing. * Removing node label code since we do not need to validate against node labels anymore. Removing the directly associated unit test code as well. Fixing powerflex validate zone code to check that all arrays have zone detail if there is at least one array with zone detail and to handle corner cases in zone struct where labelValue or Name are either not specified or are empty values. Cleaned up test code and added test cases for these scenarios. --- controllers/csm_controller.go | 17 ++- controllers/csm_controller_test.go | 66 ++++----- go.mod | 8 +- go.sum | 16 +-- pkg/drivers/powerflex.go | 72 +++++++--- pkg/drivers/powerflex_test.go | 210 ++++++++++++++++++++++++++--- tests/e2e/go.mod | 2 +- tests/e2e/go.sum | 22 +-- tests/shared/common.go | 80 +++++++++++ tests/shared/crclient/client.go | 31 ++++- 10 files changed, 406 insertions(+), 118 deletions(-) diff --git a/controllers/csm_controller.go b/controllers/csm_controller.go index 3d5d6e54..02e68cca 100644 --- a/controllers/csm_controller.go +++ b/controllers/csm_controller.go @@ -1334,6 +1334,11 @@ func (r *ContainerStorageModuleReconciler) PreChecks(ctx context.Context, cr *cs if err != nil { return fmt.Errorf("failed powerflex validation: %v", err) } + // zoning initially applies only to pflex + err = r.ZoneValidation(ctx, cr) + if err != nil { + return fmt.Errorf("error during zone validation: %v", err) + } case csmv1.PowerStore: err := drivers.PrecheckPowerStore(ctx, cr, operatorConfig, r.GetClient()) if err != nil { @@ -1524,12 +1529,12 @@ func (r *ContainerStorageModuleReconciler) GetK8sClient() kubernetes.Interface { return r.K8sClient } -func (r *ContainerStorageModuleReconciler) GetMatchingNodes(ctx context.Context, labelKey string, labelValue string) (*corev1.NodeList, error) { - nodeList := &corev1.NodeList{} - opts := []client.ListOption{ - client.MatchingLabels{labelKey: labelValue}, +// ZoneValidation - If zones are configured performs validation and returns an error if the zone validation fails +func (r *ContainerStorageModuleReconciler) ZoneValidation(ctx context.Context, cr *csmv1.ContainerStorageModule) error { + err := drivers.ValidateZones(ctx, cr, r.Client) + if err != nil { + return fmt.Errorf("zone validation failed with error: %v", err) } - err := r.List(ctx, nodeList, opts...) - return nodeList, err + return err } diff --git a/controllers/csm_controller_test.go b/controllers/csm_controller_test.go index 618500df..ca1ceda0 100644 --- a/controllers/csm_controller_test.go +++ b/controllers/csm_controller_test.go @@ -585,7 +585,7 @@ func (suite *CSMControllerTestSuite) TestCsmDowngrade() { if err != nil { panic(err) } - sec := shared.MakeSecret(csmName+"-config", suite.namespace, pFlexConfigVersion) + sec := shared.MakeSecretPowerFlex(csmName+"-config", suite.namespace, pFlexConfigVersion) err = suite.fakeClient.Create(ctx, sec) if err != nil { panic(err) @@ -653,7 +653,7 @@ func (suite *CSMControllerTestSuite) TestCsmDowngradeSkipVersion() { if err != nil { panic(err) } - sec := shared.MakeSecret(csmName+"-config", suite.namespace, pFlexConfigVersion) + sec := shared.MakeSecretPowerFlex(csmName+"-config", suite.namespace, pFlexConfigVersion) err = suite.fakeClient.Create(ctx, sec) if err != nil { panic(err) @@ -2383,56 +2383,44 @@ func (suite *CSMControllerTestSuite) makeFakeRevProxyCSM(name string, ns string, assert.Nil(suite.T(), err) } -func (suite *CSMControllerTestSuite) TestGetNodeLabels() { - // TBD: crclient/client.go needs to be augmented to filter on labels during - // the List return for a viable thorough test. Since this functionality is - // missing, this test is quite elementary as a result. - +func (suite *CSMControllerTestSuite) TestZoneValidation() { csm := shared.MakeCSM(csmName, suite.namespace, configVersion) - csm.Spec.Driver.CSIDriverType = csmv1.PowerScale + csm.Spec.Driver.CSIDriverType = csmv1.PowerFlex csm.Spec.Driver.Common.Image = "image" csm.Annotations[configVersionKey] = configVersion - sec := shared.MakeSecret(csmName+"-creds", suite.namespace, configVersion) - err := suite.fakeClient.Create(ctx, sec) - if err != nil { - panic(err) - } - csm.ObjectMeta.Finalizers = []string{CSMFinalizerName} - err = suite.fakeClient.Create(ctx, &csm) - if err != nil { - panic(err) - } + err := suite.fakeClient.Create(ctx, &csm) + assert.Nil(suite.T(), err) reconciler := suite.createReconciler() - // create node object, add to fakeclient, reconcile.GetMatchingNodes - node := shared.MakeNode("node1", suite.namespace) - node.Labels["topology.kubernetes.io/zone"] = "US-EAST" - - err = suite.fakeClient.Create(ctx, &node) + // add secret with NO zone to the namespace + sec := shared.MakeSecretPowerFlex(csmName+"-config", suite.namespace, pFlexConfigVersion) + err = suite.fakeClient.Create(ctx, sec) assert.Nil(suite.T(), err) - nodeList := &corev1.NodeList{} - err = suite.fakeClient.List(ctx, nodeList, nil) + err = reconciler.ZoneValidation(ctx, &csm) assert.Nil(suite.T(), err) +} - nodeListMatching, err := reconciler.GetMatchingNodes(ctx, "topology.kubernetes.io/zone", "US-EAST") - ctrl.Log.Info("node list response (1)", "number of nodes is: ", len(nodeListMatching.Items)) +func (suite *CSMControllerTestSuite) TestZoneValidation2() { + csm := shared.MakeCSM(csmName, suite.namespace, configVersion) + csm.Spec.Driver.CSIDriverType = csmv1.PowerFlex + csm.Spec.Driver.Common.Image = "image" + csm.Annotations[configVersionKey] = configVersion - // Check the len to be 1 else fail - if len(nodeListMatching.Items) != 1 { - ctrl.Log.Error(err, "Unexpected length on node list.", "length", len(nodeListMatching.Items)) - panic(err) - } + csm.ObjectMeta.Finalizers = []string{CSMFinalizerName} + err := suite.fakeClient.Create(ctx, &csm) + assert.Nil(suite.T(), err) - for _, node := range nodeListMatching.Items { - ctrl.Log.Info("Matching node item", "name ", node.ObjectMeta.GetName()) - } - if node.ObjectMeta.GetName() != "node1" { - ctrl.Log.Error(err, "Unmatched label on node.") - panic(err) - } + reconciler := suite.createReconciler() + + // add secret with an invalid multi zone to the namespace + secretZone := shared.MakeSecretPowerFlexMultiZoneInvalid(csmName+"-config", suite.namespace, pFlexConfigVersion) + err = suite.fakeClient.Create(ctx, secretZone) assert.Nil(suite.T(), err) + + err = reconciler.ZoneValidation(ctx, &csm) + assert.NotNil(suite.T(), err) } diff --git a/go.mod b/go.mod index 22887007..6ec4b3a0 100644 --- a/go.mod +++ b/go.mod @@ -55,11 +55,11 @@ require ( github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect - golang.org/x/net v0.31.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/term v0.26.0 // indirect - golang.org/x/text v0.20.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/term v0.27.0 // indirect + golang.org/x/text v0.21.0 // indirect golang.org/x/time v0.5.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/protobuf v1.35.1 // indirect diff --git a/go.sum b/go.sum index 15dfc3f1..b9d36c8e 100644 --- a/go.sum +++ b/go.sum @@ -120,8 +120,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -130,14 +130,14 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= -golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= +golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/pkg/drivers/powerflex.go b/pkg/drivers/powerflex.go index 883ce678..419459d6 100644 --- a/pkg/drivers/powerflex.go +++ b/pkg/drivers/powerflex.go @@ -16,6 +16,7 @@ import ( "context" "fmt" "os" + "reflect" "regexp" "strings" @@ -347,52 +348,87 @@ func ModifyPowerflexCR(yamlString string, cr csmv1.ContainerStorageModule, fileT return yamlString } -// ExtractZonesFromSecret - Reads the array config secret and returns the zone label information -func ExtractZonesFromSecret(ctx context.Context, kube client.Client, namespace string, secret string) (map[string]string, error) { +// ValidateZones - zone validation for topology aware clusters +func ValidateZones(ctx context.Context, cr *csmv1.ContainerStorageModule, ct client.Client) error { + secretName := cr.Name + "-config" + err := ValidateZonesInSecret(ctx, ct, cr.Namespace, secretName) + return err +} + +// ValidateZonesInSecret - inspects incoming secret for zone validity +func ValidateZonesInSecret(ctx context.Context, kube client.Client, namespace string, secret string) error { log := logger.GetLogger(ctx) arraySecret, err := utils.GetSecret(ctx, secret, namespace, kube) if err != nil { - return nil, fmt.Errorf("reading secret [%s] error [%s]", secret, err) + return fmt.Errorf("reading secret [%s] error %v", secret, err) + } + + type Zone struct { + Name string `json:"name,omitempty"` + LabelKey string `json:"labelKey,omitempty"` } type StorageArrayConfig struct { - SystemID string `json:"systemId"` - Zone struct { - Name string `json:"name"` - } `json:"zone"` + SystemID string `json:"systemID"` + Zone Zone `json:"zone,omitempty"` } data := arraySecret.Data configBytes := data["config"] - zonesMapData := make(map[string]string) if string(configBytes) != "" { yamlConfig := make([]StorageArrayConfig, 0) configs, err := yaml.JSONToYAML(configBytes) if err != nil { - return nil, fmt.Errorf("unable to parse multi-array configuration[%v]", err) + return fmt.Errorf("malformed json in array secret - unable to parse multi-array configuration %v", err) } err = yaml.Unmarshal(configs, &yamlConfig) if err != nil { - return nil, fmt.Errorf("unable to unmarshal multi-array configuration[%v]", err) + return fmt.Errorf("unable to unmarshal array secret %v", err) } + var labelKey string + var numArrays, numArraysWithZone int + numArrays = len(yamlConfig) for _, configParam := range yamlConfig { if configParam.SystemID == "" { - return nil, fmt.Errorf("invalid value for SystemID") + return fmt.Errorf("invalid value for SystemID") } - if configParam.Zone.Name != "" { - zonesMapData[configParam.SystemID] = configParam.Zone.Name - log.Infof("Zoning information configured for systemID %s: %v ", configParam.SystemID, zonesMapData) + if reflect.DeepEqual(configParam.Zone, Zone{}) { + log.Infof("Zone is not specified for SystemID: %s", configParam.SystemID) } else { - log.Info("Zoning information not found in the array config. Continue with topology-unaware driver installation mode") - return zonesMapData, nil + log.Infof("Zone is specified for SystemID: %s", configParam.SystemID) + if configParam.Zone.LabelKey == "" { + return fmt.Errorf("zone LabelKey is empty or not specified for SystemID: %s", + configParam.SystemID) + } + + if labelKey == "" { + labelKey = configParam.Zone.LabelKey + } else { + if labelKey != configParam.Zone.LabelKey { + return fmt.Errorf("labelKey is not consistent across all arrays in secret") + } + } + + if configParam.Zone.Name == "" { + return fmt.Errorf("zone name is empty or not specified for SystemID: %s", + configParam.SystemID) + } + numArraysWithZone++ } } + + log.Infof("found %d arrays zoning on %d", numArrays, numArraysWithZone) + if numArraysWithZone > 0 && numArrays != numArraysWithZone { + return fmt.Errorf("not all arrays have zoning configured. Check the array info secret, zone key should be the same for all arrays") + } else if numArraysWithZone == 0 { + log.Info("Zoning information not found in the array secret. Continue with topology-unaware driver installation mode") + } } else { - return nil, fmt.Errorf("Array details are not provided in vxflexos-config secret") + return fmt.Errorf("array details are not provided in secret") } - return zonesMapData, nil + return nil } diff --git a/pkg/drivers/powerflex_test.go b/pkg/drivers/powerflex_test.go index 03f0fade..6deb8ddd 100644 --- a/pkg/drivers/powerflex_test.go +++ b/pkg/drivers/powerflex_test.go @@ -240,6 +240,52 @@ func TestExtractZonesFromSecret(t *testing.T) { zone: name: "ZONE-2" labelKey: "zone.csi-vxflexos.dellemc.com" +` + zoneDataWithMultiArraySomeZone := ` +- username: "admin" + password: "password" + systemID: "2b11bb111111bb1b" + endpoint: "https://127.0.0.2" + skipCertificateValidation: true + mdm: "10.0.0.3,10.0.0.4" +- username: "admin" + password: "password" + systemID: "1a99aa999999aa9a" + endpoint: "https://127.0.0.1" + skipCertificateValidation: true + mdm: "10.0.0.5,10.0.0.6" + zone: + name: "ZONE-2" + labelKey: "zone.csi-vxflexos.dellemc.com" +` + zoneDataWithMultiArraySomeZone2 := ` +- username: "admin" + password: "password" + systemID: "2b11bb111111bb1b" + endpoint: "https://127.0.0.2" + skipCertificateValidation: true + mdm: "10.0.0.3,10.0.0.4" +- username: "admin" + password: "password" + systemID: "1a99aa999999aa9a" + endpoint: "https://127.0.0.1" + skipCertificateValidation: true + mdm: "10.0.0.5,10.0.0.6" +- username: "admin" + password: "password" + systemID: "1a99aa999999aa9a" + endpoint: "https://127.0.0.1" + skipCertificateValidation: true + mdm: "10.0.0.5,10.0.0.6" + zone: + name: "ZONE-2" + labelKey: "zone.csi-vxflexos.dellemc.com" +- username: "admin" + password: "password" + systemID: "1a99aa999999aa9a" + endpoint: "https://127.0.0.1" + skipCertificateValidation: true + mdm: "10.0.0.5,10.0.0.6" ` dataWithoutZone := ` - username: "admin" @@ -249,9 +295,69 @@ func TestExtractZonesFromSecret(t *testing.T) { skipCertificateValidation: true mdm: "10.0.0.3,10.0.0.4" ` + zoneDataWithMultiArrayPartialZone1 := ` +- username: "admin" + password: "password" + systemID: "2b11bb111111bb1b" + endpoint: "https://127.0.0.2" + skipCertificateValidation: true + mdm: "10.0.0.3,10.0.0.4" + zone: + name: "ZONE-1" + labelKey: "zone.csi-vxflexos.dellemc.com" +- username: "admin" + password: "password" + systemID: "1a99aa999999aa9a" + endpoint: "https://127.0.0.1" + skipCertificateValidation: true + mdm: "10.0.0.5,10.0.0.6" + zone: + name: "" + labelKey: "zone.csi-vxflexos.dellemc.com" +` + zoneDataWithMultiArrayPartialZone2 := ` +- username: "admin" + password: "password" + systemID: "2b11bb111111bb1b" + endpoint: "https://127.0.0.2" + skipCertificateValidation: true + mdm: "10.0.0.3,10.0.0.4" + zone: + name: "ZONE-1" + labelKey: "zone.csi-vxflexos.dellemc.com" +- username: "admin" + password: "password" + systemID: "1a99aa999999aa9a" + endpoint: "https://127.0.0.1" + skipCertificateValidation: true + mdm: "10.0.0.5,10.0.0.6" + zone: + name: "myname" +` + zoneDataWithMultiArrayPartialZone3 := ` +- username: "admin" + password: "password" + systemID: "2b11bb111111bb1b" + endpoint: "https://127.0.0.2" + skipCertificateValidation: true + mdm: "10.0.0.3,10.0.0.4" + zone: + name: "ZONE-1" + labelKey: "" +- username: "admin" + password: "password" + systemID: "1a99aa999999aa9a" + endpoint: "https://127.0.0.1" + skipCertificateValidation: true + mdm: "10.0.0.5,10.0.0.6" + zone: + name: "myname" + labelKey: "zone.csi-vxflexos.dellemc.com" +` + ctx := context.Background() - tests := map[string]func() (client.WithWatch, map[string]string, string, bool){ - "success with zone": func() (client.WithWatch, map[string]string, string, bool) { + tests := map[string]func() (client.WithWatch, string, bool){ + "success with zone": func() (client.WithWatch, string, bool) { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "vxflexos-config", @@ -263,9 +369,9 @@ func TestExtractZonesFromSecret(t *testing.T) { } client := fake.NewClientBuilder().WithObjects(secret).Build() - return client, map[string]string{"2b11bb111111bb1b": "US-EAST"}, "vxflexos-config", false + return client, "vxflexos-config", false }, - "success with zone and multi array": func() (client.WithWatch, map[string]string, string, bool) { + "success with zone and multi array": func() (client.WithWatch, string, bool) { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "vxflexos-config", @@ -277,9 +383,35 @@ func TestExtractZonesFromSecret(t *testing.T) { } client := fake.NewClientBuilder().WithObjects(secret).Build() - return client, map[string]string{"2b11bb111111bb1b": "ZONE-1", "1a99aa999999aa9a": "ZONE-2"}, "vxflexos-config", false + return client, "vxflexos-config", false }, - "success no zone": func() (client.WithWatch, map[string]string, string, bool) { + "fail multi array but only some zone": func() (client.WithWatch, string, bool) { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vxflexos-config", + Namespace: "vxflexos", + }, + Data: map[string][]byte{ + "config": []byte(zoneDataWithMultiArraySomeZone), + }, + } + client := fake.NewClientBuilder().WithObjects(secret).Build() + return client, "vxflexos-config", true + }, + "fail multi array but only some zone test two": func() (client.WithWatch, string, bool) { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vxflexos-config", + Namespace: "vxflexos", + }, + Data: map[string][]byte{ + "config": []byte(zoneDataWithMultiArraySomeZone2), + }, + } + client := fake.NewClientBuilder().WithObjects(secret).Build() + return client, "vxflexos-config", true + }, + "success no zone": func() (client.WithWatch, string, bool) { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "vxflexos-config", @@ -291,13 +423,13 @@ func TestExtractZonesFromSecret(t *testing.T) { } client := fake.NewClientBuilder().WithObjects(secret).Build() - return client, map[string]string{}, "vxflexos-config", false + return client, "vxflexos-config", false }, - "error getting secret": func() (client.WithWatch, map[string]string, string, bool) { + "error getting secret": func() (client.WithWatch, string, bool) { client := fake.NewClientBuilder().Build() - return client, nil, "vxflexos-not-found", true + return client, "vxflexos-not-found", true }, - "error parsing empty secret": func() (client.WithWatch, map[string]string, string, bool) { + "error parsing empty secret": func() (client.WithWatch, string, bool) { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "vxflexos-config", @@ -309,9 +441,9 @@ func TestExtractZonesFromSecret(t *testing.T) { } client := fake.NewClientBuilder().WithObjects(secret).Build() - return client, nil, "vxflexos-config", true + return client, "vxflexos-config", true }, - "error with no system id": func() (client.WithWatch, map[string]string, string, bool) { + "error with no system id": func() (client.WithWatch, string, bool) { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "vxflexos-config", @@ -323,9 +455,10 @@ func TestExtractZonesFromSecret(t *testing.T) { } client := fake.NewClientBuilder().WithObjects(secret).Build() - return client, nil, "vxflexos-config", true + return client, "vxflexos-config", true }, - "error unmarshaling config": func() (client.WithWatch, map[string]string, string, bool) { + + "error unmarshaling config": func() (client.WithWatch, string, bool) { secret := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ Name: "vxflexos-config", @@ -337,19 +470,60 @@ func TestExtractZonesFromSecret(t *testing.T) { } client := fake.NewClientBuilder().WithObjects(secret).Build() - return client, nil, "vxflexos-config", true + return client, "vxflexos-config", true + }, + "Fail Partial Zone Config 1": func() (client.WithWatch, string, bool) { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vxflexos-config", + Namespace: "vxflexos", + }, + Data: map[string][]byte{ + "config": []byte(zoneDataWithMultiArrayPartialZone1), + }, + } + + client := fake.NewClientBuilder().WithObjects(secret).Build() + return client, "vxflexos-config", true + }, + "Fail Partial Zone Config 2": func() (client.WithWatch, string, bool) { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vxflexos-config", + Namespace: "vxflexos", + }, + Data: map[string][]byte{ + "config": []byte(zoneDataWithMultiArrayPartialZone2), + }, + } + + client := fake.NewClientBuilder().WithObjects(secret).Build() + return client, "vxflexos-config", true + }, + "Fail Partial Zone Config 3": func() (client.WithWatch, string, bool) { + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "vxflexos-config", + Namespace: "vxflexos", + }, + Data: map[string][]byte{ + "config": []byte(zoneDataWithMultiArrayPartialZone3), + }, + } + + client := fake.NewClientBuilder().WithObjects(secret).Build() + return client, "vxflexos-config", true }, } for name, tc := range tests { t.Run(name, func(t *testing.T) { - client, wantZones, secret, wantErr := tc() - zones, err := ExtractZonesFromSecret(ctx, client, "vxflexos", secret) + client, secret, wantErr := tc() + err := ValidateZonesInSecret(ctx, client, "vxflexos", secret) if wantErr { assert.NotNil(t, err) } else { assert.Nil(t, err) - assert.Equal(t, wantZones, zones) } }) } diff --git a/tests/e2e/go.mod b/tests/e2e/go.mod index c49678cb..8e0cad3c 100644 --- a/tests/e2e/go.mod +++ b/tests/e2e/go.mod @@ -91,7 +91,7 @@ require ( go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/net v0.31.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/oauth2 v0.21.0 // indirect golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.28.0 // indirect diff --git a/tests/e2e/go.sum b/tests/e2e/go.sum index 3380003e..7e63d281 100644 --- a/tests/e2e/go.sum +++ b/tests/e2e/go.sum @@ -75,8 +75,6 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5 h1:5iH8iuqE5apketRbSFBy+X1V0o+l+8NF1avt4HWl7cA= -github.com/google/pprof v0.0.0-20240827171923-fa2c70bbbfe5/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= @@ -120,12 +118,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/onsi/ginkgo/v2 v2.20.2 h1:7NVCeyIWROIAheY21RLS+3j2bb52W0W82tkberYytp4= -github.com/onsi/ginkgo/v2 v2.20.2/go.mod h1:K9gyxPIlb+aIvnZ8bd9Ak+YP18w3APlR+5coaZoE2ag= github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= -github.com/onsi/gomega v1.34.2 h1:pNCwDkzrsv7MS9kpaQvVb1aVLahQXyJ/Tv5oAZMI3i8= -github.com/onsi/gomega v1.34.2/go.mod h1:v1xfxRgk0KIsG+QOdm7p8UosrOzPYRo60fd3B/1Dukc= github.com/onsi/gomega v1.36.0 h1:Pb12RlruUtj4XUuPUqeEWc6j5DkVVVA49Uf6YLfC95Y= github.com/onsi/gomega v1.36.0/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -214,8 +208,6 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= @@ -228,32 +220,24 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU= -golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E= golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= @@ -262,8 +246,6 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= -golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ= golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/tests/shared/common.go b/tests/shared/common.go index 73934a46..e0323708 100644 --- a/tests/shared/common.go +++ b/tests/shared/common.go @@ -144,6 +144,86 @@ func MakeModule(configVersion string) csmv1.Module { return moduleObj } +// MakeSecretPowerFlexWithZone returns a driver pre-req secret with zoning specified +func MakeSecretPowerFlexWithZone(name, ns, _ string) *corev1.Secret { + dataWithZone := ` +- username: "admin" + password: "password" + systemID: "2b11bb111111bb1b" + endpoint: "https://127.0.0.2" + skipCertificateValidation: true + mdm: "10.0.0.3,10.0.0.4" + zone: + name: "US-EAST" + labelKey: "zone.csi-vxflexos.dellemc.com" +` + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + Data: map[string][]byte{ + "config": []byte(dataWithZone), + }, + } + return secret +} + +// MakeSecretPowerFlex returns a pflex driver pre-req secret +func MakeSecretPowerFlex(name, ns, _ string) *corev1.Secret { + dataWithoutZone := ` +- username: "admin" + password: "password" + systemID: "2b11bb111111bb1b" + endpoint: "https://127.0.0.2" + skipCertificateValidation: true + mdm: "10.0.0.3,10.0.0.4" +` + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + Data: map[string][]byte{ + "config": []byte(dataWithoutZone), + }, + } + return secret +} + +// MakeSecretPowerFlexMultiZoneInvalid returns a pflex driver pre-req secret with invalid zone config +func MakeSecretPowerFlexMultiZoneInvalid(name, ns, _ string) *corev1.Secret { + dataWithInvalidZone := ` +- username: "admin" + password: "password" + systemID: "2b11bb111111bb1b" + endpoint: "https://127.0.0.2" + skipCertificateValidation: true + mdm: "10.0.0.3,10.0.0.4" +- username: "admin" + password: "password" + systemID: "2b11bb111111bb1b" + endpoint: "https://127.0.0.2" + skipCertificateValidation: true + mdm: "10.0.0.3,10.0.0.4" + zone: + name: "US-EAST" + labelKey: "myzone.csi-vxflexos.dellemc.com" +` + + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: ns, + }, + Data: map[string][]byte{ + "config": []byte(dataWithInvalidZone), + }, + } + return secret +} + // MakeSecret returns a driver pre-req secret array-config func MakeSecret(name, ns, _ string) *corev1.Secret { data := map[string][]byte{ diff --git a/tests/shared/crclient/client.go b/tests/shared/crclient/client.go index ee6be344..b9cab262 100644 --- a/tests/shared/crclient/client.go +++ b/tests/shared/crclient/client.go @@ -97,7 +97,7 @@ func (f Client) Get(_ context.Context, key client.ObjectKey, obj client.Object, } // List implements client.Client. -func (f Client) List(ctx context.Context, list client.ObjectList, _ ...client.ListOption) error { +func (f Client) List(ctx context.Context, list client.ObjectList, opts ...client.ListOption) error { if f.ErrorInjector != nil { if err := f.ErrorInjector.ShouldFail("List", list); err != nil { return err @@ -107,7 +107,22 @@ func (f Client) List(ctx context.Context, list client.ObjectList, _ ...client.Li case *corev1.PodList: return f.listPodList(l) case *corev1.NodeList: - return f.listNodeList(l) + + var labelKey string + // Initialize ListOptions + listOpts := &client.ListOptions{} + // Apply each ListOption to listOpts + for _, opt := range opts { + if opt != nil { + opt.ApplyToList(listOpts) + } + } + s := listOpts.LabelSelector + if s != nil { + labelKey = s.String() + } + + return f.listNodeList(l, labelKey) case *appsv1.DeploymentList: return f.listDeploymentList(ctx, &appsv1.DeploymentList{}) default: @@ -124,12 +139,20 @@ func (f Client) listPodList(list *corev1.PodList) error { return nil } -func (f Client) listNodeList(list *corev1.NodeList) error { +func (f Client) listNodeList(list *corev1.NodeList, label string) error { for k, v := range f.Objects { if k.Kind == "Node" { node, ok := v.(*corev1.Node) if ok { - list.Items = append(list.Items, *node) + if label != "" { + for key := range (*node).ObjectMeta.Labels { + if label == key { + list.Items = append(list.Items, *node) + } + } + } else { + list.Items = append(list.Items, *node) + } } } }