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 fc48d6ae..f73b34d0 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) + } } } }