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

     1  package aws
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"sort"
     8  	"testing"
     9  
    10  	"github.com/aws/aws-sdk-go/service/ec2"
    11  	"github.com/aws/aws-sdk-go/service/route53"
    12  	"github.com/golang/mock/gomock"
    13  	"github.com/stretchr/testify/assert"
    14  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    15  	"k8s.io/apimachinery/pkg/util/validation/field"
    16  	"k8s.io/utils/ptr"
    17  
    18  	"github.com/openshift/installer/pkg/asset/installconfig/aws/mock"
    19  	"github.com/openshift/installer/pkg/ipnet"
    20  	"github.com/openshift/installer/pkg/types"
    21  	"github.com/openshift/installer/pkg/types/aws"
    22  )
    23  
    24  var (
    25  	validCIDR             = "10.0.0.0/16"
    26  	validRegion           = "us-east-1"
    27  	validCallerRef        = "valid-caller-reference"
    28  	validDSId             = "valid-delegation-set-id"
    29  	validNameServers      = []string{"valid-name-server"}
    30  	validHostedZoneName   = "valid-private-subnet-a"
    31  	invalidHostedZoneName = "invalid-hosted-zone"
    32  	validDomainName       = "valid-base-domain"
    33  	invalidBaseDomain     = "invalid-base-domain"
    34  	metaName              = "ClusterMetaName"
    35  
    36  	publishInternal      = func(ic *types.InstallConfig) { ic.Publish = types.InternalPublishingStrategy }
    37  	clearHostedZone      = func(ic *types.InstallConfig) { ic.AWS.HostedZone = "" }
    38  	invalidateHostedZone = func(ic *types.InstallConfig) { ic.AWS.HostedZone = invalidHostedZoneName }
    39  	invalidateBaseDomain = func(ic *types.InstallConfig) { ic.BaseDomain = invalidBaseDomain }
    40  	clearBaseDomain      = func(ic *types.InstallConfig) { ic.BaseDomain = "" }
    41  	invalidateRegion     = func(ic *types.InstallConfig) { ic.AWS.Region = "us-east4" }
    42  )
    43  
    44  type editFunctions []func(ic *types.InstallConfig)
    45  
    46  func validInstallConfig() *types.InstallConfig {
    47  	return &types.InstallConfig{
    48  		Networking: &types.Networking{
    49  			MachineNetwork: []types.MachineNetworkEntry{
    50  				{CIDR: *ipnet.MustParseCIDR(validCIDR)},
    51  			},
    52  		},
    53  		BaseDomain: validDomainName,
    54  		Publish:    types.ExternalPublishingStrategy,
    55  		Platform: types.Platform{
    56  			AWS: &aws.Platform{
    57  				Region: "us-east-1",
    58  				Subnets: []string{
    59  					"valid-private-subnet-a",
    60  					"valid-private-subnet-b",
    61  					"valid-private-subnet-c",
    62  					"valid-public-subnet-a",
    63  					"valid-public-subnet-b",
    64  					"valid-public-subnet-c",
    65  				},
    66  				HostedZone: validHostedZoneName,
    67  			},
    68  		},
    69  		ControlPlane: &types.MachinePool{
    70  			Architecture: types.ArchitectureAMD64,
    71  			Replicas:     ptr.To[int64](3),
    72  			Platform: types.MachinePoolPlatform{
    73  				AWS: &aws.MachinePool{
    74  					Zones: []string{"a", "b", "c"},
    75  				},
    76  			},
    77  		},
    78  		Compute: []types.MachinePool{{
    79  			Name:         types.MachinePoolComputeRoleName,
    80  			Architecture: types.ArchitectureAMD64,
    81  			Replicas:     ptr.To[int64](3),
    82  			Platform: types.MachinePoolPlatform{
    83  				AWS: &aws.MachinePool{
    84  					Zones: []string{"a", "b", "c"},
    85  				},
    86  			},
    87  		}},
    88  		ObjectMeta: metav1.ObjectMeta{
    89  			Name: metaName,
    90  		},
    91  	}
    92  }
    93  
    94  // validInstallConfigEdgeSubnets returns install-config for edge compute pool
    95  // for existing VPC (subnets).
    96  func validInstallConfigEdgeSubnets() *types.InstallConfig {
    97  	ic := validInstallConfig()
    98  	edgeSubnets := validEdgeSubnets()
    99  	for subnet := range edgeSubnets {
   100  		ic.Platform.AWS.Subnets = append(ic.Platform.AWS.Subnets, subnet)
   101  	}
   102  	ic.Compute = append(ic.Compute, types.MachinePool{
   103  		Name: types.MachinePoolEdgeRoleName,
   104  		Platform: types.MachinePoolPlatform{
   105  			AWS: &aws.MachinePool{},
   106  		},
   107  	})
   108  	return ic
   109  }
   110  
   111  func validAvailZones() []string {
   112  	return []string{"a", "b", "c"}
   113  }
   114  
   115  func validAvailZonesWithEdge() []string {
   116  	return []string{"a", "b", "c", "edge-a", "edge-b", "edge-c"}
   117  }
   118  
   119  func validAvailZonesOnlyEdge() []string {
   120  	return []string{"edge-a", "edge-b", "edge-c"}
   121  }
   122  
   123  func validPrivateSubnets() Subnets {
   124  	return Subnets{
   125  		"valid-private-subnet-a": {
   126  			Zone: &Zone{Name: "a"},
   127  			CIDR: "10.0.1.0/24",
   128  		},
   129  		"valid-private-subnet-b": {
   130  			Zone: &Zone{Name: "b"},
   131  			CIDR: "10.0.2.0/24",
   132  		},
   133  		"valid-private-subnet-c": {
   134  			Zone: &Zone{Name: "c"},
   135  			CIDR: "10.0.3.0/24",
   136  		},
   137  	}
   138  }
   139  
   140  func validPublicSubnets() Subnets {
   141  	return Subnets{
   142  		"valid-public-subnet-a": {
   143  			Zone: &Zone{Name: "a"},
   144  			CIDR: "10.0.4.0/24",
   145  		},
   146  		"valid-public-subnet-b": {
   147  			Zone: &Zone{Name: "b"},
   148  			CIDR: "10.0.5.0/24",
   149  		},
   150  		"valid-public-subnet-c": {
   151  			Zone: &Zone{Name: "c"},
   152  			CIDR: "10.0.6.0/24",
   153  		},
   154  	}
   155  }
   156  
   157  func validEdgeSubnets() Subnets {
   158  	return Subnets{
   159  		"valid-public-subnet-edge-a": {
   160  			Zone: &Zone{Name: "edge-a"},
   161  			CIDR: "10.0.7.0/24",
   162  		},
   163  		"valid-public-subnet-edge-b": {
   164  			Zone: &Zone{Name: "edge-b"},
   165  			CIDR: "10.0.8.0/24",
   166  		},
   167  		"valid-public-subnet-edge-c": {
   168  			Zone: &Zone{Name: "edge-c"},
   169  			CIDR: "10.0.9.0/24",
   170  		},
   171  	}
   172  }
   173  
   174  func validServiceEndpoints() []aws.ServiceEndpoint {
   175  	return []aws.ServiceEndpoint{{
   176  		Name: "ec2",
   177  		URL:  "e2e.local",
   178  	}, {
   179  		Name: "s3",
   180  		URL:  "e2e.local",
   181  	}, {
   182  		Name: "iam",
   183  		URL:  "e2e.local",
   184  	}, {
   185  		Name: "elasticloadbalancing",
   186  		URL:  "e2e.local",
   187  	}, {
   188  		Name: "tagging",
   189  		URL:  "e2e.local",
   190  	}, {
   191  		Name: "route53",
   192  		URL:  "e2e.local",
   193  	}, {
   194  		Name: "sts",
   195  		URL:  "e2e.local",
   196  	}}
   197  }
   198  
   199  func invalidServiceEndpoint() []aws.ServiceEndpoint {
   200  	return []aws.ServiceEndpoint{{
   201  		Name: "testing",
   202  		URL:  "testing",
   203  	}, {
   204  		Name: "test",
   205  		URL:  "http://testing.non",
   206  	}}
   207  }
   208  
   209  func validInstanceTypes() map[string]InstanceType {
   210  	return map[string]InstanceType{
   211  		"t2.small": {
   212  			DefaultVCpus: 1,
   213  			MemInMiB:     2048,
   214  			Arches:       []string{ec2.ArchitectureTypeX8664},
   215  		},
   216  		"m5.large": {
   217  			DefaultVCpus: 2,
   218  			MemInMiB:     8192,
   219  			Arches:       []string{ec2.ArchitectureTypeX8664},
   220  		},
   221  		"m5.xlarge": {
   222  			DefaultVCpus: 4,
   223  			MemInMiB:     16384,
   224  			Arches:       []string{ec2.ArchitectureTypeX8664},
   225  		},
   226  		"m6g.xlarge": {
   227  			DefaultVCpus: 4,
   228  			MemInMiB:     16384,
   229  			Arches:       []string{ec2.ArchitectureTypeArm64},
   230  		},
   231  	}
   232  }
   233  
   234  func createBaseDomainHostedZone() route53.HostedZone {
   235  	return route53.HostedZone{
   236  		CallerReference: &validCallerRef,
   237  		Id:              &validDSId,
   238  		Name:            &validDomainName,
   239  	}
   240  }
   241  
   242  func createValidHostedZone() route53.GetHostedZoneOutput {
   243  	ptrValidNameServers := []*string{}
   244  	for i := range validNameServers {
   245  		ptrValidNameServers = append(ptrValidNameServers, &validNameServers[i])
   246  	}
   247  
   248  	validDelegationSet := route53.DelegationSet{CallerReference: &validCallerRef, Id: &validDSId, NameServers: ptrValidNameServers}
   249  	validHostedZone := route53.HostedZone{CallerReference: &validCallerRef, Id: &validDSId, Name: &validHostedZoneName}
   250  	validVPCs := []*route53.VPC{{VPCId: &validHostedZoneName, VPCRegion: &validRegion}}
   251  
   252  	return route53.GetHostedZoneOutput{
   253  		DelegationSet: &validDelegationSet,
   254  		HostedZone:    &validHostedZone,
   255  		VPCs:          validVPCs,
   256  	}
   257  }
   258  
   259  func TestValidate(t *testing.T) {
   260  	tests := []struct {
   261  		name           string
   262  		installConfig  *types.InstallConfig
   263  		availZones     []string
   264  		edgeZones      []string
   265  		privateSubnets Subnets
   266  		publicSubnets  Subnets
   267  		edgeSubnets    Subnets
   268  		instanceTypes  map[string]InstanceType
   269  		proxy          string
   270  		expectErr      string
   271  	}{{
   272  		name: "valid no byo",
   273  		installConfig: func() *types.InstallConfig {
   274  			c := validInstallConfig()
   275  			c.Platform.AWS = &aws.Platform{Region: "us-east-1"}
   276  			return c
   277  		}(),
   278  		availZones: validAvailZones(),
   279  	}, {
   280  		name: "valid no byo",
   281  		installConfig: func() *types.InstallConfig {
   282  			c := validInstallConfig()
   283  			c.Platform.AWS.Subnets = nil
   284  			return c
   285  		}(),
   286  		availZones: validAvailZones(),
   287  	}, {
   288  		name: "valid no byo",
   289  		installConfig: func() *types.InstallConfig {
   290  			c := validInstallConfig()
   291  			c.Platform.AWS.Subnets = []string{}
   292  			return c
   293  		}(),
   294  		availZones: validAvailZones(),
   295  	}, {
   296  		name:           "valid byo",
   297  		installConfig:  validInstallConfig(),
   298  		availZones:     validAvailZones(),
   299  		privateSubnets: validPrivateSubnets(),
   300  		publicSubnets:  validPublicSubnets(),
   301  	}, {
   302  		name:           "valid byo",
   303  		installConfig:  validInstallConfigEdgeSubnets(),
   304  		availZones:     validAvailZones(),
   305  		privateSubnets: validPrivateSubnets(),
   306  		publicSubnets:  validPublicSubnets(),
   307  		edgeSubnets:    validEdgeSubnets(),
   308  	}, {
   309  		name: "valid byo",
   310  		installConfig: func() *types.InstallConfig {
   311  			c := validInstallConfig()
   312  			c.Publish = types.InternalPublishingStrategy
   313  			c.Platform.AWS.Subnets = []string{
   314  				"valid-private-subnet-a",
   315  				"valid-private-subnet-b",
   316  				"valid-private-subnet-c",
   317  			}
   318  			return c
   319  		}(),
   320  		availZones:     validAvailZones(),
   321  		privateSubnets: validPrivateSubnets(),
   322  	}, {
   323  		name: "valid instance types",
   324  		installConfig: func() *types.InstallConfig {
   325  			c := validInstallConfig()
   326  			c.Platform.AWS = &aws.Platform{
   327  				Region: "us-east-1",
   328  				DefaultMachinePlatform: &aws.MachinePool{
   329  					InstanceType: "m5.xlarge",
   330  				},
   331  			}
   332  			c.ControlPlane.Platform.AWS.InstanceType = "m5.xlarge"
   333  			c.Compute[0].Platform.AWS.InstanceType = "m5.large"
   334  			return c
   335  		}(),
   336  		availZones:    validAvailZones(),
   337  		instanceTypes: validInstanceTypes(),
   338  	}, {
   339  		name: "invalid control plane instance type",
   340  		installConfig: func() *types.InstallConfig {
   341  			c := validInstallConfig()
   342  			c.Platform.AWS = &aws.Platform{Region: "us-east-1"}
   343  			c.ControlPlane.Platform.AWS.InstanceType = "t2.small"
   344  			c.Compute[0].Platform.AWS.InstanceType = "m5.large"
   345  			return c
   346  		}(),
   347  		availZones:    validAvailZones(),
   348  		instanceTypes: validInstanceTypes(),
   349  		expectErr:     `^\Q[controlPlane.platform.aws.type: Invalid value: "t2.small": instance type does not meet minimum resource requirements of 4 vCPUs, controlPlane.platform.aws.type: Invalid value: "t2.small": instance type does not meet minimum resource requirements of 16384 MiB Memory]\E$`,
   350  	}, {
   351  		name: "invalid compute instance type",
   352  		installConfig: func() *types.InstallConfig {
   353  			c := validInstallConfig()
   354  			c.Platform.AWS = &aws.Platform{Region: "us-east-1"}
   355  			c.ControlPlane.Platform.AWS.InstanceType = "m5.xlarge"
   356  			c.Compute[0].Platform.AWS.InstanceType = "t2.small"
   357  			return c
   358  		}(),
   359  		availZones:    validAvailZones(),
   360  		instanceTypes: validInstanceTypes(),
   361  		expectErr:     `^\Q[compute[0].platform.aws.type: Invalid value: "t2.small": instance type does not meet minimum resource requirements of 2 vCPUs, compute[0].platform.aws.type: Invalid value: "t2.small": instance type does not meet minimum resource requirements of 8192 MiB Memory]\E$`,
   362  	}, {
   363  		name: "undefined compute instance type",
   364  		installConfig: func() *types.InstallConfig {
   365  			c := validInstallConfig()
   366  			c.Platform.AWS = &aws.Platform{Region: "us-east-1"}
   367  			c.Compute[0].Platform.AWS.InstanceType = "m5.dummy"
   368  			return c
   369  		}(),
   370  		availZones:    validAvailZones(),
   371  		instanceTypes: validInstanceTypes(),
   372  		expectErr:     `^\Qcompute[0].platform.aws.type: Invalid value: "m5.dummy": instance type m5.dummy not found\E$`,
   373  	}, {
   374  		name: "mismatched instance architecture",
   375  		installConfig: func() *types.InstallConfig {
   376  			c := validInstallConfig()
   377  			c.Platform.AWS = &aws.Platform{
   378  				Region:                 "us-east-1",
   379  				DefaultMachinePlatform: &aws.MachinePool{InstanceType: "m5.xlarge"},
   380  			}
   381  			c.ControlPlane.Architecture = types.ArchitectureARM64
   382  			c.Compute[0].Platform.AWS.InstanceType = "m6g.xlarge"
   383  			c.Compute[0].Architecture = types.ArchitectureAMD64
   384  			return c
   385  		}(),
   386  		availZones:    validAvailZones(),
   387  		instanceTypes: validInstanceTypes(),
   388  		expectErr:     `^\[controlPlane.platform.aws.type: Invalid value: "m5.xlarge": instance type supported architectures \[amd64\] do not match specified architecture arm64, compute\[0\].platform.aws.type: Invalid value: "m6g.xlarge": instance type supported architectures \[arm64\] do not match specified architecture amd64\]$`,
   389  	}, {
   390  		name: "invalid no private subnets",
   391  		installConfig: func() *types.InstallConfig {
   392  			c := validInstallConfig()
   393  			c.Platform.AWS.Subnets = []string{
   394  				"valid-public-subnet-a",
   395  				"valid-public-subnet-b",
   396  				"valid-public-subnet-c",
   397  			}
   398  			return c
   399  		}(),
   400  		availZones:    validAvailZones(),
   401  		publicSubnets: validPublicSubnets(),
   402  		expectErr:     `^\[platform\.aws\.subnets: Invalid value: \[\]string{\"valid-public-subnet-a\", \"valid-public-subnet-b\", \"valid-public-subnet-c\"}: No private subnets found, controlPlane\.platform\.aws\.zones: Invalid value: \[\]string{\"a\", \"b\", \"c\"}: No subnets provided for zones \[a b c\], compute\[0\]\.platform\.aws\.zones: Invalid value: \[\]string{\"a\", \"b\", \"c\"}: No subnets provided for zones \[a b c\]\]$`,
   403  	}, {
   404  		name: "invalid no public subnets",
   405  		installConfig: func() *types.InstallConfig {
   406  			c := validInstallConfig()
   407  			c.Platform.AWS.Subnets = []string{
   408  				"valid-private-subnet-a",
   409  				"valid-private-subnet-b",
   410  				"valid-private-subnet-c",
   411  			}
   412  			return c
   413  		}(),
   414  		availZones:     validAvailZones(),
   415  		privateSubnets: validPrivateSubnets(),
   416  		expectErr:      `^platform\.aws\.subnets: Invalid value: \[\]string{\"valid-private-subnet-a\", \"valid-private-subnet-b\", \"valid-private-subnet-c\"}: No public subnet provided for zones \[a b c\]$`,
   417  	}, {
   418  		name: "invalid cidr does not belong to machine CIDR",
   419  		installConfig: func() *types.InstallConfig {
   420  			c := validInstallConfig()
   421  			c.Platform.AWS.Subnets = append(c.Platform.AWS.Subnets, "invalid-cidr-subnet")
   422  			return c
   423  		}(),
   424  		availZones: func() []string {
   425  			zones := validAvailZones()
   426  			return append(zones, "zone-for-invalid-cidr-subnet")
   427  		}(),
   428  		privateSubnets: validPrivateSubnets(),
   429  		publicSubnets: func() Subnets {
   430  			s := validPublicSubnets()
   431  			s["invalid-cidr-subnet"] = Subnet{
   432  				Zone: &Zone{Name: "zone-for-invalid-cidr-subnet"},
   433  				CIDR: "192.168.126.0/24",
   434  			}
   435  			return s
   436  		}(),
   437  		expectErr: `^platform\.aws\.subnets\[6\]: Invalid value: \"invalid-cidr-subnet\": subnet's CIDR range start 192.168.126.0 is outside of the specified machine networks$`,
   438  	}, {
   439  		name: "invalid cidr does not belong to machine CIDR",
   440  		installConfig: func() *types.InstallConfig {
   441  			c := validInstallConfig()
   442  			c.Platform.AWS.Subnets = append(c.Platform.AWS.Subnets, "invalid-private-cidr-subnet", "invalid-public-cidr-subnet")
   443  			return c
   444  		}(),
   445  		availZones: func() []string {
   446  			zones := validAvailZones()
   447  			return append(zones, "zone-for-invalid-cidr-subnet")
   448  		}(),
   449  		privateSubnets: func() Subnets {
   450  			s := validPrivateSubnets()
   451  			s["invalid-private-cidr-subnet"] = Subnet{
   452  				Zone: &Zone{Name: "zone-for-invalid-cidr-subnet"},
   453  				CIDR: "192.168.126.0/24",
   454  			}
   455  			return s
   456  		}(),
   457  		publicSubnets: func() Subnets {
   458  			s := validPublicSubnets()
   459  			s["invalid-public-cidr-subnet"] = Subnet{
   460  				Zone: &Zone{Name: "zone-for-invalid-cidr-subnet"},
   461  				CIDR: "192.168.127.0/24",
   462  			}
   463  			return s
   464  		}(),
   465  		expectErr: `^\[platform\.aws\.subnets\[6\]: Invalid value: \"invalid-private-cidr-subnet\": subnet's CIDR range start 192.168.126.0 is outside of the specified machine networks, platform\.aws\.subnets\[7\]: Invalid value: \"invalid-public-cidr-subnet\": subnet's CIDR range start 192.168.127.0 is outside of the specified machine networks\]$`,
   466  	}, {
   467  		name: "invalid missing public subnet in a zone",
   468  		installConfig: func() *types.InstallConfig {
   469  			c := validInstallConfig()
   470  			c.Platform.AWS.Subnets = append(c.Platform.AWS.Subnets, "no-matching-public-private-zone")
   471  			return c
   472  		}(),
   473  		availZones: validAvailZones(),
   474  		privateSubnets: func() Subnets {
   475  			s := validPrivateSubnets()
   476  			s["no-matching-public-private-zone"] = Subnet{
   477  				Zone: &Zone{Name: "f"},
   478  				CIDR: "10.0.7.0/24",
   479  			}
   480  			return s
   481  		}(),
   482  		publicSubnets: validPublicSubnets(),
   483  		expectErr:     `^platform\.aws\.subnets: Invalid value: \[\]string{\"valid-private-subnet-a\", \"valid-private-subnet-b\", \"valid-private-subnet-c\", \"valid-public-subnet-a\", \"valid-public-subnet-b\", \"valid-public-subnet-c\", \"no-matching-public-private-zone\"}: No public subnet provided for zones \[f\]$`,
   484  	}, {
   485  		name: "invalid multiple private in same zone",
   486  		installConfig: func() *types.InstallConfig {
   487  			c := validInstallConfig()
   488  			c.Platform.AWS.Subnets = append(c.Platform.AWS.Subnets, "valid-private-zone-c-2")
   489  			return c
   490  		}(),
   491  		availZones: validAvailZones(),
   492  		privateSubnets: func() Subnets {
   493  			s := validPrivateSubnets()
   494  			s["valid-private-zone-c-2"] = Subnet{
   495  				Zone: &Zone{Name: "c"},
   496  				CIDR: "10.0.7.0/24",
   497  			}
   498  			return s
   499  		}(),
   500  		publicSubnets: validPublicSubnets(),
   501  		expectErr:     `^platform\.aws\.subnets\[6\]: Invalid value: \"valid-private-zone-c-2\": private subnet valid-private-subnet-c is also in zone c$`,
   502  	}, {
   503  		name: "invalid multiple public in same zone",
   504  		installConfig: func() *types.InstallConfig {
   505  			c := validInstallConfig()
   506  			c.Platform.AWS.Subnets = append(c.Platform.AWS.Subnets, "valid-public-zone-c-2")
   507  			return c
   508  		}(),
   509  		availZones:     validAvailZones(),
   510  		privateSubnets: validPrivateSubnets(),
   511  		publicSubnets: func() Subnets {
   512  			s := validPublicSubnets()
   513  			s["valid-public-zone-c-2"] = Subnet{
   514  				Zone: &Zone{Name: "c"},
   515  				CIDR: "10.0.7.0/24",
   516  			}
   517  			return s
   518  		}(),
   519  		expectErr: `^platform\.aws\.subnets\[6\]: Invalid value: \"valid-public-zone-c-2\": public subnet valid-public-subnet-c is also in zone c$`,
   520  	}, {
   521  		name: "invalid multiple public edge in same zone",
   522  		installConfig: func() *types.InstallConfig {
   523  			c := validInstallConfigEdgeSubnets()
   524  			c.Platform.AWS.Subnets = append(c.Platform.AWS.Subnets, "valid-public-zone-edge-c-2")
   525  			return c
   526  		}(),
   527  		availZones:     validAvailZonesWithEdge(),
   528  		privateSubnets: validPrivateSubnets(),
   529  		publicSubnets:  validPublicSubnets(),
   530  		edgeSubnets: func() Subnets {
   531  			s := validEdgeSubnets()
   532  			s["valid-public-zone-edge-c-2"] = Subnet{
   533  				Zone: &Zone{Name: "edge-c", Type: aws.LocalZoneType},
   534  				CIDR: "10.0.9.0/24",
   535  			}
   536  			return s
   537  		}(),
   538  		expectErr: `^platform\.aws\.subnets\[9\]: Invalid value: \"valid-public-zone-edge-c-2\": edge subnet valid-public-subnet-edge-c is also in zone edge-c$`,
   539  	}, {
   540  		name:           "invalid edge pool missing valid subnets",
   541  		installConfig:  validInstallConfigEdgeSubnets(),
   542  		availZones:     validAvailZonesWithEdge(),
   543  		privateSubnets: validPrivateSubnets(),
   544  		publicSubnets:  validPublicSubnets(),
   545  		edgeSubnets:    Subnets{},
   546  		expectErr:      `^compute\[1\]\.platform\.aws: Required value: the provided subnets must include valid subnets for the specified edge zones$`,
   547  	}, {
   548  		name: "invalid edge pool missing zones",
   549  		installConfig: func() *types.InstallConfig {
   550  			ic := validInstallConfig()
   551  			ic.Platform.AWS.Subnets = []string{}
   552  			ic.ControlPlane = &types.MachinePool{}
   553  			edgePool := types.MachinePool{
   554  				Name: types.MachinePoolEdgeRoleName,
   555  				Platform: types.MachinePoolPlatform{
   556  					AWS: &aws.MachinePool{},
   557  				},
   558  			}
   559  			ic.Compute = []types.MachinePool{edgePool}
   560  			return ic
   561  		}(),
   562  		expectErr: `^compute\[0\]\.platform\.aws: Required value: zone is required when using edge machine pools$`,
   563  	}, {
   564  		name: "invalid edge pool empty zones",
   565  		installConfig: func() *types.InstallConfig {
   566  			ic := validInstallConfig()
   567  			ic.Platform.AWS.Subnets = []string{}
   568  			ic.ControlPlane = &types.MachinePool{}
   569  			edgePool := types.MachinePool{
   570  				Name: types.MachinePoolEdgeRoleName,
   571  				Platform: types.MachinePoolPlatform{
   572  					AWS: &aws.MachinePool{
   573  						Zones: []string{},
   574  					},
   575  				},
   576  			}
   577  			ic.Compute = []types.MachinePool{edgePool}
   578  			return ic
   579  		}(),
   580  		expectErr: `^compute\[0\]\.platform\.aws: Required value: zone is required when using edge machine pools$`,
   581  	}, {
   582  		name: "invalid edge pool missing platform definition",
   583  		installConfig: func() *types.InstallConfig {
   584  			ic := validInstallConfig()
   585  			ic.Platform.AWS.Subnets = []string{}
   586  			ic.ControlPlane = &types.MachinePool{}
   587  			edgePool := types.MachinePool{
   588  				Name:     types.MachinePoolEdgeRoleName,
   589  				Platform: types.MachinePoolPlatform{},
   590  			}
   591  			ic.Compute = []types.MachinePool{edgePool}
   592  			return ic
   593  		}(),
   594  		expectErr: `^\[compute\[0\]\.platform\.aws: Required value: edge compute pools are only supported on the AWS platform, compute\[0\].platform.aws: Required value: zone is required when using edge machine pools\]$`,
   595  	}, {
   596  		name: "invalid edge pool missing subnets on availability zones",
   597  		installConfig: func() *types.InstallConfig {
   598  			c := validInstallConfigEdgeSubnets()
   599  			c.Platform.AWS.Subnets = []string{}
   600  			edgeSubnets := validEdgeSubnets()
   601  			for subnet := range edgeSubnets {
   602  				c.Platform.AWS.Subnets = append(c.Platform.AWS.Subnets, subnet)
   603  			}
   604  			sort.Strings(c.Platform.AWS.Subnets)
   605  			return c
   606  		}(),
   607  		availZones:     validAvailZonesOnlyEdge(),
   608  		privateSubnets: Subnets{},
   609  		publicSubnets:  Subnets{},
   610  		edgeSubnets:    validEdgeSubnets(),
   611  		expectErr:      `^\[platform\.aws\.subnets: Invalid value: \[\]string{\"valid-public-subnet-edge-a\", \"valid-public-subnet-edge-b\", \"valid-public-subnet-edge-c\"}: No private subnets found, controlPlane\.platform\.aws\.zones: Invalid value: \[\]string{\"a\", \"b\", \"c\"}: No subnets provided for zones \[a b c\], compute\[0\]\.platform\.aws\.zones: Invalid value: \[\]string{\"a\", \"b\", \"c\"}: No subnets provided for zones \[a b c\]]$`,
   612  	}, {
   613  		name: "invalid no subnet for control plane zones",
   614  		installConfig: func() *types.InstallConfig {
   615  			c := validInstallConfig()
   616  			c.ControlPlane.Platform.AWS.Zones = append(c.ControlPlane.Platform.AWS.Zones, "d")
   617  			return c
   618  		}(),
   619  		availZones:     validAvailZones(),
   620  		privateSubnets: validPrivateSubnets(),
   621  		publicSubnets:  validPublicSubnets(),
   622  		expectErr:      `^controlPlane\.platform\.aws\.zones: Invalid value: \[\]string{\"a\", \"b\", \"c\", \"d\"}: No subnets provided for zones \[d\]$`,
   623  	}, {
   624  		name: "invalid no subnet for control plane zones",
   625  		installConfig: func() *types.InstallConfig {
   626  			c := validInstallConfig()
   627  			c.ControlPlane.Platform.AWS.Zones = append(c.ControlPlane.Platform.AWS.Zones, "d", "e")
   628  			return c
   629  		}(),
   630  		availZones:     validAvailZones(),
   631  		privateSubnets: validPrivateSubnets(),
   632  		publicSubnets:  validPublicSubnets(),
   633  		expectErr:      `^controlPlane\.platform\.aws\.zones: Invalid value: \[\]string{\"a\", \"b\", \"c\", \"d\", \"e\"}: No subnets provided for zones \[d e\]$`,
   634  	}, {
   635  		name: "invalid no subnet for compute[0] zones",
   636  		installConfig: func() *types.InstallConfig {
   637  			c := validInstallConfig()
   638  			c.Compute[0].Platform.AWS.Zones = append(c.ControlPlane.Platform.AWS.Zones, "d")
   639  			return c
   640  		}(),
   641  		availZones:     validAvailZones(),
   642  		privateSubnets: validPrivateSubnets(),
   643  		publicSubnets:  validPublicSubnets(),
   644  		expectErr:      `^compute\[0\]\.platform\.aws\.zones: Invalid value: \[\]string{\"a\", \"b\", \"c\", \"d\"}: No subnets provided for zones \[d\]$`,
   645  	}, {
   646  		name: "invalid no subnet for compute zone",
   647  		installConfig: func() *types.InstallConfig {
   648  			c := validInstallConfig()
   649  			c.Compute[0].Platform.AWS.Zones = append(c.ControlPlane.Platform.AWS.Zones, "d")
   650  			c.Compute = append(c.Compute, types.MachinePool{
   651  				Architecture: types.ArchitectureAMD64,
   652  				Platform: types.MachinePoolPlatform{
   653  					AWS: &aws.MachinePool{
   654  						Zones: []string{"a", "b", "e"},
   655  					},
   656  				},
   657  			})
   658  			return c
   659  		}(),
   660  		availZones:     validAvailZones(),
   661  		privateSubnets: validPrivateSubnets(),
   662  		publicSubnets:  validPublicSubnets(),
   663  		expectErr:      `^\[compute\[0\]\.platform\.aws\.zones: Invalid value: \[\]string{\"a\", \"b\", \"c\", \"d\"}: No subnets provided for zones \[d\], compute\[1\]\.platform\.aws\.zones: Invalid value: \[\]string{\"a\", \"b\", \"e\"}: No subnets provided for zones \[e\]\]$`,
   664  	}, {
   665  		name: "custom region invalid service endpoints none provided",
   666  		installConfig: func() *types.InstallConfig {
   667  			c := validInstallConfig()
   668  			c.Platform.AWS.Region = "test-region"
   669  			c.Platform.AWS.AMIID = "dummy-id"
   670  			return c
   671  		}(),
   672  		availZones:     validAvailZones(),
   673  		privateSubnets: validPrivateSubnets(),
   674  		publicSubnets:  validPublicSubnets(),
   675  		expectErr:      `^platform\.aws\.serviceEndpoints: Invalid value: (.|\n)*: \[failed to find endpoint for service "ec2": (.|\n)*, failed to find endpoint for service "elasticloadbalancing": (.|\n)*, failed to find endpoint for service "iam": (.|\n)*, failed to find endpoint for service "route53": (.|\n)*, failed to find endpoint for service "s3": (.|\n)*, failed to find endpoint for service "sts": (.|\n)*, failed to find endpoint for service "tagging": (.|\n)*\]$`,
   676  	}, {
   677  		name: "custom region invalid service endpoints some provided",
   678  		installConfig: func() *types.InstallConfig {
   679  			c := validInstallConfig()
   680  			c.Platform.AWS.Region = "test-region"
   681  			c.Platform.AWS.AMIID = "dummy-id"
   682  			c.Platform.AWS.ServiceEndpoints = validServiceEndpoints()[:3]
   683  			return c
   684  		}(),
   685  		availZones:     validAvailZones(),
   686  		privateSubnets: validPrivateSubnets(),
   687  		publicSubnets:  validPublicSubnets(),
   688  		expectErr:      `^platform\.aws\.serviceEndpoints: Invalid value: (.|\n)*: \[failed to find endpoint for service "elasticloadbalancing": (.|\n)*, failed to find endpoint for service "route53": (.|\n)*, failed to find endpoint for service "sts": (.|\n)*, failed to find endpoint for service "tagging": (.|\n)*$`,
   689  	}, {
   690  		name: "custom region valid service endpoints",
   691  		installConfig: func() *types.InstallConfig {
   692  			c := validInstallConfig()
   693  			c.Platform.AWS.Region = "test-region"
   694  			c.Platform.AWS.AMIID = "dummy-id"
   695  			c.Platform.AWS.ServiceEndpoints = validServiceEndpoints()
   696  			return c
   697  		}(),
   698  		availZones:     validAvailZones(),
   699  		privateSubnets: validPrivateSubnets(),
   700  		publicSubnets:  validPublicSubnets(),
   701  	}, {
   702  		name: "AMI omitted for new region in standard partition",
   703  		installConfig: func() *types.InstallConfig {
   704  			c := validInstallConfig()
   705  			c.Platform.AWS.Region = "us-newregion-1"
   706  			c.Platform.AWS.ServiceEndpoints = validServiceEndpoints()
   707  			return c
   708  		}(),
   709  		availZones:     validAvailZones(),
   710  		privateSubnets: validPrivateSubnets(),
   711  		publicSubnets:  validPublicSubnets(),
   712  	}, {
   713  		name: "accept platform-level AMI",
   714  		installConfig: func() *types.InstallConfig {
   715  			c := validInstallConfig()
   716  			c.Platform.AWS.Region = "us-gov-east-1"
   717  			c.Platform.AWS.AMIID = "custom-ami"
   718  			return c
   719  		}(),
   720  		availZones:     validAvailZones(),
   721  		privateSubnets: validPrivateSubnets(),
   722  		publicSubnets:  validPublicSubnets(),
   723  	}, {
   724  		name: "accept AMI from default machine platform",
   725  		installConfig: func() *types.InstallConfig {
   726  			c := validInstallConfig()
   727  			c.Platform.AWS.Region = "us-gov-east-1"
   728  			c.Platform.AWS.DefaultMachinePlatform = &aws.MachinePool{AMIID: "custom-ami"}
   729  			return c
   730  		}(),
   731  		availZones:     validAvailZones(),
   732  		privateSubnets: validPrivateSubnets(),
   733  		publicSubnets:  validPublicSubnets(),
   734  	}, {
   735  		name: "accept AMIs specified for each machine pool",
   736  		installConfig: func() *types.InstallConfig {
   737  			c := validInstallConfig()
   738  			c.Platform.AWS.Region = "us-gov-east-1"
   739  			c.ControlPlane.Platform.AWS.AMIID = "custom-ami"
   740  			c.Compute[0].Platform.AWS.AMIID = "custom-ami"
   741  			return c
   742  		}(),
   743  		availZones:     validAvailZones(),
   744  		privateSubnets: validPrivateSubnets(),
   745  		publicSubnets:  validPublicSubnets(),
   746  	}, {
   747  		name: "AMI omitted for compute with no replicas",
   748  		installConfig: func() *types.InstallConfig {
   749  			c := validInstallConfig()
   750  			c.Platform.AWS.Region = "us-gov-east-1"
   751  			c.ControlPlane.Platform.AWS.AMIID = "custom-ami"
   752  			c.Compute[0].Replicas = ptr.To[int64](0)
   753  			return c
   754  		}(),
   755  		availZones:     validAvailZones(),
   756  		privateSubnets: validPrivateSubnets(),
   757  		publicSubnets:  validPublicSubnets(),
   758  	}, {
   759  		name: "AMI not provided for unknown region",
   760  		installConfig: func() *types.InstallConfig {
   761  			c := validInstallConfig()
   762  			c.Platform.AWS.Region = "test-region"
   763  			c.Platform.AWS.ServiceEndpoints = validServiceEndpoints()
   764  			return c
   765  		}(),
   766  		availZones:     validAvailZones(),
   767  		privateSubnets: validPrivateSubnets(),
   768  		publicSubnets:  validPublicSubnets(),
   769  		expectErr:      `^platform\.aws\.amiID: Required value: AMI must be provided$`,
   770  	}, {
   771  		name: "invalid endpoint URL",
   772  		installConfig: func() *types.InstallConfig {
   773  			c := validInstallConfig()
   774  			c.Platform.AWS.Region = "us-east-1"
   775  			c.Platform.AWS.ServiceEndpoints = invalidServiceEndpoint()
   776  			c.Platform.AWS.AMIID = "custom-ami"
   777  			return c
   778  		}(),
   779  		availZones:     validAvailZones(),
   780  		privateSubnets: validPrivateSubnets(),
   781  		publicSubnets:  validPublicSubnets(),
   782  		expectErr:      `^\Q[platform.aws.serviceEndpoints[0].url: Invalid value: "testing": Head "testing": unsupported protocol scheme "", platform.aws.serviceEndpoints[1].url: Invalid value: "http://testing.non": Head "http://testing.non": dial tcp: lookup testing.non\E.*: no such host\]$`,
   783  	}, {
   784  		name: "invalid proxy URL but valid URL",
   785  		installConfig: func() *types.InstallConfig {
   786  			c := validInstallConfig()
   787  			c.Platform.AWS.Region = "us-east-1"
   788  			c.Platform.AWS.AMIID = "custom-ami"
   789  			c.Platform.AWS.ServiceEndpoints = []aws.ServiceEndpoint{{Name: "test", URL: "http://testing.com"}}
   790  			return c
   791  		}(),
   792  		availZones:     validAvailZones(),
   793  		privateSubnets: validPrivateSubnets(),
   794  		publicSubnets:  validPublicSubnets(),
   795  		proxy:          "proxy",
   796  	}, {
   797  		name: "invalid proxy URL and invalid URL",
   798  		installConfig: func() *types.InstallConfig {
   799  			c := validInstallConfig()
   800  			c.Platform.AWS.Region = "us-east-1"
   801  			c.Platform.AWS.AMIID = "custom-ami"
   802  			c.Platform.AWS.ServiceEndpoints = []aws.ServiceEndpoint{{Name: "test", URL: "http://test"}}
   803  			return c
   804  		}(),
   805  		availZones:     validAvailZones(),
   806  		privateSubnets: validPrivateSubnets(),
   807  		publicSubnets:  validPublicSubnets(),
   808  		proxy:          "http://proxy.com",
   809  		expectErr:      `^\Qplatform.aws.serviceEndpoints[0].url: Invalid value: "http://test": Head "http://test": dial tcp: lookup test\E.*: no such host$`,
   810  	}, {
   811  		name: "invalid public ipv4 pool private installation",
   812  		installConfig: func() *types.InstallConfig {
   813  			c := validInstallConfig()
   814  			c.Publish = types.InternalPublishingStrategy
   815  			c.Platform.AWS.PublicIpv4Pool = "ipv4pool-ec2-123"
   816  			c.Platform.AWS.Subnets = []string{}
   817  			return c
   818  		}(),
   819  		availZones: validAvailZones(),
   820  		expectErr:  `^platform.aws.publicIpv4PoolId: Invalid value: "ipv4pool-ec2-123": publish strategy Internal can't be used with custom Public IPv4 Pools$`,
   821  	}}
   822  
   823  	for _, test := range tests {
   824  		t.Run(test.name, func(t *testing.T) {
   825  			meta := &Metadata{
   826  				availabilityZones: test.availZones,
   827  				privateSubnets:    test.privateSubnets,
   828  				publicSubnets:     test.publicSubnets,
   829  				edgeSubnets:       test.edgeSubnets,
   830  				instanceTypes:     test.instanceTypes,
   831  				Subnets:           test.installConfig.Platform.AWS.Subnets,
   832  			}
   833  			if test.proxy != "" {
   834  				os.Setenv("HTTP_PROXY", test.proxy)
   835  			} else {
   836  				os.Unsetenv("HTTP_PROXY")
   837  			}
   838  			err := Validate(context.TODO(), meta, test.installConfig)
   839  			if test.expectErr == "" {
   840  				assert.NoError(t, err)
   841  			} else {
   842  				if assert.Error(t, err) {
   843  					assert.Regexp(t, test.expectErr, err.Error())
   844  				}
   845  			}
   846  		})
   847  	}
   848  }
   849  
   850  func TestIsHostedZoneDomainParentOfClusterDomain(t *testing.T) {
   851  	cases := []struct {
   852  		name             string
   853  		hostedZoneDomain string
   854  		clusterDomain    string
   855  		expected         bool
   856  	}{{
   857  		name:             "same",
   858  		hostedZoneDomain: "c.b.a.",
   859  		clusterDomain:    "c.b.a.",
   860  		expected:         true,
   861  	}, {
   862  		name:             "strict parent",
   863  		hostedZoneDomain: "b.a.",
   864  		clusterDomain:    "c.b.a.",
   865  		expected:         true,
   866  	}, {
   867  		name:             "grandparent",
   868  		hostedZoneDomain: "a.",
   869  		clusterDomain:    "c.b.a.",
   870  		expected:         true,
   871  	}, {
   872  		name:             "not parent",
   873  		hostedZoneDomain: "f.e.d.",
   874  		clusterDomain:    "c.b.a.",
   875  		expected:         false,
   876  	}, {
   877  		name:             "child",
   878  		hostedZoneDomain: "d.c.b.a.",
   879  		clusterDomain:    "c.b.a.",
   880  		expected:         false,
   881  	}, {
   882  		name:             "suffix but not parent",
   883  		hostedZoneDomain: "b.a.",
   884  		clusterDomain:    "cb.a.",
   885  		expected:         false,
   886  	}}
   887  	for _, tc := range cases {
   888  		t.Run(tc.name, func(t *testing.T) {
   889  			zone := &route53.HostedZone{Name: &tc.hostedZoneDomain}
   890  			actual := isHostedZoneDomainParentOfClusterDomain(zone, tc.clusterDomain)
   891  			assert.Equal(t, tc.expected, actual)
   892  		})
   893  	}
   894  }
   895  
   896  func TestValidateForProvisioning(t *testing.T) {
   897  	cases := []struct {
   898  		name        string
   899  		edits       editFunctions
   900  		expectedErr string
   901  	}{{
   902  		// This really should test for nil, as nothing happened, but no errors were provided
   903  		name:  "internal publish strategy no hosted zone",
   904  		edits: editFunctions{publishInternal, clearHostedZone},
   905  	}, {
   906  		name:        "external publish strategy no hosted zone invalid (empty) base domain",
   907  		edits:       editFunctions{clearHostedZone, clearBaseDomain},
   908  		expectedErr: "baseDomain: Invalid value: \"\": cannot find base domain",
   909  	}, {
   910  		name:        "external publish strategy no hosted zone invalid base domain",
   911  		edits:       editFunctions{clearHostedZone, invalidateBaseDomain},
   912  		expectedErr: "baseDomain: Invalid value: \"invalid-base-domain\": cannot find base domain",
   913  	}, {
   914  		name:  "external publish strategy no hosted zone valid base domain",
   915  		edits: editFunctions{clearHostedZone},
   916  	}, {
   917  		name:  "internal publish strategy valid hosted zone",
   918  		edits: editFunctions{publishInternal},
   919  	}, {
   920  		name:        "internal publish strategy invalid hosted zone",
   921  		edits:       editFunctions{publishInternal, invalidateHostedZone},
   922  		expectedErr: "aws.hostedZone: Invalid value: \"invalid-hosted-zone\": unable to retrieve hosted zone",
   923  	}, {
   924  		name: "external publish strategy valid hosted zone",
   925  	}, {
   926  		name:        "external publish strategy invalid hosted zone",
   927  		edits:       editFunctions{invalidateHostedZone},
   928  		expectedErr: "aws.hostedZone: Invalid value: \"invalid-hosted-zone\": unable to retrieve hosted zone",
   929  	}}
   930  
   931  	mockCtrl := gomock.NewController(t)
   932  	defer mockCtrl.Finish()
   933  
   934  	route53Client := mock.NewMockAPI(mockCtrl)
   935  
   936  	validHostedZoneOutput := createValidHostedZone()
   937  	validDomainOutput := createBaseDomainHostedZone()
   938  
   939  	route53Client.EXPECT().GetBaseDomain(validDomainName).Return(&validDomainOutput, nil).AnyTimes()
   940  	route53Client.EXPECT().GetBaseDomain("").Return(nil, fmt.Errorf("invalid value: \"\": cannot find base domain")).AnyTimes()
   941  	route53Client.EXPECT().GetBaseDomain(invalidBaseDomain).Return(nil, fmt.Errorf("invalid value: \"%s\": cannot find base domain", invalidBaseDomain)).AnyTimes()
   942  
   943  	route53Client.EXPECT().ValidateZoneRecords(&validDomainOutput, gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(field.ErrorList{}).AnyTimes()
   944  	route53Client.EXPECT().ValidateZoneRecords(gomock.Any(), validHostedZoneName, gomock.Any(), gomock.Any(), gomock.Any()).Return(field.ErrorList{}).AnyTimes()
   945  
   946  	// An invalid hosted zone should provide an error
   947  	route53Client.EXPECT().GetHostedZone(validHostedZoneName, gomock.Any()).Return(&validHostedZoneOutput, nil).AnyTimes()
   948  	route53Client.EXPECT().GetHostedZone(gomock.Not(validHostedZoneName), gomock.Any()).Return(nil, fmt.Errorf("invalid value: \"invalid-hosted-zone\": cannot find hosted zone")).AnyTimes()
   949  
   950  	for _, test := range cases {
   951  		t.Run(test.name, func(t *testing.T) {
   952  			editedInstallConfig := validInstallConfig()
   953  			for _, edit := range test.edits {
   954  				edit(editedInstallConfig)
   955  			}
   956  
   957  			meta := &Metadata{
   958  				availabilityZones: validAvailZones(),
   959  				privateSubnets:    validPrivateSubnets(),
   960  				publicSubnets:     validPublicSubnets(),
   961  				instanceTypes:     validInstanceTypes(),
   962  				Region:            editedInstallConfig.AWS.Region,
   963  				vpc:               "valid-private-subnet-a",
   964  				Subnets:           editedInstallConfig.Platform.AWS.Subnets,
   965  			}
   966  
   967  			err := ValidateForProvisioning(route53Client, editedInstallConfig, meta)
   968  			if test.expectedErr == "" {
   969  				assert.NoError(t, err)
   970  			} else {
   971  				if assert.Error(t, err) {
   972  					assert.Regexp(t, test.expectedErr, err.Error())
   973  				}
   974  			}
   975  		})
   976  	}
   977  }
   978  
   979  func TestGetSubDomainDNSRecords(t *testing.T) {
   980  	cases := []struct {
   981  		name               string
   982  		baseDomain         string
   983  		problematicRecords []string
   984  		expectedErr        string
   985  	}{{
   986  		name:        "empty cluster domain",
   987  		expectedErr: fmt.Sprintf("hosted zone domain %s is not a parent of the cluster domain %s", validDomainName, ""),
   988  	}, {
   989  		name:        "period cluster domain",
   990  		baseDomain:  ".",
   991  		expectedErr: fmt.Sprintf("hosted zone domain %s is not a parent of the cluster domain %s", validDomainName, "."),
   992  	}, {
   993  		name:       "valid dns record no problems",
   994  		baseDomain: validDomainName + ".",
   995  	}, {
   996  		name:               "valid dns record with problems",
   997  		baseDomain:         validDomainName,
   998  		problematicRecords: []string{"test1.ClusterMetaName.valid-base-domain."},
   999  	}, {
  1000  		name:               "valid dns record with skipped problems",
  1001  		baseDomain:         validDomainName,
  1002  		problematicRecords: []string{"test1.ClusterMetaName.valid-base-domain.", "ClusterMetaName.xxxxx-xxxx-xxxxxx."},
  1003  	},
  1004  	}
  1005  
  1006  	validDomainOutput := createBaseDomainHostedZone()
  1007  
  1008  	mockCtrl := gomock.NewController(t)
  1009  	defer mockCtrl.Finish()
  1010  	route53Client := mock.NewMockAPI(mockCtrl)
  1011  
  1012  	for _, test := range cases {
  1013  
  1014  		t.Run(test.name, func(t *testing.T) {
  1015  
  1016  			ic := validInstallConfig()
  1017  			ic.BaseDomain = test.baseDomain
  1018  
  1019  			if test.expectedErr != "" {
  1020  				if test.problematicRecords == nil {
  1021  					route53Client.EXPECT().GetSubDomainDNSRecords(&validDomainOutput, ic, gomock.Any()).Return(nil, fmt.Errorf(test.expectedErr)).AnyTimes()
  1022  				} else {
  1023  					// mimic the results of what should happen in the internal function passed to
  1024  					// ListResourceRecordSetsPages by GetSubDomainDNSRecords. Skip certain problematicRecords
  1025  					returnedProblems := make([]string, 0, len(test.problematicRecords))
  1026  					expectedName := ic.ClusterDomain() + "."
  1027  					for _, pr := range test.problematicRecords {
  1028  						if len(pr) != len(expectedName) {
  1029  							returnedProblems = append(returnedProblems, pr)
  1030  						}
  1031  					}
  1032  					route53Client.EXPECT().GetSubDomainDNSRecords(&validDomainOutput, ic, gomock.Any()).Return(returnedProblems, fmt.Errorf(test.expectedErr)).AnyTimes()
  1033  				}
  1034  			} else {
  1035  				route53Client.EXPECT().GetSubDomainDNSRecords(&validDomainOutput, ic, gomock.Any()).Return(nil, nil).AnyTimes()
  1036  			}
  1037  
  1038  			_, err := route53Client.GetSubDomainDNSRecords(&validDomainOutput, ic, nil)
  1039  			if test.expectedErr == "" {
  1040  				assert.NoError(t, err)
  1041  			} else {
  1042  				if assert.Error(t, err) {
  1043  					assert.Regexp(t, test.expectedErr, err.Error())
  1044  				}
  1045  			}
  1046  		})
  1047  	}
  1048  }
  1049  
  1050  func TestSkipRecords(t *testing.T) {
  1051  	cases := []struct {
  1052  		name           string
  1053  		recordName     string
  1054  		expectedResult bool
  1055  	}{{
  1056  		name:           "record not part of cluster",
  1057  		recordName:     fmt.Sprintf("%s.test.domain.", metaName),
  1058  		expectedResult: true,
  1059  	}, {
  1060  		name:           "record and cluster domain are same",
  1061  		recordName:     fmt.Sprintf("%s.%s.", metaName, validDomainName),
  1062  		expectedResult: true,
  1063  	}, {
  1064  		name: "record not part of cluster bad suffix",
  1065  		// The parent below does not have a dot following it on purpose - do not Remove
  1066  		recordName:     fmt.Sprintf("parent%s.%s.", metaName, validDomainName),
  1067  		expectedResult: true,
  1068  	}, {
  1069  		name: "record part of cluster bad suffix",
  1070  		// The parent below does not have a dot following it on purpose - do not Remove
  1071  		recordName:     fmt.Sprintf("parent.%s.%s.", metaName, validDomainName),
  1072  		expectedResult: false,
  1073  	},
  1074  	}
  1075  
  1076  	// create the dottedClusterDomain in the same manner that it will be used in GetSubDomainDNSRecords
  1077  	ic := validInstallConfig()
  1078  	ic.BaseDomain = validDomainName
  1079  	dottedClusterDomain := ic.ClusterDomain() + "."
  1080  
  1081  	for _, test := range cases {
  1082  		t.Run(test.name, func(t *testing.T) {
  1083  			assert.Equal(t, test.expectedResult, skipRecord(test.recordName, dottedClusterDomain))
  1084  		})
  1085  	}
  1086  }