From ad1af14b00dc27fc82e7bd69b967a1111b6e92b1 Mon Sep 17 00:00:00 2001 From: Ilya Alekseyev Date: Thu, 18 Jul 2024 19:51:49 +0200 Subject: [PATCH] Fix Subnet Validation Bug for Nutanix provider (#8461) * Fix Subnet Validation Bug for Nutanix provider - fixed a bug causing subnet validation to be unaware of the cluster context - made subnet requests by name cluster-aware - updated unit tests to reflect these changes * Fix cyclomatic compexity --- pkg/providers/nutanix/validator.go | 55 ++++++++++++++++++------- pkg/providers/nutanix/validator_test.go | 51 ++++++++++++----------- 2 files changed, 67 insertions(+), 39 deletions(-) diff --git a/pkg/providers/nutanix/validator.go b/pkg/providers/nutanix/validator.go index d5cf8ad732ff..827d91b20652 100644 --- a/pkg/providers/nutanix/validator.go +++ b/pkg/providers/nutanix/validator.go @@ -152,7 +152,7 @@ func (v *Validator) validateFailureDomains(ctx context.Context, client Client, c } for _, subnet := range fd.Subnets { - if err := v.validateSubnetConfig(ctx, client, subnet); err != nil { + if err := v.validateSubnetConfig(ctx, client, fd.Cluster, subnet); err != nil { return err } } @@ -254,7 +254,7 @@ func (v *Validator) ValidateMachineConfig(ctx context.Context, client Client, co return err } - if err := v.validateSubnetConfig(ctx, client, config.Spec.Subnet); err != nil { + if err := v.validateSubnetConfig(ctx, client, config.Spec.Cluster, config.Spec.Subnet); err != nil { return err } @@ -362,28 +362,33 @@ func (v *Validator) validateTemplateMatchesKubernetesVersion(ctx context.Context return nil } -func (v *Validator) validateSubnetConfig(ctx context.Context, client Client, identifier anywherev1.NutanixResourceIdentifier) error { - switch identifier.Type { +func (v *Validator) validateSubnetConfig(ctx context.Context, client Client, cluster, subnet anywherev1.NutanixResourceIdentifier) error { + clusterUUID, err := getClusterUUID(ctx, client, cluster) + if err != nil { + return err + } + + switch subnet.Type { case anywherev1.NutanixIdentifierName: - if identifier.Name == nil || *identifier.Name == "" { + if subnet.Name == nil || *subnet.Name == "" { return fmt.Errorf("missing subnet name") } else { - subnetName := *identifier.Name - if _, err := findSubnetUUIDByName(ctx, client, subnetName); err != nil { + subnetName := *subnet.Name + if _, err = findSubnetUUIDByName(ctx, client, clusterUUID, subnetName); err != nil { return fmt.Errorf("failed to find subnet with name %s: %v", subnetName, err) } } case anywherev1.NutanixIdentifierUUID: - if identifier.UUID == nil || *identifier.UUID == "" { + if subnet.UUID == nil || *subnet.UUID == "" { return fmt.Errorf("missing subnet uuid") } else { - subnetUUID := *identifier.UUID - if _, err := client.GetSubnet(ctx, subnetUUID); err != nil { + subnetUUID := *subnet.UUID + if _, err = client.GetSubnet(ctx, subnetUUID); err != nil { return fmt.Errorf("failed to find subnet with uuid %s: %v", subnetUUID, err) } } default: - return fmt.Errorf("invalid subnet identifier type: %s; valid types are: %q and %q", identifier.Type, anywherev1.NutanixIdentifierName, anywherev1.NutanixIdentifierUUID) + return fmt.Errorf("invalid subnet identifier type: %s; valid types are: %q and %q", subnet.Type, anywherev1.NutanixIdentifierName, anywherev1.NutanixIdentifierUUID) } return nil @@ -437,21 +442,43 @@ func (v *Validator) validateAdditionalCategories(ctx context.Context, client Cli } // findSubnetUUIDByName retrieves the subnet uuid by the given subnet name. -func findSubnetUUIDByName(ctx context.Context, v3Client Client, subnetName string) (*string, error) { +func findSubnetUUIDByName(ctx context.Context, v3Client Client, clusterUUID, subnetName string) (*string, error) { res, err := v3Client.ListSubnet(ctx, &v3.DSMetadata{ - Filter: utils.StringPtr(fmt.Sprintf("name==%s", subnetName)), + Filter: utils.StringPtr(fmt.Sprintf("name==%s;cluster_uuid==%s", subnetName, clusterUUID)), }) if err != nil || len(res.Entities) == 0 { return nil, fmt.Errorf("failed to find subnet by name %q: %v", subnetName, err) } if len(res.Entities) > 1 { - return nil, fmt.Errorf("found more than one (%v) subnet with name %q", len(res.Entities), subnetName) + return nil, fmt.Errorf("found more than one (%v) subnet with name %q and cluster uuid %v", len(res.Entities), subnetName, clusterUUID) } return res.Entities[0].Metadata.UUID, nil } +// getClusterUUID retrieves the cluster uuid by the given cluster identifier. +func getClusterUUID(ctx context.Context, v3Client Client, cluster anywherev1.NutanixResourceIdentifier) (string, error) { + var clusterUUID string + var err error + if cluster.Type == anywherev1.NutanixIdentifierUUID { + if cluster.UUID == nil || *cluster.UUID == "" { + return "", fmt.Errorf("missing cluster uuid") + } + clusterUUID = *cluster.UUID + } + + if cluster.Type == anywherev1.NutanixIdentifierName { + clusterName := *cluster.Name + var uuid *string + if uuid, err = findClusterUUIDByName(ctx, v3Client, clusterName); err != nil { + return "", fmt.Errorf("failed to find cluster with name %q: %v", clusterName, err) + } + clusterUUID = *uuid + } + return clusterUUID, nil +} + // findClusterUUIDByName retrieves the cluster uuid by the given cluster name. func findClusterUUIDByName(ctx context.Context, v3Client Client, clusterName string) (*string, error) { res, err := v3Client.ListCluster(ctx, &v3.DSMetadata{ diff --git a/pkg/providers/nutanix/validator_test.go b/pkg/providers/nutanix/validator_test.go index 01fdb204aa17..e0609c415923 100644 --- a/pkg/providers/nutanix/validator_test.go +++ b/pkg/providers/nutanix/validator_test.go @@ -153,7 +153,7 @@ func fakeSubnetListForDCTest(filter *string) (*v3.SubnetListIntentResponse, erro Entities: []*v3.SubnetIntentResponse{ { Metadata: &v3.Metadata{ - UUID: utils.StringPtr("b15f6966-bfc7-4d1e-8575-224096fc1cdb"), + UUID: utils.StringPtr("2d166190-7759-4dc6-b835-923262d6b497"), }, Spec: &v3.Subnet{ Name: utils.StringPtr("prism-subnet"), @@ -175,7 +175,8 @@ func fakeSubnetListForDCTest(filter *string) (*v3.SubnetListIntentResponse, erro } if filter != nil && *filter != "" { - str := strings.Replace(*filter, "name==", "", -1) + filters := strings.Split(*filter, ";") + str := strings.Replace(filters[0], "name==", "", -1) for _, subnet := range data.Entities { if str == *subnet.Spec.Name { result.Entities = append(result.Entities, subnet) @@ -320,7 +321,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "empty subnet name", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) machineConf.Spec.Subnet.Name = nil clientCache := &ClientCache{clients: map[string]Client{"test": mockClient}} return NewValidator(clientCache, validator, &http.Client{Transport: transport}) @@ -330,7 +331,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "empty subnet uuid", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) machineConf.Spec.Subnet.Type = anywherev1.NutanixIdentifierUUID machineConf.Spec.Subnet.UUID = nil clientCache := &ClientCache{clients: map[string]Client{"test": mockClient}} @@ -341,7 +342,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "invalid subnet identifier type", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) machineConf.Spec.Subnet.Type = "notanidentifier" clientCache := &ClientCache{clients: map[string]Client{"test": mockClient}} return NewValidator(clientCache, validator, &http.Client{Transport: transport}) @@ -351,7 +352,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "list subnet request failed", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(nil, errors.New("subnet not found")) clientCache := &ClientCache{clients: map[string]Client{"test": mockClient}} return NewValidator(clientCache, validator, &http.Client{Transport: transport}) @@ -361,7 +362,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "list subnet request did not find match", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(&v3.SubnetListIntentResponse{}, nil) clientCache := &ClientCache{clients: map[string]Client{"test": mockClient}} return NewValidator(clientCache, validator, &http.Client{Transport: transport}) @@ -371,7 +372,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "duplicate subnets found", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) subnets := fakeSubnetList() subnets.Entities = append(subnets.Entities, subnets.Entities[0]) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(subnets, nil) @@ -383,7 +384,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "empty image name", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) machineConf.Spec.Image.Name = nil clientCache := &ClientCache{clients: map[string]Client{"test": mockClient}} @@ -394,7 +395,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "empty image uuid", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) machineConf.Spec.Image.Type = anywherev1.NutanixIdentifierUUID machineConf.Spec.Image.UUID = nil @@ -406,7 +407,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "invalid image identifier type", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) machineConf.Spec.Image.Type = "notanidentifier" clientCache := &ClientCache{clients: map[string]Client{"test": mockClient}} @@ -417,7 +418,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "list image request failed", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) mockClient.EXPECT().ListImage(gomock.Any(), gomock.Any()).Return(nil, errors.New("image not found")) clientCache := &ClientCache{clients: map[string]Client{"test": mockClient}} @@ -428,7 +429,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "list image request did not find match", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) mockClient.EXPECT().ListImage(gomock.Any(), gomock.Any()).Return(&v3.ImageListIntentResponse{}, nil) clientCache := &ClientCache{clients: map[string]Client{"test": mockClient}} @@ -439,7 +440,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "duplicate image found", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) images := fakeImageList() images.Entities = append(images.Entities, images.Entities[0]) @@ -460,7 +461,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { assert.NoError(t, err) cluster.Status.Resources.Config.ServiceList = []*string{utils.StringPtr("PRISM_CENTRAL")} clusters.Entities = append(clusters.Entities, &cluster) - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(clusters, nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(clusters, nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) mockClient.EXPECT().ListImage(gomock.Any(), gomock.Any()).Return(fakeImageList(), nil) clientCache := &ClientCache{clients: map[string]Client{"test": mockClient}} @@ -471,7 +472,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "empty project name", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) mockClient.EXPECT().ListImage(gomock.Any(), gomock.Any()).Return(fakeImageList(), nil) machineConf.Spec.Project = &anywherev1.NutanixResourceIdentifier{ @@ -486,7 +487,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "empty project uuid", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) mockClient.EXPECT().ListImage(gomock.Any(), gomock.Any()).Return(fakeImageList(), nil) machineConf.Spec.Project = &anywherev1.NutanixResourceIdentifier{ @@ -501,7 +502,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "invalid project identifier type", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) mockClient.EXPECT().ListImage(gomock.Any(), gomock.Any()).Return(fakeImageList(), nil) machineConf.Spec.Project = &anywherev1.NutanixResourceIdentifier{ @@ -516,7 +517,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "list project request failed", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) mockClient.EXPECT().ListImage(gomock.Any(), gomock.Any()).Return(fakeImageList(), nil) mockClient.EXPECT().ListProject(gomock.Any(), gomock.Any()).Return(nil, errors.New("project not found")) @@ -532,7 +533,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "list project request did not find match", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) mockClient.EXPECT().ListImage(gomock.Any(), gomock.Any()).Return(fakeImageList(), nil) mockClient.EXPECT().ListProject(gomock.Any(), gomock.Any()).Return(&v3.ProjectListResponse{}, nil) @@ -548,7 +549,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "duplicate project found", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) mockClient.EXPECT().ListImage(gomock.Any(), gomock.Any()).Return(fakeImageList(), nil) projects := fakeProjectList() @@ -566,7 +567,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "empty category key", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) mockClient.EXPECT().ListImage(gomock.Any(), gomock.Any()).Return(fakeImageList(), nil) machineConf.Spec.AdditionalCategories = []anywherev1.NutanixCategoryIdentifier{ @@ -583,7 +584,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "empty category value", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) mockClient.EXPECT().ListImage(gomock.Any(), gomock.Any()).Return(fakeImageList(), nil) machineConf.Spec.AdditionalCategories = []anywherev1.NutanixCategoryIdentifier{ @@ -600,7 +601,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "get category key failed", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) mockClient.EXPECT().ListImage(gomock.Any(), gomock.Any()).Return(fakeImageList(), nil) mockClient.EXPECT().GetCategoryKey(gomock.Any(), gomock.Any()).Return(nil, errors.New("category key not found")) @@ -618,7 +619,7 @@ func TestNutanixValidatorValidateMachineConfig(t *testing.T) { { name: "get category value failed", setup: func(machineConf *anywherev1.NutanixMachineConfig, mockClient *mocknutanix.MockClient, validator *mockCrypto.MockTlsValidator, transport *mocknutanix.MockRoundTripper) *Validator { - mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil) + mockClient.EXPECT().ListCluster(gomock.Any(), gomock.Any()).Return(fakeClusterList(), nil).Times(2) mockClient.EXPECT().ListSubnet(gomock.Any(), gomock.Any()).Return(fakeSubnetList(), nil) mockClient.EXPECT().ListImage(gomock.Any(), gomock.Any()).Return(fakeImageList(), nil) categoryKey := v3.CategoryKeyStatus{