github.com/openshift/installer@v1.4.17/pkg/asset/installconfig/ibmcloud/metadata_test.go (about)

     1  package ibmcloud
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"testing"
     7  
     8  	"github.com/IBM/platform-services-go-sdk/iamidentityv1"
     9  	"github.com/IBM/vpc-go-sdk/vpcv1"
    10  	"github.com/golang/mock/gomock"
    11  	"github.com/stretchr/testify/assert"
    12  
    13  	"github.com/openshift/installer/pkg/asset/installconfig/ibmcloud/mock"
    14  	"github.com/openshift/installer/pkg/asset/installconfig/ibmcloud/responses"
    15  	"github.com/openshift/installer/pkg/types"
    16  	ibmcloudtypes "github.com/openshift/installer/pkg/types/ibmcloud"
    17  )
    18  
    19  type editMetadata []func(m *Metadata)
    20  
    21  var (
    22  	// Shared test values.
    23  	goodDomain = "domain.good.com"
    24  	badDomain  = "domain.bad"
    25  	region     = "us-south"
    26  
    27  	// Account ID test values.
    28  	newAccountID      = "new-account-id"
    29  	existingAccountID = "existing-account-id"
    30  
    31  	// CIS Instance CRN test values.
    32  	newCISCRN      = "new-cis-crn"
    33  	existingCISCRN = "existing-cis-crn"
    34  
    35  	// DNS Instance test values.
    36  	newDNSInstanceID       = "new-dns-instance-id"
    37  	newDNSInstanceCRN      = "new-dns-instance-crn"
    38  	existingDNSInstanceID  = "existing-dns-instance-id"
    39  	existingDNSInstanceCRN = "existing-dns-instance-crn"
    40  	unknownDNSInstanceID   = "unknown-dns-instance-id"
    41  	unknownDNSInstanceCRN  = "unknown-dns-instance-crn"
    42  
    43  	// Newly retrieved Compute Subnets.
    44  	newComputeSubnet1Name     = "new-compute-subnet-1"
    45  	newComputeSubnet1ID       = "new-compute-1-id"
    46  	newComputeSubnet1CIDR     = "new-compute-1-cidr"
    47  	newComputeSubnet1CRN      = "new-compute-1-crn"
    48  	newComputeSubnet1VPCName  = "new-compute-1-vpc"
    49  	newComputeSubnet1ZoneName = "new-compute-1-zone"
    50  	newComputeSubnet2Name     = "new-compute-subnet-2"
    51  	newComputeSubnet2ID       = "new-compute-2-id"
    52  	newComputeSubnet2CIDR     = "new-compute-2-cidr"
    53  	newComputeSubnet2CRN      = "new-compute-2-crn"
    54  	newComputeSubnet2VPCName  = "new-compute-2-vpc"
    55  	newComputeSubnet2ZoneName = "new-compute-2-zone"
    56  	newComputeSubnets         = map[string]Subnet{
    57  		newComputeSubnet1ID: {
    58  			Name: newComputeSubnet1Name,
    59  			ID:   newComputeSubnet1ID,
    60  			CIDR: newComputeSubnet1CIDR,
    61  			CRN:  newComputeSubnet1CRN,
    62  			VPC:  newComputeSubnet1VPCName,
    63  			Zone: newComputeSubnet1ZoneName,
    64  		},
    65  		newComputeSubnet2ID: {
    66  			Name: newComputeSubnet2Name,
    67  			ID:   newComputeSubnet2ID,
    68  			CIDR: newComputeSubnet2CIDR,
    69  			CRN:  newComputeSubnet2CRN,
    70  			VPC:  newComputeSubnet2VPCName,
    71  			Zone: newComputeSubnet2ZoneName,
    72  		},
    73  	}
    74  
    75  	// Previously retrieved Compute Subnets.
    76  	existingComputeSubnets = map[string]Subnet{
    77  		"existing-compute-1-id": {
    78  			Name: "existing-compute-subnet-1",
    79  			ID:   "existing-compute-1-id",
    80  			CIDR: "existing-compute-1-cidr",
    81  			CRN:  "existing-compute-1-crn",
    82  			VPC:  "existing-compute-1-vpc",
    83  			Zone: "existing-compute-1-zone",
    84  		},
    85  		"existing-compute-2-id": {
    86  			Name: "existing-compute-subnet-2",
    87  			ID:   "existing-compute-2-id",
    88  			CIDR: "existing-compute-2-cidr",
    89  			CRN:  "existing-compute-2-crn",
    90  			VPC:  "existing-compute-2-vpc",
    91  			Zone: "existing-compute-2-zone",
    92  		},
    93  	}
    94  
    95  	// Newly retrieved Control Plane Subnets.
    96  	newControlPlaneSubnet1Name     = "new-cp-subnet-1"
    97  	newControlPlaneSubnet1ID       = "new-cp-1-id"
    98  	newControlPlaneSubnet1CIDR     = "new-cp-1-cidr"
    99  	newControlPlaneSubnet1CRN      = "new-cp-1-crn"
   100  	newControlPlaneSubnet1VPCName  = "new-cp-1-vpc"
   101  	newControlPlaneSubnet1ZoneName = "new-cp-1-zone"
   102  	newControlPlaneSubnet2Name     = "new-cp-subnet-2"
   103  	newControlPlaneSubnet2ID       = "new-cp-2-id"
   104  	newControlPlaneSubnet2CIDR     = "new-cp-2-cidr"
   105  	newControlPlaneSubnet2CRN      = "new-cp-2-crn"
   106  	newControlPlaneSubnet2VPCName  = "new-cp-2-vpc"
   107  	newControlPlaneSubnet2ZoneName = "new-cp-2-zone"
   108  	newControlPlaneSubnets         = map[string]Subnet{
   109  		newControlPlaneSubnet1ID: {
   110  			Name: newControlPlaneSubnet1Name,
   111  			ID:   newControlPlaneSubnet1ID,
   112  			CIDR: newControlPlaneSubnet1CIDR,
   113  			CRN:  newControlPlaneSubnet1CRN,
   114  			VPC:  newControlPlaneSubnet1VPCName,
   115  			Zone: newControlPlaneSubnet1ZoneName,
   116  		},
   117  		newControlPlaneSubnet2ID: {
   118  			Name: newControlPlaneSubnet2Name,
   119  			ID:   newControlPlaneSubnet2ID,
   120  			CIDR: newControlPlaneSubnet2CIDR,
   121  			CRN:  newControlPlaneSubnet2CRN,
   122  			VPC:  newControlPlaneSubnet2VPCName,
   123  			Zone: newControlPlaneSubnet2ZoneName,
   124  		},
   125  	}
   126  
   127  	// Previously retrieved Control Plane Subnets.
   128  	existingControlPlaneSubnets = map[string]Subnet{
   129  		"existing-cp-1-id": {
   130  			Name: "existing-cp-subnet-1",
   131  			ID:   "existing-cp-1-id",
   132  			CIDR: "existing-cp-1-cidr",
   133  			CRN:  "existing-cp-1-crn",
   134  			VPC:  "existing-cp-1-vpc",
   135  			Zone: "existing-cp-1-zone",
   136  		},
   137  		"existing-cp-2-id": {
   138  			Name: "existing-cp-subnet-2",
   139  			ID:   "existing-cp-2-id",
   140  			CIDR: "existing-cp-2-cidr",
   141  			CRN:  "existing-cp-2-crn",
   142  			VPC:  "existing-cp-2-vpc",
   143  			Zone: "existing-cp-2-zone",
   144  		},
   145  	}
   146  
   147  	// Subnet Names for failure finding subnets.
   148  	failedGetComputeSubnetName      = "failed-get-compute-subnet"
   149  	failedGetControlPlaneSubnetName = "failed-get-cp-subnet"
   150  
   151  	// Subnet Names and ID's for missing values.
   152  	noIDComputeSubnetName       = "no-id-compute-subnet"
   153  	incompleteComputeSubnetName = "incomplete-compute-subnet-name"
   154  	noCIDRComputeSubnetID       = "no-cidr-compute-subnet-id"
   155  	noCRNComputeSubnetID        = "no-crn-compute-subnet-id"
   156  	noNameComputeSubnetID       = "no-name-compute-subnet-id"
   157  	noVPCComputeSubnetID        = "no-vpc-compute-subnet-id"
   158  	noZoneComputeSubnetID       = "no-zone-compute-subnet-id"
   159  
   160  	// VPCReferences for Client Subnet responses.
   161  	vpcReferenceComputeSubnet1      = vpcv1.VPCReference{Name: &newComputeSubnet1VPCName}
   162  	vpcReferenceComputeSubnet2      = vpcv1.VPCReference{Name: &newComputeSubnet2VPCName}
   163  	vpcReferenceControlPlaneSubnet1 = vpcv1.VPCReference{Name: &newControlPlaneSubnet1VPCName}
   164  	vpcReferenceControlPlaneSubnet2 = vpcv1.VPCReference{Name: &newControlPlaneSubnet2VPCName}
   165  
   166  	// ZoneRefences for Client Subnet responses.
   167  	zoneReferenceComputeSubnet1      = vpcv1.ZoneReference{Name: &newComputeSubnet1ZoneName}
   168  	zoneReferenceComputeSubnet2      = vpcv1.ZoneReference{Name: &newComputeSubnet2ZoneName}
   169  	zoneReferenceControlPlaneSubnet1 = vpcv1.ZoneReference{Name: &newControlPlaneSubnet1ZoneName}
   170  	zoneReferenceControlPlaneSubnet2 = vpcv1.ZoneReference{Name: &newControlPlaneSubnet2ZoneName}
   171  )
   172  
   173  func baseMetadata() *Metadata {
   174  	return NewMetadata(&types.InstallConfig{
   175  		BaseDomain: goodDomain,
   176  		Platform: types.Platform{
   177  			IBMCloud: &ibmcloudtypes.Platform{
   178  				Region: region,
   179  			},
   180  		},
   181  		Publish: types.ExternalPublishingStrategy,
   182  	})
   183  }
   184  
   185  func setInternalPublishingStrategy(m *Metadata) {
   186  	m.publishStrategy = types.InternalPublishingStrategy
   187  }
   188  
   189  func TestAccountID(t *testing.T) {
   190  	testCases := []struct {
   191  		name          string
   192  		edits         editMetadata
   193  		errorMsg      string
   194  		expectedValue string
   195  	}{
   196  		{
   197  			name:          "new accountID",
   198  			expectedValue: newAccountID,
   199  		},
   200  		{
   201  			name: "existing accountID",
   202  			edits: editMetadata{
   203  				func(m *Metadata) {
   204  					m.accountID = existingAccountID
   205  				},
   206  			},
   207  			expectedValue: existingAccountID,
   208  		},
   209  		{
   210  			name:     "auth apikey error",
   211  			errorMsg: "bad api key",
   212  		},
   213  	}
   214  
   215  	// IBM Cloud Client Mocks.
   216  	mockCtrl := gomock.NewController(t)
   217  	defer mockCtrl.Finish()
   218  	ibmcloudClient := mock.NewMockAPI(mockCtrl)
   219  
   220  	// Shared Mocks.
   221  	ibmcloudClient.EXPECT().SetVPCServiceURLForRegion(gomock.Any(), "us-south").AnyTimes()
   222  
   223  	// Mocks: new accountID.
   224  	ibmcloudClient.EXPECT().GetAuthenticatorAPIKeyDetails(gomock.Any()).Return(&iamidentityv1.APIKey{AccountID: &newAccountID}, nil)
   225  
   226  	// Mocks: existing accountID.
   227  	// N/A.
   228  
   229  	// Mocks: auth apikey error.
   230  	ibmcloudClient.EXPECT().GetAuthenticatorAPIKeyDetails(gomock.Any()).Return(nil, fmt.Errorf("bad api key"))
   231  
   232  	for _, tCase := range testCases {
   233  		t.Run(tCase.name, func(t *testing.T) {
   234  			metadata := baseMetadata()
   235  			metadata.client = ibmcloudClient
   236  			for _, edit := range tCase.edits {
   237  				edit(metadata)
   238  			}
   239  
   240  			actualValue, err := metadata.AccountID(context.TODO())
   241  			if err != nil {
   242  				assert.Regexp(t, tCase.errorMsg, err)
   243  			} else {
   244  				assert.Equal(t, tCase.expectedValue, actualValue)
   245  			}
   246  		})
   247  	}
   248  }
   249  
   250  func TestCISInstanceCRN(t *testing.T) {
   251  	testCases := []struct {
   252  		name          string
   253  		edits         editMetadata
   254  		errorMsg      string
   255  		expectedValue string
   256  	}{
   257  		{
   258  			name:          "new cis crn",
   259  			expectedValue: newCISCRN,
   260  		},
   261  		{
   262  			name: "existing cis crn",
   263  			edits: editMetadata{
   264  				func(m *Metadata) {
   265  					m.cisInstanceCRN = existingCISCRN
   266  				},
   267  			},
   268  			expectedValue: existingCISCRN,
   269  		},
   270  		{
   271  			name:     "get dns zone error",
   272  			errorMsg: "dns zone error",
   273  		},
   274  		{
   275  			name:     "dns zone not found error",
   276  			errorMsg: fmt.Sprintf("cisInstanceCRN unknown due to DNS zone %q not found", goodDomain),
   277  		},
   278  	}
   279  
   280  	// IBM Cloud Client Mocks.
   281  	mockCtrl := gomock.NewController(t)
   282  	defer mockCtrl.Finish()
   283  	ibmcloudClient := mock.NewMockAPI(mockCtrl)
   284  
   285  	// Shared Mocks.
   286  	// N/A.
   287  
   288  	// Mocks: new cis crn.
   289  	ibmcloudClient.EXPECT().GetDNSZones(gomock.Any(), types.ExternalPublishingStrategy).Return([]responses.DNSZoneResponse{{Name: goodDomain, InstanceCRN: newCISCRN}}, nil)
   290  
   291  	// Mocks: existing cis crn.
   292  	// N/A.
   293  
   294  	// Mocks: get dns zone error.
   295  	ibmcloudClient.EXPECT().GetDNSZones(gomock.Any(), types.ExternalPublishingStrategy).Return(nil, fmt.Errorf("dns zone error"))
   296  
   297  	// Mocks: dns zone not found error.
   298  	ibmcloudClient.EXPECT().GetDNSZones(gomock.Any(), types.ExternalPublishingStrategy).Return([]responses.DNSZoneResponse{{Name: badDomain}}, nil)
   299  
   300  	for _, tCase := range testCases {
   301  		t.Run(tCase.name, func(t *testing.T) {
   302  			metadata := baseMetadata()
   303  			metadata.client = ibmcloudClient
   304  			for _, edit := range tCase.edits {
   305  				edit(metadata)
   306  			}
   307  
   308  			actualValue, err := metadata.CISInstanceCRN(context.TODO())
   309  			if err != nil {
   310  				assert.Regexp(t, tCase.errorMsg, err)
   311  			} else {
   312  				assert.Equal(t, tCase.expectedValue, actualValue)
   313  			}
   314  		})
   315  	}
   316  }
   317  
   318  func TestSetCISInstanceCRN(t *testing.T) {
   319  	testCases := []struct {
   320  		name   string
   321  		cisCRN string
   322  	}{
   323  		{
   324  			name:   "set cis crn",
   325  			cisCRN: "cis-instance-crn",
   326  		},
   327  	}
   328  
   329  	for _, tCase := range testCases {
   330  		t.Run(tCase.name, func(t *testing.T) {
   331  			metadata := baseMetadata()
   332  
   333  			metadata.cisInstanceCRN = tCase.cisCRN
   334  			actualValue, err := metadata.CISInstanceCRN(context.TODO())
   335  			assert.NoError(t, err)
   336  			assert.Equal(t, tCase.cisCRN, actualValue)
   337  		})
   338  	}
   339  }
   340  
   341  func TestDNSInstance(t *testing.T) {
   342  	testCases := []struct {
   343  		name        string
   344  		edits       editMetadata
   345  		errorMsg    string
   346  		expectedID  string
   347  		expectedCRN string
   348  	}{
   349  		{
   350  			name:        "new dns instance",
   351  			expectedID:  newDNSInstanceID,
   352  			expectedCRN: newDNSInstanceCRN,
   353  		},
   354  		{
   355  			name: "existing dns instance",
   356  			edits: editMetadata{
   357  				func(m *Metadata) {
   358  					m.dnsInstance = &DNSInstance{
   359  						ID:  existingDNSInstanceID,
   360  						CRN: existingDNSInstanceCRN,
   361  					}
   362  				},
   363  			},
   364  			expectedID:  existingDNSInstanceID,
   365  			expectedCRN: existingDNSInstanceCRN,
   366  		},
   367  		{
   368  			name:     "get dns zone error",
   369  			errorMsg: "dns zone error",
   370  		},
   371  		{
   372  			name:     "dns instance unknown id error",
   373  			errorMsg: fmt.Sprintf("dnsInstance has unknown ID/CRN: %q - %q", "", unknownDNSInstanceCRN),
   374  		},
   375  		{
   376  			name:     "dns instance unknown crn error",
   377  			errorMsg: fmt.Sprintf("dnsInstance has unknown ID/CRN: %q - %q", unknownDNSInstanceID, ""),
   378  		},
   379  		{
   380  			name:     "dns zone not found error",
   381  			errorMsg: fmt.Sprintf("dnsInstance unknown due to DNS zone %q not found", goodDomain),
   382  		},
   383  	}
   384  
   385  	// IBM Cloud Client Mocks.
   386  	mockCtrl := gomock.NewController(t)
   387  	defer mockCtrl.Finish()
   388  	ibmcloudClient := mock.NewMockAPI(mockCtrl)
   389  
   390  	// Shared Mocks.
   391  	// N/A.
   392  
   393  	// Mocks: new dns instance.
   394  	ibmcloudClient.EXPECT().GetDNSZones(gomock.Any(), types.InternalPublishingStrategy).Return([]responses.DNSZoneResponse{{Name: goodDomain, InstanceID: newDNSInstanceID, InstanceCRN: newDNSInstanceCRN}}, nil)
   395  
   396  	// Mocks: existing dns instance.
   397  	// N/A.
   398  
   399  	// Mocks: get dns zone error.
   400  	ibmcloudClient.EXPECT().GetDNSZones(gomock.Any(), types.InternalPublishingStrategy).Return(nil, fmt.Errorf("dns zone error"))
   401  
   402  	// Mocks: dns instance unknown id error.
   403  	ibmcloudClient.EXPECT().GetDNSZones(gomock.Any(), types.InternalPublishingStrategy).Return([]responses.DNSZoneResponse{{Name: goodDomain, InstanceCRN: unknownDNSInstanceCRN}}, nil)
   404  
   405  	// Mocks: dns instance unknown crn error.
   406  	ibmcloudClient.EXPECT().GetDNSZones(gomock.Any(), types.InternalPublishingStrategy).Return([]responses.DNSZoneResponse{{Name: goodDomain, InstanceID: unknownDNSInstanceID}}, nil)
   407  
   408  	// Mocks: dns zone not found error.
   409  	ibmcloudClient.EXPECT().GetDNSZones(gomock.Any(), types.InternalPublishingStrategy).Return([]responses.DNSZoneResponse{{Name: badDomain}}, nil)
   410  
   411  	for _, tCase := range testCases {
   412  		t.Run(tCase.name, func(t *testing.T) {
   413  			metadata := baseMetadata()
   414  			setInternalPublishingStrategy(metadata)
   415  			metadata.client = ibmcloudClient
   416  			for _, edit := range tCase.edits {
   417  				edit(metadata)
   418  			}
   419  
   420  			actualDNS, err := metadata.DNSInstance(context.TODO())
   421  			if err != nil {
   422  				assert.Regexp(t, tCase.errorMsg, err)
   423  			} else {
   424  				assert.Equal(t, tCase.expectedID, actualDNS.ID)
   425  				assert.Equal(t, tCase.expectedCRN, actualDNS.CRN)
   426  			}
   427  		})
   428  	}
   429  }
   430  
   431  func TestSetDNSInstance(t *testing.T) {
   432  	testCases := []struct {
   433  		name   string
   434  		dnsID  string
   435  		dnsCRN string
   436  	}{
   437  		{
   438  			name:   "set dns id/crn",
   439  			dnsID:  "dns-instance-id",
   440  			dnsCRN: "dns-instance-crn",
   441  		},
   442  	}
   443  
   444  	for _, tCase := range testCases {
   445  		t.Run(tCase.name, func(t *testing.T) {
   446  			metadata := baseMetadata()
   447  			setInternalPublishingStrategy(metadata)
   448  
   449  			metadata.dnsInstance = &DNSInstance{
   450  				ID:  tCase.dnsID,
   451  				CRN: tCase.dnsCRN,
   452  			}
   453  			actualDNS, err := metadata.DNSInstance(context.TODO())
   454  			assert.NoError(t, err)
   455  			assert.Equal(t, tCase.dnsID, actualDNS.ID)
   456  			assert.Equal(t, tCase.dnsCRN, actualDNS.CRN)
   457  		})
   458  	}
   459  }
   460  
   461  func TestComputeSubnets(t *testing.T) {
   462  	testCases := []struct {
   463  		name          string
   464  		edits         editMetadata
   465  		errorMsg      string
   466  		expectedValue map[string]Subnet
   467  	}{
   468  		{
   469  			name:          "no compute subnets",
   470  			expectedValue: nil,
   471  		},
   472  		{
   473  			name: "new compute subnets",
   474  			edits: editMetadata{
   475  				func(m *Metadata) {
   476  					m.ComputeSubnetNames = []string{newComputeSubnet1Name, newComputeSubnet2Name}
   477  				},
   478  			},
   479  			expectedValue: newComputeSubnets,
   480  		},
   481  		{
   482  			name: "new single compute subnet",
   483  			edits: editMetadata{
   484  				func(m *Metadata) {
   485  					m.ComputeSubnetNames = []string{newComputeSubnet2Name}
   486  				},
   487  			},
   488  			expectedValue: map[string]Subnet{
   489  				newComputeSubnet2ID: newComputeSubnets[newComputeSubnet2ID],
   490  			},
   491  		},
   492  		{
   493  			name: "existing compute subnets",
   494  			edits: editMetadata{
   495  				func(m *Metadata) {
   496  					m.computeSubnets = existingComputeSubnets
   497  				},
   498  			},
   499  			expectedValue: existingComputeSubnets,
   500  		},
   501  		{
   502  			name: "failed getting compute subnet",
   503  			edits: editMetadata{
   504  				func(m *Metadata) {
   505  					m.ComputeSubnetNames = []string{failedGetComputeSubnetName}
   506  				},
   507  			},
   508  			errorMsg: fmt.Sprintf("getting subnet %s", failedGetComputeSubnetName),
   509  		},
   510  		{
   511  			name: "compute subnet has no id",
   512  			edits: editMetadata{
   513  				func(m *Metadata) {
   514  					m.ComputeSubnetNames = []string{noIDComputeSubnetName}
   515  				},
   516  			},
   517  			errorMsg: fmt.Sprintf("%s has no ID", noIDComputeSubnetName),
   518  		},
   519  		{
   520  			name: "compute subnet has no CIDR block",
   521  			edits: editMetadata{
   522  				func(m *Metadata) {
   523  					m.ComputeSubnetNames = []string{incompleteComputeSubnetName}
   524  				},
   525  			},
   526  			errorMsg: fmt.Sprintf("%s has no Ipv4CIDRBlock", noCIDRComputeSubnetID),
   527  		},
   528  		{
   529  			name: "compute subnet has no CRN",
   530  			edits: editMetadata{
   531  				func(m *Metadata) {
   532  					m.ComputeSubnetNames = []string{incompleteComputeSubnetName}
   533  				},
   534  			},
   535  			errorMsg: fmt.Sprintf("%s has no CRN", noCRNComputeSubnetID),
   536  		},
   537  		{
   538  			name: "compute subnet has no Name",
   539  			edits: editMetadata{
   540  				func(m *Metadata) {
   541  					m.ComputeSubnetNames = []string{incompleteComputeSubnetName}
   542  				},
   543  			},
   544  			errorMsg: fmt.Sprintf("%s has no Name", noNameComputeSubnetID),
   545  		},
   546  		{
   547  			name: "compute subnet has no VPC",
   548  			edits: editMetadata{
   549  				func(m *Metadata) {
   550  					m.ComputeSubnetNames = []string{incompleteComputeSubnetName}
   551  				},
   552  			},
   553  			errorMsg: fmt.Sprintf("%s has no VPC", noVPCComputeSubnetID),
   554  		},
   555  		{
   556  			name: "compute subnet has no Zone",
   557  			edits: editMetadata{
   558  				func(m *Metadata) {
   559  					m.ComputeSubnetNames = []string{incompleteComputeSubnetName}
   560  				},
   561  			},
   562  			errorMsg: fmt.Sprintf("%s has no Zone", noZoneComputeSubnetID),
   563  		},
   564  	}
   565  
   566  	// IBM Cloud Client Mocks.
   567  	mockCtrl := gomock.NewController(t)
   568  	defer mockCtrl.Finish()
   569  	ibmcloudClient := mock.NewMockAPI(mockCtrl)
   570  
   571  	// Shared Mocks.
   572  	// N/A.
   573  
   574  	// Mocks: no compute subnets.
   575  	// N/A.
   576  
   577  	// Mocks: new compute subnets.
   578  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), newComputeSubnet1Name, region).Return(
   579  		&vpcv1.Subnet{
   580  			Name:          &newComputeSubnet1Name,
   581  			ID:            &newComputeSubnet1ID,
   582  			Ipv4CIDRBlock: &newComputeSubnet1CIDR,
   583  			CRN:           &newComputeSubnet1CRN,
   584  			VPC:           &vpcReferenceComputeSubnet1,
   585  			Zone:          &zoneReferenceComputeSubnet1,
   586  		},
   587  		nil,
   588  	)
   589  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), newComputeSubnet2Name, region).Return(
   590  		&vpcv1.Subnet{
   591  			Name:          &newComputeSubnet2Name,
   592  			ID:            &newComputeSubnet2ID,
   593  			Ipv4CIDRBlock: &newComputeSubnet2CIDR,
   594  			CRN:           &newComputeSubnet2CRN,
   595  			VPC:           &vpcReferenceComputeSubnet2,
   596  			Zone:          &zoneReferenceComputeSubnet2,
   597  		},
   598  		nil,
   599  	)
   600  
   601  	// Mocks: new single compute subnet.
   602  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), newComputeSubnet2Name, region).Return(
   603  		&vpcv1.Subnet{
   604  			Name:          &newComputeSubnet2Name,
   605  			ID:            &newComputeSubnet2ID,
   606  			Ipv4CIDRBlock: &newComputeSubnet2CIDR,
   607  			CRN:           &newComputeSubnet2CRN,
   608  			VPC:           &vpcReferenceComputeSubnet2,
   609  			Zone:          &zoneReferenceComputeSubnet2,
   610  		},
   611  		nil,
   612  	)
   613  
   614  	// Mocks: existing compute subnets.
   615  	// N/A.
   616  
   617  	// Mocks: failed getting compute subnet.
   618  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), failedGetComputeSubnetName, region).Return(nil, &VPCResourceNotFoundError{})
   619  
   620  	// Mocks: compute subnet has no ID.
   621  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), noIDComputeSubnetName, region).Return(
   622  		&vpcv1.Subnet{
   623  			Name: &noIDComputeSubnetName,
   624  			// ID Skipped.
   625  			Ipv4CIDRBlock: &newComputeSubnet1CIDR,
   626  			CRN:           &newComputeSubnet1CRN,
   627  			VPC:           &vpcReferenceComputeSubnet1,
   628  			Zone:          &zoneReferenceComputeSubnet1,
   629  		},
   630  		nil,
   631  	)
   632  
   633  	// Mocks: compute subnet has no CIDR.
   634  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), incompleteComputeSubnetName, region).Return(
   635  		&vpcv1.Subnet{
   636  			Name: &incompleteComputeSubnetName,
   637  			ID:   &noCIDRComputeSubnetID,
   638  			// Ipv4CIDRBlock Skipped.
   639  			CRN:  &newComputeSubnet1CRN,
   640  			VPC:  &vpcReferenceComputeSubnet1,
   641  			Zone: &zoneReferenceComputeSubnet1,
   642  		},
   643  		nil,
   644  	)
   645  
   646  	// Mocks: compute subnet has no CRN.
   647  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), incompleteComputeSubnetName, region).Return(
   648  		&vpcv1.Subnet{
   649  			Name:          &incompleteComputeSubnetName,
   650  			ID:            &noCRNComputeSubnetID,
   651  			Ipv4CIDRBlock: &newComputeSubnet1CIDR,
   652  			// CRN Skipped.
   653  			VPC:  &vpcReferenceComputeSubnet1,
   654  			Zone: &zoneReferenceComputeSubnet1,
   655  		},
   656  		nil,
   657  	)
   658  
   659  	// Mocks: compute subnet has no Name.
   660  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), incompleteComputeSubnetName, region).Return(
   661  		&vpcv1.Subnet{
   662  			// Name Skipped.
   663  			ID:            &noNameComputeSubnetID,
   664  			Ipv4CIDRBlock: &newComputeSubnet1CIDR,
   665  			CRN:           &newComputeSubnet1CRN,
   666  			VPC:           &vpcReferenceComputeSubnet1,
   667  			Zone:          &zoneReferenceComputeSubnet1,
   668  		},
   669  		nil,
   670  	)
   671  
   672  	// Mocks: compute subnet has no VPC.
   673  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), incompleteComputeSubnetName, region).Return(
   674  		&vpcv1.Subnet{
   675  			Name:          &incompleteComputeSubnetName,
   676  			ID:            &noVPCComputeSubnetID,
   677  			Ipv4CIDRBlock: &newComputeSubnet1CIDR,
   678  			CRN:           &newComputeSubnet1CRN,
   679  			// VPC Skipped.
   680  			Zone: &zoneReferenceComputeSubnet1,
   681  		},
   682  		nil,
   683  	)
   684  
   685  	// Mocks: compute subnet has no Zone.
   686  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), incompleteComputeSubnetName, region).Return(
   687  		&vpcv1.Subnet{
   688  			Name:          &incompleteComputeSubnetName,
   689  			ID:            &noZoneComputeSubnetID,
   690  			Ipv4CIDRBlock: &newComputeSubnet1CIDR,
   691  			CRN:           &newComputeSubnet1CRN,
   692  			VPC:           &vpcReferenceComputeSubnet1,
   693  			// Zone Skipped.
   694  		},
   695  		nil,
   696  	)
   697  
   698  	for _, tCase := range testCases {
   699  		t.Run(tCase.name, func(t *testing.T) {
   700  			metadata := baseMetadata()
   701  			metadata.client = ibmcloudClient
   702  
   703  			for _, edit := range tCase.edits {
   704  				edit(metadata)
   705  			}
   706  
   707  			actualValue, err := metadata.ComputeSubnets(context.TODO())
   708  			if err != nil {
   709  				assert.Regexp(t, tCase.errorMsg, err)
   710  			} else {
   711  				assert.Equal(t, tCase.expectedValue, actualValue)
   712  			}
   713  		})
   714  	}
   715  }
   716  
   717  func TestControlPlaneSubnets(t *testing.T) {
   718  	testCases := []struct {
   719  		name          string
   720  		edits         editMetadata
   721  		errorMsg      string
   722  		expectedValue map[string]Subnet
   723  	}{
   724  		{
   725  			name:          "no control plane subnets",
   726  			expectedValue: nil,
   727  		},
   728  		{
   729  			name: "new control plane subnets",
   730  			edits: editMetadata{
   731  				func(m *Metadata) {
   732  					m.ControlPlaneSubnetNames = []string{newControlPlaneSubnet1Name, newControlPlaneSubnet2Name}
   733  				},
   734  			},
   735  			expectedValue: newControlPlaneSubnets,
   736  		},
   737  		{
   738  			name: "new single control plane subnet",
   739  			edits: editMetadata{
   740  				func(m *Metadata) {
   741  					m.ControlPlaneSubnetNames = []string{newControlPlaneSubnet2Name}
   742  				},
   743  			},
   744  			expectedValue: map[string]Subnet{
   745  				newControlPlaneSubnet2ID: newControlPlaneSubnets[newControlPlaneSubnet2ID],
   746  			},
   747  		},
   748  		{
   749  			name: "existing control plane subnets",
   750  			edits: editMetadata{
   751  				func(m *Metadata) {
   752  					m.controlPlaneSubnets = existingControlPlaneSubnets
   753  				},
   754  			},
   755  			expectedValue: existingControlPlaneSubnets,
   756  		},
   757  		{
   758  			name: "failed getting control plane subnet",
   759  			edits: editMetadata{
   760  				func(m *Metadata) {
   761  					m.ControlPlaneSubnetNames = []string{failedGetControlPlaneSubnetName}
   762  				},
   763  			},
   764  			errorMsg: fmt.Sprintf("getting subnet %v", failedGetControlPlaneSubnetName),
   765  		},
   766  	}
   767  
   768  	// IBM Cloud Client Mocks.
   769  	mockCtrl := gomock.NewController(t)
   770  	defer mockCtrl.Finish()
   771  	ibmcloudClient := mock.NewMockAPI(mockCtrl)
   772  
   773  	// Shared Mocks.
   774  	// N/A.
   775  
   776  	// Mocks: no control plane subnets.
   777  	// N/A.
   778  
   779  	// Mocks: new control plane subnets.
   780  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), newControlPlaneSubnet1Name, region).Return(
   781  		&vpcv1.Subnet{
   782  			Name:          &newControlPlaneSubnet1Name,
   783  			ID:            &newControlPlaneSubnet1ID,
   784  			Ipv4CIDRBlock: &newControlPlaneSubnet1CIDR,
   785  			CRN:           &newControlPlaneSubnet1CRN,
   786  			VPC:           &vpcReferenceControlPlaneSubnet1,
   787  			Zone:          &zoneReferenceControlPlaneSubnet1,
   788  		},
   789  		nil,
   790  	)
   791  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), newControlPlaneSubnet2Name, region).Return(
   792  		&vpcv1.Subnet{
   793  			Name:          &newControlPlaneSubnet2Name,
   794  			ID:            &newControlPlaneSubnet2ID,
   795  			Ipv4CIDRBlock: &newControlPlaneSubnet2CIDR,
   796  			CRN:           &newControlPlaneSubnet2CRN,
   797  			VPC:           &vpcReferenceControlPlaneSubnet2,
   798  			Zone:          &zoneReferenceControlPlaneSubnet2,
   799  		},
   800  		nil,
   801  	)
   802  
   803  	// Mocks: new single control plane subnet.
   804  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), newControlPlaneSubnet2Name, region).Return(
   805  		&vpcv1.Subnet{
   806  			Name:          &newControlPlaneSubnet2Name,
   807  			ID:            &newControlPlaneSubnet2ID,
   808  			Ipv4CIDRBlock: &newControlPlaneSubnet2CIDR,
   809  			CRN:           &newControlPlaneSubnet2CRN,
   810  			VPC:           &vpcReferenceControlPlaneSubnet2,
   811  			Zone:          &zoneReferenceControlPlaneSubnet2,
   812  		},
   813  		nil,
   814  	)
   815  
   816  	// Mocks: existing control plane subnets.
   817  	// N/A.
   818  
   819  	// Mocks: failed getting control plane subnet.
   820  	ibmcloudClient.EXPECT().GetSubnetByName(gomock.Any(), failedGetControlPlaneSubnetName, region).Return(nil, &VPCResourceNotFoundError{})
   821  
   822  	for _, tCase := range testCases {
   823  		t.Run(tCase.name, func(t *testing.T) {
   824  			metadata := baseMetadata()
   825  			metadata.client = ibmcloudClient
   826  
   827  			for _, edit := range tCase.edits {
   828  				edit(metadata)
   829  			}
   830  
   831  			actualValue, err := metadata.ControlPlaneSubnets(context.TODO())
   832  			if err != nil {
   833  				assert.Regexp(t, tCase.errorMsg, err)
   834  			} else {
   835  				assert.Equal(t, tCase.expectedValue, actualValue)
   836  			}
   837  		})
   838  	}
   839  }
   840  
   841  func TestClient(t *testing.T) {
   842  	mockCtrl := gomock.NewController(t)
   843  	defer mockCtrl.Finish()
   844  	ibmcloudClient := mock.NewMockAPI(mockCtrl)
   845  
   846  	testCases := []struct {
   847  		name          string
   848  		edits         editMetadata
   849  		errorMsg      string
   850  		expectedValue API
   851  	}{
   852  		{
   853  			name: "existing client",
   854  			edits: editMetadata{
   855  				func(m *Metadata) {
   856  					m.client = ibmcloudClient
   857  				},
   858  			},
   859  			expectedValue: ibmcloudClient,
   860  		},
   861  	}
   862  
   863  	// IBM Cloud Client Mocks.
   864  	// N/A.
   865  
   866  	for _, tCase := range testCases {
   867  		t.Run(tCase.name, func(t *testing.T) {
   868  			metadata := baseMetadata()
   869  
   870  			actualValue, err := metadata.Client()
   871  			if err != nil {
   872  				assert.Regexp(t, tCase.errorMsg, err)
   873  			} else {
   874  				assert.Equal(t, tCase.expectedValue, actualValue)
   875  			}
   876  		})
   877  	}
   878  }