github.com/openshift/installer@v1.4.17/pkg/types/validation/installconfig_test.go (about) 1 package validation 2 3 import ( 4 "fmt" 5 "net" 6 "testing" 7 8 "github.com/stretchr/testify/assert" 9 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 "k8s.io/apimachinery/pkg/util/validation/field" 11 "k8s.io/utils/pointer" 12 utilsslice "k8s.io/utils/strings/slices" 13 14 configv1 "github.com/openshift/api/config/v1" 15 operv1 "github.com/openshift/api/operator/v1" 16 "github.com/openshift/installer/pkg/ipnet" 17 "github.com/openshift/installer/pkg/types" 18 "github.com/openshift/installer/pkg/types/aws" 19 "github.com/openshift/installer/pkg/types/azure" 20 "github.com/openshift/installer/pkg/types/baremetal" 21 "github.com/openshift/installer/pkg/types/external" 22 "github.com/openshift/installer/pkg/types/gcp" 23 "github.com/openshift/installer/pkg/types/ibmcloud" 24 "github.com/openshift/installer/pkg/types/none" 25 "github.com/openshift/installer/pkg/types/nutanix" 26 "github.com/openshift/installer/pkg/types/openstack" 27 "github.com/openshift/installer/pkg/types/powervs" 28 "github.com/openshift/installer/pkg/types/vsphere" 29 ) 30 31 const TechPreviewNoUpgrade = "TechPreviewNoUpgrade" 32 33 func validInstallConfig() *types.InstallConfig { 34 return &types.InstallConfig{ 35 TypeMeta: metav1.TypeMeta{ 36 APIVersion: types.InstallConfigVersion, 37 }, 38 ObjectMeta: metav1.ObjectMeta{ 39 Name: "test-cluster", 40 }, 41 BaseDomain: "test-domain", 42 Networking: validIPv4NetworkingConfig(), 43 ControlPlane: validMachinePool("master"), 44 Compute: []types.MachinePool{*validMachinePool("worker")}, 45 Platform: types.Platform{ 46 AWS: validAWSPlatform(), 47 }, 48 PullSecret: `{"auths":{"example.com":{"auth":"authorization value"}}}`, 49 Publish: types.ExternalPublishingStrategy, 50 Proxy: &types.Proxy{ 51 HTTPProxy: "http://user:password@127.0.0.1:8080", 52 HTTPSProxy: "https://user:password@127.0.0.1:8080", 53 NoProxy: "valid-proxy.com,172.30.0.0/16", 54 }, 55 } 56 } 57 58 func validAWSPlatform() *aws.Platform { 59 return &aws.Platform{ 60 Region: "us-east-1", 61 } 62 } 63 64 func validAzureStackPlatform() *azure.Platform { 65 return &azure.Platform{ 66 Region: "test-region", 67 ARMEndpoint: "http://test-endpoint.com", 68 BaseDomainResourceGroupName: "test-basedomain-rg", 69 CloudName: azure.StackCloud, 70 OutboundType: "Loadbalancer", 71 } 72 } 73 74 func validGCPPlatform() *gcp.Platform { 75 return &gcp.Platform{ 76 ProjectID: "myProject", 77 Region: "us-east1", 78 } 79 } 80 81 func validIBMCloudPlatform() *ibmcloud.Platform { 82 return &ibmcloud.Platform{ 83 Region: "us-south", 84 } 85 } 86 87 func validSSHKey() string { 88 return "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQD1+D0ns3LYRPeFK2nqOtVKBGueBQGdBLre5A+afvjaIj/QgtJuwv3rb6Uso8GMPbFlj693/b9BcV0TGxa5lC8cAGKrpxKUPvZ0WLRFLMP5HKBFf6+N4SQR9NKi7Liw8Km1GW9l+s/gMFz/ypANTg8PqvR4yglW+6jJEuKdCy/q14s9kEn4czifBzqiBw60gUiDdWbawl8yF+TxiqeKTCfw4HTeY6j1vui0ROuN2XAWgdH999rNAr1QY8BPMTjQJ5X7jeFgagq7u+snXgWycoDsn4fZP1XL91nQXLdZZgJ3T/qtjUbQt4wUuiqCu4cyN8KRoFQBtX9X7TKU8aH/Kkf+t67zS/SE0ZgvCkNr+iaqYVyHpmBoLh3AaWUYJ2bQ7fx9FvEGLcDYNkwqBED6VwuqB7nw+zGYVouGLs+2UKjfc+A1BOP0Q/2ACEkt1u5iLA+dfEC5nMMThIMNgXpjpsYLsGDKV+e9fEzrTphYtYs/XKaYlG634kGMk7wdgsHoTL0= localhost" 89 } 90 91 func validPowerVSPlatform() *powervs.Platform { 92 return &powervs.Platform{ 93 Zone: "dal10", 94 } 95 } 96 97 func validVSpherePlatform() *vsphere.Platform { 98 return &vsphere.Platform{ 99 VCenters: []vsphere.VCenter{ 100 { 101 Server: "test-vcenter", 102 Port: 443, 103 Username: "test-username", 104 Password: "test-password", 105 Datacenters: []string{ 106 "test-datacenter", 107 }, 108 }, 109 }, 110 FailureDomains: []vsphere.FailureDomain{ 111 { 112 Name: "test-east-1a", 113 Region: "test-east", 114 Zone: "test-east-1a", 115 Server: "test-vcenter", 116 Topology: vsphere.Topology{ 117 Datacenter: "test-datacenter", 118 ComputeCluster: "/test-datacenter/host/test-cluster", 119 Datastore: "/test-datacenter/datastore/test-datastore", 120 Networks: []string{"test-portgroup"}, 121 ResourcePool: "/test-datacenter/host/test-cluster/Resources/test-resourcepool", 122 Folder: "/test-datacenter/vm/test-folder", 123 }, 124 }, 125 { 126 Name: "test-east-2a", 127 Region: "test-east", 128 Zone: "test-east-2a", 129 Server: "test-vcenter", 130 Topology: vsphere.Topology{ 131 Datacenter: "test-datacenter", 132 ComputeCluster: "/test-datacenter/host/test-cluster", 133 Datastore: "/test-datacenter/datastore/test-datastore", 134 Networks: []string{"test-portgroup"}, 135 Folder: "/test-datacenter/vm/test-folder", 136 }, 137 }, 138 }, 139 } 140 } 141 142 func validBareMetalPlatform() *baremetal.Platform { 143 iface, _ := net.Interfaces() 144 return &baremetal.Platform{ 145 LibvirtURI: "qemu+tcp://192.168.122.1/system", 146 ProvisioningNetworkInterface: "ens3", 147 ProvisioningNetworkCIDR: ipnet.MustParseCIDR("192.168.111.0/24"), 148 BootstrapProvisioningIP: "192.168.111.1", 149 ClusterProvisioningIP: "192.168.111.2", 150 ProvisioningNetwork: baremetal.ManagedProvisioningNetwork, 151 Hosts: []*baremetal.Host{ 152 { 153 Name: "host1", 154 Role: "master", 155 BootMACAddress: "CA:FE:CA:FE:00:00", 156 BMC: baremetal.BMC{ 157 Username: "root", 158 Password: "password", 159 Address: "ipmi://192.168.111.1", 160 }, 161 }, 162 { 163 Name: "host2", 164 Role: "worker", 165 BootMACAddress: "CA:FE:CA:FE:00:01", 166 BMC: baremetal.BMC{ 167 Username: "root", 168 Password: "password", 169 Address: "ipmi://192.168.111.2", 170 }, 171 }, 172 }, 173 ExternalBridge: iface[0].Name, 174 ProvisioningBridge: iface[0].Name, 175 DefaultMachinePlatform: &baremetal.MachinePool{}, 176 APIVIPs: []string{"10.0.0.5"}, 177 IngressVIPs: []string{"10.0.0.4"}, 178 } 179 } 180 181 func validOpenStackPlatform() *openstack.Platform { 182 return &openstack.Platform{ 183 Cloud: "test-cloud", 184 ExternalNetwork: "test-network", 185 DefaultMachinePlatform: &openstack.MachinePool{ 186 FlavorName: "test-flavor", 187 }, 188 APIVIPs: []string{"10.0.0.5"}, 189 IngressVIPs: []string{"10.0.0.4"}, 190 } 191 } 192 193 func validNutanixPlatform() *nutanix.Platform { 194 return &nutanix.Platform{ 195 PrismCentral: nutanix.PrismCentral{ 196 Endpoint: nutanix.PrismEndpoint{Address: "test-pc", Port: 8080}, 197 Username: "test-username-pc", 198 Password: "test-password-pc", 199 }, 200 PrismElements: []nutanix.PrismElement{{ 201 UUID: "test-pe-uuid", 202 Endpoint: nutanix.PrismEndpoint{Address: "test-pe", Port: 8081}, 203 }}, 204 SubnetUUIDs: []string{"test-subnet"}, 205 } 206 } 207 208 func validIPv4NetworkingConfig() *types.Networking { 209 return &types.Networking{ 210 NetworkType: "OVNKubernetes", 211 MachineNetwork: []types.MachineNetworkEntry{ 212 { 213 CIDR: *ipnet.MustParseCIDR("10.0.0.0/16"), 214 }, 215 }, 216 ServiceNetwork: []ipnet.IPNet{ 217 *ipnet.MustParseCIDR("172.30.0.0/16"), 218 }, 219 ClusterNetwork: []types.ClusterNetworkEntry{ 220 { 221 CIDR: *ipnet.MustParseCIDR("192.168.1.0/24"), 222 HostPrefix: 28, 223 }, 224 }, 225 ClusterNetworkMTU: 0, 226 } 227 } 228 229 func validIPv6NetworkingConfig() *types.Networking { 230 return &types.Networking{ 231 NetworkType: "OVNKubernetes", 232 MachineNetwork: []types.MachineNetworkEntry{ 233 { 234 CIDR: *ipnet.MustParseCIDR("ffd0::/48"), 235 }, 236 }, 237 ServiceNetwork: []ipnet.IPNet{ 238 *ipnet.MustParseCIDR("ffd1::/112"), 239 }, 240 ClusterNetwork: []types.ClusterNetworkEntry{ 241 { 242 CIDR: *ipnet.MustParseCIDR("ffd2::/48"), 243 HostPrefix: 64, 244 }, 245 }, 246 } 247 } 248 249 func validDualStackNetworkingConfig() *types.Networking { 250 return &types.Networking{ 251 NetworkType: "OVNKubernetes", 252 MachineNetwork: []types.MachineNetworkEntry{ 253 { 254 CIDR: *ipnet.MustParseCIDR("10.0.0.0/16"), 255 }, 256 { 257 CIDR: *ipnet.MustParseCIDR("ffd0::/48"), 258 }, 259 }, 260 ServiceNetwork: []ipnet.IPNet{ 261 *ipnet.MustParseCIDR("172.30.0.0/16"), 262 *ipnet.MustParseCIDR("ffd1::/112"), 263 }, 264 ClusterNetwork: []types.ClusterNetworkEntry{ 265 { 266 CIDR: *ipnet.MustParseCIDR("192.168.1.0/24"), 267 HostPrefix: 28, 268 }, 269 { 270 CIDR: *ipnet.MustParseCIDR("ffd2::/48"), 271 HostPrefix: 64, 272 }, 273 }, 274 } 275 } 276 277 func TestValidateInstallConfig(t *testing.T) { 278 cases := []struct { 279 name string 280 installConfig *types.InstallConfig 281 expectedError string 282 }{ 283 { 284 name: "minimal", 285 installConfig: validInstallConfig(), 286 }, 287 { 288 name: "invalid version", 289 installConfig: func() *types.InstallConfig { 290 c := validInstallConfig() 291 c.APIVersion = "bad-version" 292 return c 293 }(), 294 expectedError: fmt.Sprintf(`^apiVersion: Invalid value: "bad-version": install-config version must be %q`, types.InstallConfigVersion), 295 }, 296 { 297 name: "invalid name", 298 installConfig: func() *types.InstallConfig { 299 c := validInstallConfig() 300 c.ObjectMeta.Name = "bad-name-" 301 return c 302 }(), 303 expectedError: `^metadata.name: Invalid value: "bad-name-": a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '\.', and must start and end with an alphanumeric character \(e\.g\. 'example\.com', regex used for validation is '\[a-z0-9]\(\[-a-z0-9]\*\[a-z0-9]\)\?\(\\\.\[a-z0-9]\(\[-a-z0-9]\*\[a-z0-9]\)\?\)\*'\)$`, 304 }, 305 { 306 name: "invalid ssh key", 307 installConfig: func() *types.InstallConfig { 308 c := validInstallConfig() 309 c.SSHKey = "bad-ssh-key" 310 return c 311 }(), 312 expectedError: `^sshKey: Invalid value: "bad-ssh-key": ssh: no key found$`, 313 }, 314 { 315 name: "invalid base domain", 316 installConfig: func() *types.InstallConfig { 317 c := validInstallConfig() 318 c.BaseDomain = ".bad-domain." 319 return c 320 }(), 321 expectedError: `^baseDomain: Invalid value: "\.bad-domain\.": a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '\.', and must start and end with an alphanumeric character \(e\.g\. 'example\.com', regex used for validation is '\[a-z0-9]\(\[-a-z0-9]\*\[a-z0-9]\)\?\(\\\.\[a-z0-9]\(\[-a-z0-9]\*\[a-z0-9]\)\?\)\*'\)$`, 322 }, 323 { 324 name: "overly long cluster domain", 325 installConfig: func() *types.InstallConfig { 326 c := validInstallConfig() 327 c.ObjectMeta.Name = fmt.Sprintf("test-cluster%042d", 0) 328 c.BaseDomain = fmt.Sprintf("test-domain%056d.a%060d.b%060d.c%060d", 0, 0, 0, 0) 329 return c 330 }(), 331 expectedError: `^baseDomain: Invalid value: "` + fmt.Sprintf("test-cluster%042d.test-domain%056d.a%060d.b%060d.c%060d", 0, 0, 0, 0, 0) + `": must be no more than 253 characters$`, 332 }, 333 { 334 name: "missing networking", 335 installConfig: func() *types.InstallConfig { 336 c := validInstallConfig() 337 c.Networking = nil 338 return c 339 }(), 340 expectedError: `^networking: Required value: networking is required$`, 341 }, 342 { 343 name: "invalid network type", 344 installConfig: func() *types.InstallConfig { 345 c := validInstallConfig() 346 c.Networking.NetworkType = "" 347 return c 348 }(), 349 expectedError: `^networking.networkType: Required value: network provider type required$`, 350 }, 351 { 352 name: "missing service network", 353 installConfig: func() *types.InstallConfig { 354 c := validInstallConfig() 355 c.Networking.ServiceNetwork = nil 356 return c 357 }(), 358 expectedError: `^networking\.serviceNetwork: Required value: a service network is required$`, 359 }, 360 { 361 name: "invalid service network", 362 installConfig: func() *types.InstallConfig { 363 c := validInstallConfig() 364 c.Networking.ServiceNetwork[0] = *ipnet.MustParseCIDR("13.0.128.0/16") 365 return c 366 }(), 367 expectedError: `^networking\.serviceNetwork\[0\]: Invalid value: "13\.0\.128\.0/16": invalid network address. got 13\.0\.128\.0/16, expecting 13\.0\.0\.0/16$`, 368 }, 369 { 370 name: "overlapping service network and machine cidr", 371 installConfig: func() *types.InstallConfig { 372 c := validInstallConfig() 373 c.Networking.ServiceNetwork[0] = *ipnet.MustParseCIDR("10.0.2.0/24") 374 return c 375 }(), 376 expectedError: `^networking\.serviceNetwork\[0\]: Invalid value: "10\.0\.2\.0/24": service network must not overlap with any of the machine networks$`, 377 }, 378 { 379 name: "overlapping machine network and machine network", 380 installConfig: func() *types.InstallConfig { 381 c := validInstallConfig() 382 c.Networking.MachineNetwork = []types.MachineNetworkEntry{ 383 {CIDR: *ipnet.MustParseCIDR("13.0.0.0/16")}, 384 {CIDR: *ipnet.MustParseCIDR("13.0.2.0/24")}, 385 } 386 387 return c 388 }(), 389 // also triggers the only-one-machine-network validation 390 expectedError: `^networking\.machineNetwork\[1\]: Invalid value: "13\.0\.2\.0/24": machine network must not overlap with machine network 0$`, 391 }, 392 { 393 name: "overlapping service network and service network", 394 installConfig: func() *types.InstallConfig { 395 c := validInstallConfig() 396 c.Networking.ServiceNetwork = []ipnet.IPNet{ 397 *ipnet.MustParseCIDR("13.0.0.0/16"), 398 *ipnet.MustParseCIDR("13.0.2.0/24"), 399 } 400 401 return c 402 }(), 403 // also triggers the only-one-service-network validation 404 expectedError: `^\[networking\.serviceNetwork\[1\]: Invalid value: "13\.0\.2\.0/24": service network must not overlap with service network 0, networking\.serviceNetwork: Invalid value: "13\.0\.0\.0/16, 13\.0\.2\.0/24": only one service network can be specified]$`, 405 }, 406 { 407 name: "missing machine networks", 408 installConfig: func() *types.InstallConfig { 409 c := validInstallConfig() 410 c.Networking.MachineNetwork = nil 411 return c 412 }(), 413 expectedError: `^networking\.machineNetwork: Required value: at least one machine network is required$`, 414 }, 415 { 416 name: "invalid machine cidr", 417 installConfig: func() *types.InstallConfig { 418 c := validInstallConfig() 419 c.Networking.MachineNetwork = []types.MachineNetworkEntry{{CIDR: *ipnet.MustParseCIDR("11.0.128.0/16")}} 420 return c 421 }(), 422 expectedError: `^networking\.machineNetwork\[0\]: Invalid value: "11\.0\.128\.0/16": invalid network address. got 11\.0\.128\.0/16, expecting 11\.0\.0\.0/16$`, 423 }, 424 { 425 name: "invalid cluster network", 426 installConfig: func() *types.InstallConfig { 427 c := validInstallConfig() 428 c.Networking.ClusterNetwork = []types.ClusterNetworkEntry{{CIDR: *ipnet.MustParseCIDR("12.0.128.0/16"), HostPrefix: 23}} 429 return c 430 }(), 431 expectedError: `^networking\.clusterNetwork\[0]\.cidr: Invalid value: "12\.0\.128\.0/16": invalid network address. got 12\.0\.128\.0/16, expecting 12\.0\.0\.0/16$`, 432 }, 433 { 434 name: "overlapping cluster network and machine cidr", 435 installConfig: func() *types.InstallConfig { 436 c := validInstallConfig() 437 c.Networking.ClusterNetwork[0].CIDR = *ipnet.MustParseCIDR("10.0.3.0/24") 438 return c 439 }(), 440 expectedError: `^networking\.clusterNetwork\[0]\.cidr: Invalid value: "10\.0\.3\.0/24": cluster network must not overlap with any of the machine networks$`, 441 }, 442 { 443 name: "overlapping cluster network and service network", 444 installConfig: func() *types.InstallConfig { 445 c := validInstallConfig() 446 c.Networking.ClusterNetwork[0].CIDR = *ipnet.MustParseCIDR("172.30.2.0/24") 447 return c 448 }(), 449 expectedError: `^networking\.clusterNetwork\[0]\.cidr: Invalid value: "172\.30\.2\.0/24": cluster network must not overlap with service network 0$`, 450 }, 451 { 452 name: "overlapping cluster network and cluster network", 453 installConfig: func() *types.InstallConfig { 454 c := validInstallConfig() 455 c.Networking.ClusterNetwork = []types.ClusterNetworkEntry{ 456 {CIDR: *ipnet.MustParseCIDR("12.0.0.0/16"), HostPrefix: 23}, 457 {CIDR: *ipnet.MustParseCIDR("12.0.3.0/24"), HostPrefix: 25}, 458 } 459 return c 460 }(), 461 expectedError: `^networking\.clusterNetwork\[1]\.cidr: Invalid value: "12\.0\.3\.0/24": cluster network must not overlap with cluster network 0$`, 462 }, 463 { 464 name: "cluster network host prefix too large", 465 installConfig: func() *types.InstallConfig { 466 c := validInstallConfig() 467 c.Networking.ClusterNetwork[0].CIDR = *ipnet.MustParseCIDR("192.168.1.0/24") 468 c.Networking.ClusterNetwork[0].HostPrefix = 23 469 return c 470 }(), 471 expectedError: `^networking\.clusterNetwork\[0]\.hostPrefix: Invalid value: 23: cluster network host subnetwork prefix must not be larger size than CIDR 192.168.1.0/24$`, 472 }, 473 { 474 name: "cluster network host prefix unset", 475 installConfig: func() *types.InstallConfig { 476 c := validInstallConfig() 477 c.Networking.NetworkType = "OVNKubernetes" 478 c.Networking.ClusterNetwork[0].CIDR = *ipnet.MustParseCIDR("192.168.1.0/24") 479 c.Networking.ClusterNetwork[0].HostPrefix = 0 480 return c 481 }(), 482 expectedError: `^networking\.clusterNetwork\[0]\.hostPrefix: Invalid value: 0: cluster network host subnetwork prefix must not be larger size than CIDR 192.168.1.0/24$`, 483 }, 484 { 485 name: "cluster network host prefix unset ignored", 486 installConfig: func() *types.InstallConfig { 487 c := validInstallConfig() 488 c.Networking.NetworkType = "HostPrefixNotRequiredPlugin" 489 c.Networking.ClusterNetwork[0].CIDR = *ipnet.MustParseCIDR("192.168.1.0/24") 490 return c 491 }(), 492 expectedError: ``, 493 }, 494 { 495 name: "networking clusterNetworkMTU - valid high limit ovn", 496 installConfig: func() *types.InstallConfig { 497 c := validInstallConfig() 498 c.Networking.NetworkType = string(operv1.NetworkTypeOVNKubernetes) 499 c.Networking.ClusterNetworkMTU = 8901 500 fmt.Println(c.Platform.Name()) 501 return c 502 }(), 503 }, 504 { 505 name: "networking clusterNetworkMTU - valid low limit", 506 installConfig: func() *types.InstallConfig { 507 c := validInstallConfig() 508 c.Networking.NetworkType = string(operv1.NetworkTypeOVNKubernetes) 509 c.Networking.ClusterNetworkMTU = 1000 510 return c 511 }(), 512 }, 513 { 514 name: "networking clusterNetworkMTU - invalid value lower", 515 installConfig: func() *types.InstallConfig { 516 c := validInstallConfig() 517 c.Networking.ClusterNetworkMTU = 999 518 return c 519 }(), 520 expectedError: `^networking\.clusterNetworkMTU: Invalid value: 999: cluster network MTU is lower than the minimum value of 1000$`, 521 }, 522 { 523 name: "networking clusterNetworkMTU - invalid value ovn", 524 installConfig: func() *types.InstallConfig { 525 c := validInstallConfig() 526 c.Networking.NetworkType = string(operv1.NetworkTypeOVNKubernetes) 527 c.Networking.ClusterNetworkMTU = 8951 528 return c 529 }(), 530 expectedError: `^networking\.clusterNetworkMTU: Invalid value: 8951: cluster network MTU exceeds the maximum value with the network plugin OVNKubernetes of 8901$`, 531 }, 532 { 533 name: "networking clusterNetworkMTU - invalid jumbo value", 534 installConfig: func() *types.InstallConfig { 535 c := validInstallConfig() 536 c.Networking.ClusterNetworkMTU = 9002 537 return c 538 }(), 539 expectedError: `^networking\.clusterNetworkMTU: Invalid value: 9002: cluster network MTU exceeds the maximum value of 9001$`, 540 }, 541 { 542 name: "networking clusterNetworkMTU - invalid for non-aws", 543 installConfig: func() *types.InstallConfig { 544 c := validInstallConfig() 545 c.Networking.NetworkType = string(operv1.NetworkTypeOVNKubernetes) 546 c.Networking.ClusterNetworkMTU = 8901 547 c.Platform = types.Platform{ 548 None: &none.Platform{}, 549 } 550 return c 551 }(), 552 expectedError: `^networking\.clusterNetworkMTU: Invalid value: 8901: cluster network MTU is allowed only in AWS deployments`, 553 }, 554 { 555 name: "networking clusterNetworkMTU - unsupported network type", 556 installConfig: func() *types.InstallConfig { 557 c := validInstallConfig() 558 c.Networking.NetworkType = string(operv1.NetworkTypeOpenShiftSDN) 559 c.Networking.ClusterNetworkMTU = 8000 560 return c 561 }(), 562 expectedError: `networking.networkType: Invalid value: "OpenShiftSDN": networkType OpenShiftSDN is not supported, please use OVNKubernetes, networking.clusterNetworkMTU: Invalid value: 8000: cluster network MTU is not valid with network plugin OpenShiftSDN`, 563 }, 564 { 565 name: "missing control plane", 566 installConfig: func() *types.InstallConfig { 567 c := validInstallConfig() 568 c.ControlPlane = nil 569 return c 570 }(), 571 expectedError: `^controlPlane: Required value: controlPlane is required$`, 572 }, 573 { 574 name: "control plane with 0 replicas", 575 installConfig: func() *types.InstallConfig { 576 c := validInstallConfig() 577 c.ControlPlane.Replicas = pointer.Int64Ptr(0) 578 return c 579 }(), 580 expectedError: `^controlPlane.replicas: Invalid value: 0: number of control plane replicas must be positive$`, 581 }, 582 { 583 name: "invalid control plane", 584 installConfig: func() *types.InstallConfig { 585 c := validInstallConfig() 586 c.ControlPlane.Replicas = nil 587 return c 588 }(), 589 expectedError: `^controlPlane.replicas: Required value: replicas is required$`, 590 }, 591 { 592 name: "missing compute", 593 installConfig: func() *types.InstallConfig { 594 c := validInstallConfig() 595 c.Compute = nil 596 return c 597 }(), 598 }, 599 { 600 name: "empty compute", 601 installConfig: func() *types.InstallConfig { 602 c := validInstallConfig() 603 c.Compute = []types.MachinePool{} 604 return c 605 }(), 606 }, 607 { 608 name: "duplicate compute", 609 installConfig: func() *types.InstallConfig { 610 c := validInstallConfig() 611 c.Compute = []types.MachinePool{ 612 *validMachinePool("worker"), 613 *validMachinePool("worker"), 614 } 615 return c 616 }(), 617 expectedError: `^compute\[1\]\.name: Duplicate value: "worker"$`, 618 }, 619 { 620 name: "no compute replicas", 621 installConfig: func() *types.InstallConfig { 622 c := validInstallConfig() 623 c.Compute = []types.MachinePool{ 624 func() types.MachinePool { 625 p := *validMachinePool("worker") 626 p.Replicas = pointer.Int64Ptr(0) 627 return p 628 }(), 629 } 630 return c 631 }(), 632 }, 633 { 634 name: "missing platform", 635 installConfig: func() *types.InstallConfig { 636 c := validInstallConfig() 637 c.Platform = types.Platform{} 638 return c 639 }(), 640 expectedError: `^platform: Invalid value: "": must specify one of the platforms \(aws, azure, baremetal, external, gcp, ibmcloud, none, nutanix, openstack, powervs, vsphere\)$`, 641 }, 642 { 643 name: "multiple platforms", 644 installConfig: func() *types.InstallConfig { 645 c := validInstallConfig() 646 c.Platform.IBMCloud = validIBMCloudPlatform() 647 return c 648 }(), 649 expectedError: `^platform: Invalid value: "aws": must only specify a single type of platform; cannot use both "aws" and "ibmcloud"$`, 650 }, 651 { 652 name: "invalid aws platform", 653 installConfig: func() *types.InstallConfig { 654 c := validInstallConfig() 655 c.Platform = types.Platform{ 656 AWS: &aws.Platform{}, 657 } 658 return c 659 }(), 660 expectedError: `^platform\.aws\.region: Required value: region must be specified$`, 661 }, 662 { 663 name: "valid none platform", 664 installConfig: func() *types.InstallConfig { 665 c := validInstallConfig() 666 c.Platform = types.Platform{ 667 None: &none.Platform{}, 668 } 669 return c 670 }(), 671 }, 672 { 673 name: "valid openstack platform", 674 installConfig: func() *types.InstallConfig { 675 c := validInstallConfig() 676 c.Platform = types.Platform{ 677 OpenStack: validOpenStackPlatform(), 678 } 679 return c 680 }(), 681 }, 682 { 683 name: "valid baremetal platform", 684 installConfig: func() *types.InstallConfig { 685 c := validInstallConfig() 686 c.Capabilities = &types.Capabilities{BaselineCapabilitySet: "v4.11"} 687 c.Capabilities.AdditionalEnabledCapabilities = append(c.Capabilities.AdditionalEnabledCapabilities, configv1.ClusterVersionCapabilityIngress, configv1.ClusterVersionCapabilityCloudCredential, configv1.ClusterVersionCapabilityCloudControllerManager, configv1.ClusterVersionCapabilityOperatorLifecycleManager) 688 c.Platform = types.Platform{ 689 BareMetal: validBareMetalPlatform(), 690 } 691 return c 692 }(), 693 }, 694 { 695 name: "valid vsphere platform", 696 installConfig: func() *types.InstallConfig { 697 c := validInstallConfig() 698 c.Platform = types.Platform{ 699 VSphere: validVSpherePlatform(), 700 } 701 return c 702 }(), 703 }, 704 { 705 name: "invalid vsphere platform", 706 installConfig: func() *types.InstallConfig { 707 c := validInstallConfig() 708 c.Platform = types.Platform{ 709 VSphere: validVSpherePlatform(), 710 } 711 c.Platform.VSphere.VCenters[0].Server = "" 712 return c 713 }(), 714 expectedError: `platform\.vsphere\.vcenters\[0]\.server: Required value: must be the domain name or IP address of the vCenter(.*)`, 715 }, 716 { 717 name: "invalid vsphere folder", 718 installConfig: func() *types.InstallConfig { 719 c := validInstallConfig() 720 c.Platform = types.Platform{ 721 VSphere: validVSpherePlatform(), 722 } 723 c.Platform.VSphere.FailureDomains[0].Topology.Folder = "my-folder" 724 return c 725 }(), 726 expectedError: `^platform\.vsphere\.failureDomains\.topology.folder: Invalid value: "my-folder": full path of folder must be provided in format /<datacenter>/vm/<folder>$`, 727 }, 728 { 729 name: "invalid vsphere resource pool", 730 installConfig: func() *types.InstallConfig { 731 c := validInstallConfig() 732 c.Platform = types.Platform{ 733 VSphere: validVSpherePlatform(), 734 } 735 c.Platform.VSphere.FailureDomains[0].Topology.ResourcePool = "my-resource-pool" 736 return c 737 }(), 738 expectedError: `^platform\.vsphere\.failureDomains\.topology\.resourcePool: Invalid value: "my-resource-pool": full path of resource pool must be provided in format /<datacenter>/host/<cluster>/\.\.\.$`, 739 }, 740 { 741 name: "empty proxy settings", 742 installConfig: func() *types.InstallConfig { 743 c := validInstallConfig() 744 c.Proxy.HTTPProxy = "" 745 c.Proxy.HTTPSProxy = "" 746 c.Proxy.NoProxy = "" 747 return c 748 }(), 749 expectedError: `^proxy: Required value: must include httpProxy or httpsProxy$`, 750 }, 751 { 752 name: "invalid HTTPProxy", 753 installConfig: func() *types.InstallConfig { 754 c := validInstallConfig() 755 c.Proxy.HTTPProxy = "http://bad%20uri" 756 return c 757 }(), 758 expectedError: `^proxy.httpProxy: Invalid value: "http://bad%20uri": parse "http://bad%20uri": invalid URL escape "%20"$`, 759 }, 760 { 761 name: "invalid HTTPProxy Schema missing", 762 installConfig: func() *types.InstallConfig { 763 c := validInstallConfig() 764 c.Proxy.HTTPProxy = "http//baduri" 765 return c 766 }(), 767 expectedError: `^proxy.httpProxy: Invalid value: "http//baduri": parse "http//baduri": invalid URI for request$`, 768 }, 769 { 770 name: "HTTPProxy with port overlapping with Cluster Networks", 771 installConfig: func() *types.InstallConfig { 772 c := validInstallConfig() 773 c.Proxy.HTTPProxy = "http://192.168.1.25:3030" 774 c.Networking = validIPv4NetworkingConfig() 775 return c 776 }(), 777 expectedError: `^proxy.httpProxy: Invalid value: "http://192.168.1.25:3030": proxy value is part of the cluster networks$`, 778 }, 779 { 780 name: "overlapping HTTPProxy and Cluster Networks", 781 installConfig: func() *types.InstallConfig { 782 c := validInstallConfig() 783 c.Proxy.HTTPProxy = "http://192.168.1.25" 784 c.Networking = validIPv4NetworkingConfig() 785 return c 786 }(), 787 expectedError: `^proxy.httpProxy: Invalid value: "http://192.168.1.25": proxy value is part of the cluster networks$`, 788 }, 789 { 790 name: "non-overlapping HTTPProxy and Cluster Networks", 791 installConfig: func() *types.InstallConfig { 792 c := validInstallConfig() 793 c.Proxy.HTTPProxy = "http://192.169.1.25" 794 c.Networking = validIPv4NetworkingConfig() 795 return c 796 }(), 797 }, 798 { 799 name: "overlapping HTTPProxy and more than one Cluster Networks", 800 installConfig: func() *types.InstallConfig { 801 c := validInstallConfig() 802 c.Proxy.HTTPProxy = "http://192.168.1.25" 803 c.Networking = validIPv4NetworkingConfig() 804 c.ClusterNetwork = append(c.ClusterNetwork, []types.ClusterNetworkEntry{ 805 { 806 CIDR: *ipnet.MustParseCIDR("192.168.0.0/16"), 807 HostPrefix: 28, 808 }, 809 }..., 810 ) 811 return c 812 }(), 813 expectedError: `^\Q[networking.clusterNetwork[1].cidr: Invalid value: "192.168.0.0/16": cluster network must not overlap with cluster network 0, proxy.httpProxy: Invalid value: "http://192.168.1.25": proxy value is part of the cluster networks]\E$`, 814 }, 815 { 816 name: "non-overlapping HTTPProxy and Service Networks", 817 installConfig: func() *types.InstallConfig { 818 c := validInstallConfig() 819 c.Proxy.HTTPProxy = "http://172.31.0.25" 820 c.Networking = validIPv4NetworkingConfig() 821 return c 822 }(), 823 }, 824 { 825 name: "HTTPProxy with port overlapping with Service Networks", 826 installConfig: func() *types.InstallConfig { 827 c := validInstallConfig() 828 c.Proxy.HTTPProxy = "http://172.30.0.25:3030" 829 c.Networking = validIPv4NetworkingConfig() 830 return c 831 }(), 832 expectedError: `^proxy.httpProxy: Invalid value: "http://172.30.0.25:3030": proxy value is part of the service networks$`, 833 }, 834 { 835 name: "overlapping HTTPProxy and Service Networks", 836 installConfig: func() *types.InstallConfig { 837 c := validInstallConfig() 838 c.Proxy.HTTPProxy = "http://172.30.0.25" 839 c.Networking = validIPv4NetworkingConfig() 840 return c 841 }(), 842 expectedError: `^proxy.httpProxy: Invalid value: "http://172.30.0.25": proxy value is part of the service networks$`, 843 }, 844 { 845 name: "overlapping HTTPProxy and more than one Service Networks", 846 installConfig: func() *types.InstallConfig { 847 c := validInstallConfig() 848 c.Proxy.HTTPProxy = "http://172.30.0.25" 849 c.Networking = validIPv4NetworkingConfig() 850 c.ServiceNetwork = append(c.ServiceNetwork, []ipnet.IPNet{ 851 *ipnet.MustParseCIDR("172.30.1.0/24"), 852 }..., 853 ) 854 return c 855 }(), 856 expectedError: `^\Q[networking.serviceNetwork[1]: Invalid value: "172.30.1.0/24": service network must not overlap with service network 0, networking.serviceNetwork: Invalid value: "172.30.0.0/16, 172.30.1.0/24": only one service network can be specified, proxy.httpProxy: Invalid value: "http://172.30.0.25": proxy value is part of the service networks]\E$`, 857 }, 858 { 859 name: "non-overlapping HTTPSProxy and Cluster Networks", 860 installConfig: func() *types.InstallConfig { 861 c := validInstallConfig() 862 c.Proxy.HTTPSProxy = "http://192.168.2.25" 863 c.Networking = validIPv4NetworkingConfig() 864 return c 865 }(), 866 }, 867 { 868 name: "HTTPSProxy with port overlapping with Cluster Networks", 869 installConfig: func() *types.InstallConfig { 870 c := validInstallConfig() 871 c.Proxy.HTTPSProxy = "http://192.168.1.25:3030" 872 c.Networking = validIPv4NetworkingConfig() 873 return c 874 }(), 875 expectedError: `^proxy.httpsProxy: Invalid value: "http://192.168.1.25:3030": proxy value is part of the cluster networks$`, 876 }, 877 { 878 name: "overlapping HTTPSProxy and Cluster Networks", 879 installConfig: func() *types.InstallConfig { 880 c := validInstallConfig() 881 c.Proxy.HTTPSProxy = "http://192.168.1.25" 882 c.Networking = validIPv4NetworkingConfig() 883 return c 884 }(), 885 expectedError: `^proxy.httpsProxy: Invalid value: "http://192.168.1.25": proxy value is part of the cluster networks$`, 886 }, 887 { 888 name: "overlapping HTTPSProxy and more than one Cluster Networks", 889 installConfig: func() *types.InstallConfig { 890 c := validInstallConfig() 891 c.Proxy.HTTPSProxy = "http://192.168.1.25" 892 c.Networking = validIPv4NetworkingConfig() 893 c.ClusterNetwork = append(c.ClusterNetwork, []types.ClusterNetworkEntry{ 894 { 895 CIDR: *ipnet.MustParseCIDR("192.168.0.0/16"), 896 HostPrefix: 28, 897 }, 898 }..., 899 ) 900 return c 901 }(), 902 expectedError: `^\Q[networking.clusterNetwork[1].cidr: Invalid value: "192.168.0.0/16": cluster network must not overlap with cluster network 0, proxy.httpsProxy: Invalid value: "http://192.168.1.25": proxy value is part of the cluster networks]\E$`, 903 }, 904 { 905 name: "overlapping HTTPSProxy and Service Networks", 906 installConfig: func() *types.InstallConfig { 907 c := validInstallConfig() 908 c.Proxy.HTTPSProxy = "http://172.30.0.25" 909 c.Networking = validIPv4NetworkingConfig() 910 return c 911 }(), 912 expectedError: `^proxy.httpsProxy: Invalid value: "http://172.30.0.25": proxy value is part of the service networks$`, 913 }, 914 { 915 name: "HTTPSProxy with port overlapping with Service Networks", 916 installConfig: func() *types.InstallConfig { 917 c := validInstallConfig() 918 c.Proxy.HTTPSProxy = "http://172.30.0.25:3030" 919 c.Networking = validIPv4NetworkingConfig() 920 return c 921 }(), 922 expectedError: `^proxy.httpsProxy: Invalid value: "http://172.30.0.25:3030": proxy value is part of the service networks$`, 923 }, 924 { 925 name: "overlapping HTTPSProxy and more than one Service Networks", 926 installConfig: func() *types.InstallConfig { 927 c := validInstallConfig() 928 c.Proxy.HTTPSProxy = "http://172.30.0.25" 929 c.Networking = validIPv4NetworkingConfig() 930 c.ServiceNetwork = append(c.ServiceNetwork, []ipnet.IPNet{ 931 *ipnet.MustParseCIDR("172.30.1.0/24"), 932 }..., 933 ) 934 return c 935 }(), 936 expectedError: `^\Q[networking.serviceNetwork[1]: Invalid value: "172.30.1.0/24": service network must not overlap with service network 0, networking.serviceNetwork: Invalid value: "172.30.0.0/16, 172.30.1.0/24": only one service network can be specified, proxy.httpsProxy: Invalid value: "http://172.30.0.25": proxy value is part of the service networks]\E$`, 937 }, 938 { 939 name: "invalid HTTPProxy Schema different schema", 940 installConfig: func() *types.InstallConfig { 941 c := validInstallConfig() 942 c.Proxy.HTTPProxy = "ftp://baduri" 943 return c 944 }(), 945 expectedError: `^proxy.httpProxy: Unsupported value: "ftp": supported values: "http"$`, 946 }, 947 { 948 name: "invalid HTTPSProxy", 949 installConfig: func() *types.InstallConfig { 950 c := validInstallConfig() 951 c.Proxy.HTTPSProxy = "https://bad%20uri" 952 return c 953 }(), 954 expectedError: `^proxy.httpsProxy: Invalid value: "https://bad%20uri": parse "https://bad%20uri": invalid URL escape "%20"$`, 955 }, 956 { 957 name: "invalid HTTPSProxy Schema missing", 958 installConfig: func() *types.InstallConfig { 959 c := validInstallConfig() 960 c.Proxy.HTTPSProxy = "http//baduri" 961 return c 962 }(), 963 expectedError: `^proxy.httpsProxy: Invalid value: "http//baduri": parse "http//baduri": invalid URI for request$`, 964 }, 965 { 966 name: "invalid HTTPSProxy Schema different schema", 967 installConfig: func() *types.InstallConfig { 968 c := validInstallConfig() 969 c.Proxy.HTTPSProxy = "ftp://baduri" 970 return c 971 }(), 972 expectedError: `^proxy.httpsProxy: Unsupported value: "ftp": supported values: "http", "https"$`, 973 }, 974 { 975 name: "invalid NoProxy domain", 976 installConfig: func() *types.InstallConfig { 977 c := validInstallConfig() 978 c.Proxy.NoProxy = "good-no-proxy.com,*.bad-proxy" 979 return c 980 }(), 981 expectedError: `^\Qproxy.noProxy: Invalid value: "good-no-proxy.com,*.bad-proxy": each element of noProxy must be a IP, CIDR or domain without wildcard characters, which is violated by element 1 "*.bad-proxy"\E$`, 982 }, 983 { 984 name: "invalid NoProxy spaces", 985 installConfig: func() *types.InstallConfig { 986 c := validInstallConfig() 987 c.Proxy.NoProxy = "good-no-proxy.com, *.bad-proxy" 988 return c 989 }(), 990 expectedError: `^\Q[proxy.noProxy: Invalid value: "good-no-proxy.com, *.bad-proxy": noProxy must not have spaces, proxy.noProxy: Invalid value: "good-no-proxy.com, *.bad-proxy": each element of noProxy must be a IP, CIDR or domain without wildcard characters, which is violated by element 1 "*.bad-proxy"]\E$`, 991 }, 992 { 993 name: "invalid NoProxy CIDR", 994 installConfig: func() *types.InstallConfig { 995 c := validInstallConfig() 996 c.Proxy.NoProxy = "good-no-proxy.com,172.bad.CIDR.0/16" 997 return c 998 }(), 999 expectedError: `^\Qproxy.noProxy: Invalid value: "good-no-proxy.com,172.bad.CIDR.0/16": each element of noProxy must be a IP, CIDR or domain without wildcard characters, which is violated by element 1 "172.bad.CIDR.0/16"\E$`, 1000 }, 1001 { 1002 name: "invalid NoProxy domain & CIDR", 1003 installConfig: func() *types.InstallConfig { 1004 c := validInstallConfig() 1005 c.Proxy.NoProxy = "good-no-proxy.com,a-good-one,*.bad-proxy.,another,172.bad.CIDR.0/16,good-end" 1006 return c 1007 }(), 1008 expectedError: `^\Q[proxy.noProxy: Invalid value: "good-no-proxy.com,a-good-one,*.bad-proxy.,another,172.bad.CIDR.0/16,good-end": each element of noProxy must be a IP, CIDR or domain without wildcard characters, which is violated by element 2 "*.bad-proxy.", proxy.noProxy: Invalid value: "good-no-proxy.com,a-good-one,*.bad-proxy.,another,172.bad.CIDR.0/16,good-end": each element of noProxy must be a IP, CIDR or domain without wildcard characters, which is violated by element 4 "172.bad.CIDR.0/16"]\E$`, 1009 }, 1010 { 1011 name: "valid * NoProxy", 1012 installConfig: func() *types.InstallConfig { 1013 c := validInstallConfig() 1014 c.Proxy.NoProxy = "*" 1015 return c 1016 }(), 1017 }, 1018 { 1019 name: "valid GCP platform", 1020 installConfig: func() *types.InstallConfig { 1021 c := validInstallConfig() 1022 c.Platform = types.Platform{ 1023 GCP: validGCPPlatform(), 1024 } 1025 return c 1026 }(), 1027 }, 1028 { 1029 name: "invalid GCP cluster name", 1030 installConfig: func() *types.InstallConfig { 1031 c := validInstallConfig() 1032 c.Platform = types.Platform{ 1033 GCP: validGCPPlatform(), 1034 } 1035 c.ObjectMeta.Name = "1-invalid-cluster" 1036 return c 1037 }(), 1038 expectedError: `^metadata\.name: Invalid value: "1-invalid-cluster": cluster name must begin with a lower-case letter$`, 1039 }, 1040 { 1041 name: "valid ibmcloud platform", 1042 installConfig: func() *types.InstallConfig { 1043 c := validInstallConfig() 1044 c.Platform = types.Platform{ 1045 IBMCloud: validIBMCloudPlatform(), 1046 } 1047 return c 1048 }(), 1049 }, 1050 { 1051 name: "invalid ibmcloud platform", 1052 installConfig: func() *types.InstallConfig { 1053 c := validInstallConfig() 1054 c.Platform = types.Platform{ 1055 IBMCloud: &ibmcloud.Platform{}, 1056 } 1057 return c 1058 }(), 1059 expectedError: `^\Qplatform.ibmcloud.region: Required value: region must be specified\E$`, 1060 }, 1061 { 1062 name: "valid powervs platform", 1063 installConfig: func() *types.InstallConfig { 1064 c := validInstallConfig() 1065 c.SSHKey = validSSHKey() 1066 c.Platform = types.Platform{ 1067 PowerVS: validPowerVSPlatform(), 1068 } 1069 return c 1070 }(), 1071 }, 1072 { 1073 name: "valid powervs platform manual credential mod", 1074 installConfig: func() *types.InstallConfig { 1075 c := validInstallConfig() 1076 c.SSHKey = validSSHKey() 1077 c.Platform = types.Platform{ 1078 PowerVS: validPowerVSPlatform(), 1079 } 1080 c.CredentialsMode = types.ManualCredentialsMode 1081 return c 1082 }(), 1083 }, 1084 { 1085 name: "invalid powervs platform mint credential mod", 1086 installConfig: func() *types.InstallConfig { 1087 c := validInstallConfig() 1088 c.SSHKey = validSSHKey() 1089 c.Platform = types.Platform{ 1090 PowerVS: validPowerVSPlatform(), 1091 } 1092 c.CredentialsMode = types.MintCredentialsMode 1093 return c 1094 }(), 1095 expectedError: `^credentialsMode: Unsupported value: "Mint": supported values: "Manual"$`, 1096 }, 1097 { 1098 name: "invalid powervs platform", 1099 installConfig: func() *types.InstallConfig { 1100 c := validInstallConfig() 1101 c.SSHKey = validSSHKey() 1102 c.Platform = types.Platform{ 1103 PowerVS: &powervs.Platform{}, 1104 } 1105 return c 1106 }(), 1107 expectedError: `^\Qplatform.powervs.zone: Required value: zone must be specified\E$`, 1108 }, 1109 { 1110 name: "valid azurestack platform", 1111 installConfig: func() *types.InstallConfig { 1112 c := validInstallConfig() 1113 c.Platform = types.Platform{ 1114 Azure: validAzureStackPlatform(), 1115 } 1116 return c 1117 }(), 1118 }, 1119 { 1120 name: "invalid azurestack platform mint credentials mod", 1121 installConfig: func() *types.InstallConfig { 1122 c := validInstallConfig() 1123 c.Platform = types.Platform{ 1124 Azure: validAzureStackPlatform(), 1125 } 1126 c.CredentialsMode = types.MintCredentialsMode 1127 return c 1128 }(), 1129 expectedError: `^credentialsMode: Unsupported value: "Mint": supported values: "Manual"$`, 1130 }, 1131 { 1132 name: "release image source is not valid", 1133 installConfig: func() *types.InstallConfig { 1134 c := validInstallConfig() 1135 c.DeprecatedImageContentSources = []types.ImageContentSource{{ 1136 Source: "ocp/release-x.y", 1137 }} 1138 return c 1139 }(), 1140 expectedError: `^imageContentSources\[0\]\.source: Invalid value: "ocp/release-x\.y": the repository provided is invalid: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, \'\-\' or \'\.\', and must start and end with an alphanumeric character \(e.g. \'example\.com\', regex used for validation is \'\[a\-z0\-9\]\(\[\-a\-z0\-9\]\*\[a\-z0\-9\]\)\?\(\\\.\[a\-z0\-9\]\(\[\-a\-z0\-9\]\*\[a\-z0\-9\]\)\?\)\*\'\)`, 1141 }, 1142 { 1143 name: "release image source's mirror is not valid", 1144 installConfig: func() *types.InstallConfig { 1145 c := validInstallConfig() 1146 c.DeprecatedImageContentSources = []types.ImageContentSource{{ 1147 Source: "q.io/ocp/release-x.y", 1148 Mirrors: []string{"ocp/openshift-x.y"}, 1149 }} 1150 return c 1151 }(), 1152 expectedError: `^imageContentSources\[0\]\.mirrors\[0\]: Invalid value: "ocp/openshift-x\.y": the repository provided is invalid: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, \'\-\' or \'\.\', and must start and end with an alphanumeric character \(e.g. \'example\.com\', regex used for validation is \'\[a\-z0\-9\]\(\[\-a\-z0\-9\]\*\[a\-z0\-9\]\)\?\(\\\.\[a\-z0\-9\]\(\[\-a\-z0\-9\]\*\[a\-z0\-9\]\)\?\)\*\'\)`, 1153 }, 1154 { 1155 name: "release image source's mirror is valid", 1156 installConfig: func() *types.InstallConfig { 1157 c := validInstallConfig() 1158 c.DeprecatedImageContentSources = []types.ImageContentSource{{ 1159 Source: "q.io/ocp/release-x.y", 1160 Mirrors: []string{"mirror.example.com:5000"}, 1161 }} 1162 return c 1163 }(), 1164 }, 1165 { 1166 name: "release image source is not repository but reference by digest", 1167 installConfig: func() *types.InstallConfig { 1168 c := validInstallConfig() 1169 c.DeprecatedImageContentSources = []types.ImageContentSource{{ 1170 Source: "quay.io/ocp/release-x.y@sha256:397c867cc10bcc90cf05ae9b71dd3de6000535e27cb6c704d9f503879202582c", 1171 }} 1172 return c 1173 }(), 1174 expectedError: `^imageContentSources\[0\]\.source: Invalid value: "quay\.io/ocp/release-x\.y@sha256:397c867cc10bcc90cf05ae9b71dd3de6000535e27cb6c704d9f503879202582c": must be repository--not reference$`, 1175 }, 1176 { 1177 name: "release image source is not repository but reference by tag", 1178 installConfig: func() *types.InstallConfig { 1179 c := validInstallConfig() 1180 c.DeprecatedImageContentSources = []types.ImageContentSource{{ 1181 Source: "quay.io/ocp/release-x.y:latest", 1182 }} 1183 return c 1184 }(), 1185 expectedError: `^imageContentSources\[0\]\.source: Invalid value: "quay\.io/ocp/release-x\.y:latest": must be repository--not reference$`, 1186 }, 1187 { 1188 name: "valid release image source", 1189 installConfig: func() *types.InstallConfig { 1190 c := validInstallConfig() 1191 c.DeprecatedImageContentSources = []types.ImageContentSource{{ 1192 Source: "quay.io/ocp/release-x.y", 1193 }} 1194 return c 1195 }(), 1196 }, 1197 { 1198 name: "release image source is not valid ImageDigestSource", 1199 installConfig: func() *types.InstallConfig { 1200 c := validInstallConfig() 1201 c.ImageDigestSources = []types.ImageDigestSource{{ 1202 Source: "ocp/release-x.y", 1203 }} 1204 return c 1205 }(), 1206 expectedError: `^imageDigestSources\[0\]\.source: Invalid value: "ocp/release-x\.y": the repository provided is invalid: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, \'\-\' or \'\.\', and must start and end with an alphanumeric character \(e.g. \'example\.com\', regex used for validation is \'\[a\-z0\-9\]\(\[\-a\-z0\-9\]\*\[a\-z0\-9\]\)\?\(\\\.\[a\-z0\-9\]\(\[\-a\-z0\-9\]\*\[a\-z0\-9\]\)\?\)\*\'\)`, 1207 }, 1208 { 1209 name: "release image source's mirror is not valid ImageDigestSource", 1210 installConfig: func() *types.InstallConfig { 1211 c := validInstallConfig() 1212 c.ImageDigestSources = []types.ImageDigestSource{{ 1213 Source: "q.io/ocp/release-x.y", 1214 Mirrors: []string{"ocp/openshift-x.y"}, 1215 }} 1216 return c 1217 }(), 1218 expectedError: `^imageDigestSources\[0\]\.mirrors\[0\]: Invalid value: "ocp/openshift-x\.y": the repository provided is invalid: a lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, \'\-\' or \'\.\', and must start and end with an alphanumeric character \(e.g. \'example\.com\', regex used for validation is \'\[a\-z0\-9\]\(\[\-a\-z0\-9\]\*\[a\-z0\-9\]\)\?\(\\\.\[a\-z0\-9\]\(\[\-a\-z0\-9\]\*\[a\-z0\-9\]\)\?\)\*\'\)`, 1219 }, 1220 { 1221 name: "release image source's mirror is valid ImageDigestSource", 1222 installConfig: func() *types.InstallConfig { 1223 c := validInstallConfig() 1224 c.ImageDigestSources = []types.ImageDigestSource{{ 1225 Source: "q.io/ocp/release-x.y", 1226 Mirrors: []string{"mirror.example.com:5000"}, 1227 }} 1228 return c 1229 }(), 1230 }, 1231 { 1232 name: "release image source is not repository but reference by digest ImageDigestSource", 1233 installConfig: func() *types.InstallConfig { 1234 c := validInstallConfig() 1235 c.ImageDigestSources = []types.ImageDigestSource{{ 1236 Source: "quay.io/ocp/release-x.y@sha256:397c867cc10bcc90cf05ae9b71dd3de6000535e27cb6c704d9f503879202582c", 1237 }} 1238 return c 1239 }(), 1240 expectedError: `^imageDigestSources\[0\]\.source: Invalid value: "quay\.io/ocp/release-x\.y@sha256:397c867cc10bcc90cf05ae9b71dd3de6000535e27cb6c704d9f503879202582c": must be repository--not reference$`, 1241 }, 1242 { 1243 name: "release image source is not repository but reference by tag ImageDigestSource", 1244 installConfig: func() *types.InstallConfig { 1245 c := validInstallConfig() 1246 c.ImageDigestSources = []types.ImageDigestSource{{ 1247 Source: "quay.io/ocp/release-x.y:latest", 1248 }} 1249 return c 1250 }(), 1251 expectedError: `^imageDigestSources\[0\]\.source: Invalid value: "quay\.io/ocp/release-x\.y:latest": must be repository--not reference$`, 1252 }, 1253 { 1254 name: "valid release image source ImageDigstSourrce", 1255 installConfig: func() *types.InstallConfig { 1256 c := validInstallConfig() 1257 c.ImageDigestSources = []types.ImageDigestSource{{ 1258 Source: "quay.io/ocp/release-x.y", 1259 }} 1260 return c 1261 }(), 1262 }, 1263 { 1264 name: "error out ImageContentSources and ImageDigestSources and are set at the same time", 1265 installConfig: func() *types.InstallConfig { 1266 c := validInstallConfig() 1267 c.DeprecatedImageContentSources = []types.ImageContentSource{{ 1268 Source: "q.io/ocp/source", 1269 Mirrors: []string{"ocp/openshift/mirror"}, 1270 }} 1271 c.ImageDigestSources = []types.ImageDigestSource{{ 1272 Source: "q.io/ocp/source", 1273 Mirrors: []string{"ocp-digest/openshift/mirror"}}} 1274 return c 1275 }(), 1276 expectedError: `cannot set imageContentSources and imageDigestSources at the same time`, 1277 }, 1278 { 1279 name: "invalid publishing strategy", 1280 installConfig: func() *types.InstallConfig { 1281 c := validInstallConfig() 1282 c.Publish = types.PublishingStrategy("ExternalInternalDoNotCare") 1283 return c 1284 }(), 1285 expectedError: `^publish: Unsupported value: \"ExternalInternalDoNotCare\": supported values: \"External\", \"Internal\"`, 1286 }, 1287 1288 { 1289 name: "valid dual-stack configuration", 1290 installConfig: func() *types.InstallConfig { 1291 c := validInstallConfig() 1292 c.Platform = types.Platform{None: &none.Platform{}} 1293 c.Networking = validDualStackNetworkingConfig() 1294 return c 1295 }(), 1296 }, 1297 { 1298 name: "valid single-stack IPv6 configuration", 1299 installConfig: func() *types.InstallConfig { 1300 c := validInstallConfig() 1301 c.Platform = types.Platform{None: &none.Platform{}} 1302 c.Networking = validIPv6NetworkingConfig() 1303 return c 1304 }(), 1305 }, 1306 { 1307 name: "invalid dual-stack configuration, bad platform", 1308 installConfig: func() *types.InstallConfig { 1309 c := validInstallConfig() 1310 c.Platform = types.Platform{GCP: validGCPPlatform()} 1311 c.Networking = validDualStackNetworkingConfig() 1312 return c 1313 }(), 1314 expectedError: `Invalid value: "DualStack": dual-stack IPv4/IPv6 is not supported for this platform, specify only one type of address`, 1315 }, 1316 { 1317 name: "invalid single-stack IPv6 configuration, bad platform", 1318 installConfig: func() *types.InstallConfig { 1319 c := validInstallConfig() 1320 c.Platform = types.Platform{GCP: validGCPPlatform()} 1321 c.Networking = validIPv6NetworkingConfig() 1322 return c 1323 }(), 1324 expectedError: `Invalid value: "IPv6": single-stack IPv6 is not supported for this platform`, 1325 }, 1326 { 1327 name: "invalid dual-stack configuration, machine has no IPv6", 1328 installConfig: func() *types.InstallConfig { 1329 c := validInstallConfig() 1330 c.Platform = types.Platform{None: &none.Platform{}} 1331 c.Networking = validDualStackNetworkingConfig() 1332 c.Networking.MachineNetwork = c.Networking.MachineNetwork[:1] 1333 return c 1334 }(), 1335 expectedError: `Invalid value: "10.0.0.0/16": dual-stack IPv4/IPv6 requires an IPv6 network in this list`, 1336 }, 1337 { 1338 name: "invalid dual-stack configuration, IPv6-primary", 1339 installConfig: func() *types.InstallConfig { 1340 c := validInstallConfig() 1341 c.Platform = types.Platform{None: &none.Platform{}} 1342 c.Networking = validDualStackNetworkingConfig() 1343 c.Networking.ServiceNetwork = []ipnet.IPNet{ 1344 c.Networking.ServiceNetwork[1], 1345 c.Networking.ServiceNetwork[0], 1346 } 1347 return c 1348 }(), 1349 expectedError: `Invalid value: "ffd1::/112, 172.30.0.0/16": IPv4 addresses must be listed before IPv6 addresses`, 1350 }, 1351 { 1352 name: "valid dual-stack configuration with mixed-order clusterNetworks", 1353 installConfig: func() *types.InstallConfig { 1354 c := validInstallConfig() 1355 c.Platform = types.Platform{None: &none.Platform{}} 1356 c.Networking = validDualStackNetworkingConfig() 1357 c.Networking.ClusterNetwork = append(c.Networking.ClusterNetwork, 1358 types.ClusterNetworkEntry{ 1359 CIDR: *ipnet.MustParseCIDR("192.168.2.0/24"), 1360 HostPrefix: 28, 1361 }, 1362 ) 1363 // ClusterNetwork is now "IPv4, IPv6, IPv4", which is allowed 1364 return c 1365 }(), 1366 }, 1367 { 1368 name: "invalid IPv6 hostprefix", 1369 installConfig: func() *types.InstallConfig { 1370 c := validInstallConfig() 1371 c.Platform = types.Platform{None: &none.Platform{}} 1372 c.Networking = validIPv6NetworkingConfig() 1373 c.Networking.ClusterNetwork[0].HostPrefix = 72 1374 return c 1375 }(), 1376 expectedError: `Invalid value: 72: cluster network host subnetwork prefix must be 64 for IPv6 networks`, 1377 }, 1378 { 1379 name: "invalid IPv6 service network size", 1380 installConfig: func() *types.InstallConfig { 1381 c := validInstallConfig() 1382 c.Platform = types.Platform{None: &none.Platform{}} 1383 c.Networking = validIPv6NetworkingConfig() 1384 c.Networking.ServiceNetwork[0] = *ipnet.MustParseCIDR("ffd1::/48") 1385 return c 1386 }(), 1387 expectedError: `Invalid value: "ffd1::/48": subnet size for IPv6 service network should be /112`, 1388 }, 1389 { 1390 name: "architecture is not supported", 1391 installConfig: func() *types.InstallConfig { 1392 c := validInstallConfig() 1393 c.Compute[0].Architecture = types.ArchitectureS390X 1394 c.ControlPlane.Architecture = types.ArchitectureS390X 1395 return c 1396 }(), 1397 expectedError: `[controlPlane.architecture: Unsupported value: "s390x": supported values: "amd64", "arm64", compute\[0\].architecture: Unsupported value: "s390x": supported values: "amd64", "arm64"]`, 1398 }, 1399 { 1400 name: "architecture is not supported", 1401 installConfig: func() *types.InstallConfig { 1402 c := validInstallConfig() 1403 c.Compute[0].Architecture = types.ArchitecturePPC64LE 1404 c.ControlPlane.Architecture = types.ArchitecturePPC64LE 1405 return c 1406 }(), 1407 expectedError: `[controlPlane.architecture: Unsupported value: "ppc64le": supported values: "amd64", "arm64", compute\[0\].architecture: Unsupported value: "ppc64le": supported values: "amd64", "arm64"]`, 1408 }, 1409 { 1410 name: "cluster is not heteregenous", 1411 installConfig: func() *types.InstallConfig { 1412 c := validInstallConfig() 1413 c.Platform = types.Platform{Azure: validAzureStackPlatform()} 1414 c.Compute[0].Architecture = types.ArchitectureARM64 1415 return c 1416 }(), 1417 expectedError: `^compute\[0\].architecture: Invalid value: "arm64": heteregeneous multi-arch is not supported; compute pool architecture must match control plane$`, 1418 }, 1419 { 1420 name: "aws cluster is heteregeneous", 1421 installConfig: func() *types.InstallConfig { 1422 c := validInstallConfig() 1423 c.Compute[0].Architecture = types.ArchitectureARM64 1424 return c 1425 }(), 1426 }, 1427 { 1428 name: "gcp cluster is heteregeneous", 1429 installConfig: func() *types.InstallConfig { 1430 c := validInstallConfig() 1431 c.Platform = types.Platform{GCP: validGCPPlatform()} 1432 c.Compute[0].Architecture = types.ArchitectureARM64 1433 return c 1434 }(), 1435 }, 1436 { 1437 name: "valid cloud credentials mode", 1438 installConfig: func() *types.InstallConfig { 1439 c := validInstallConfig() 1440 c.CredentialsMode = types.PassthroughCredentialsMode 1441 return c 1442 }(), 1443 }, 1444 { 1445 name: "invalidly set cloud credentials mode", 1446 installConfig: func() *types.InstallConfig { 1447 c := validInstallConfig() 1448 c.Platform = types.Platform{BareMetal: validBareMetalPlatform()} 1449 c.Capabilities = &types.Capabilities{BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetCurrent} 1450 c.CredentialsMode = types.PassthroughCredentialsMode 1451 return c 1452 }(), 1453 expectedError: `^credentialsMode: Invalid value: "Passthrough": cannot be set when using the "baremetal" platform$`, 1454 }, 1455 { 1456 name: "bad cloud credentials mode", 1457 installConfig: func() *types.InstallConfig { 1458 c := validInstallConfig() 1459 c.CredentialsMode = "bad-mode" 1460 return c 1461 }(), 1462 expectedError: `^credentialsMode: Unsupported value: "bad-mode": supported values: "Manual", "Mint", "Passthrough"$`, 1463 }, 1464 { 1465 name: "allowed docker bridge with non-libvirt", 1466 installConfig: func() *types.InstallConfig { 1467 c := validInstallConfig() 1468 c.Networking.MachineNetwork = []types.MachineNetworkEntry{{CIDR: *ipnet.MustParseCIDR("172.17.64.0/18")}} 1469 return c 1470 }(), 1471 expectedError: ``, 1472 }, 1473 { 1474 name: "publish internal for non-cloud platform", 1475 installConfig: func() *types.InstallConfig { 1476 c := validInstallConfig() 1477 c.Platform = types.Platform{VSphere: validVSpherePlatform()} 1478 c.Publish = types.InternalPublishingStrategy 1479 return c 1480 }(), 1481 expectedError: `publish: Invalid value: "Internal": Internal publish strategy is not supported on "vsphere" platform`, 1482 }, 1483 { 1484 name: "publish internal for cloud platform", 1485 installConfig: func() *types.InstallConfig { 1486 c := validInstallConfig() 1487 c.Platform = types.Platform{GCP: validGCPPlatform()} 1488 c.Publish = types.InternalPublishingStrategy 1489 return c 1490 }(), 1491 }, { 1492 name: "valid nutanix platform", 1493 installConfig: func() *types.InstallConfig { 1494 c := validInstallConfig() 1495 c.Platform = types.Platform{ 1496 Nutanix: validNutanixPlatform(), 1497 } 1498 return c 1499 }(), 1500 }, { 1501 name: "invalid nutanix platform", 1502 installConfig: func() *types.InstallConfig { 1503 c := validInstallConfig() 1504 c.Platform = types.Platform{ 1505 Nutanix: validNutanixPlatform(), 1506 } 1507 c.Platform.Nutanix.PrismCentral.Endpoint.Address = "" 1508 return c 1509 }(), 1510 expectedError: `^platform\.nutanix\.prismCentral\.endpoint\.address: Required value: must specify the Prism Central endpoint address$`, 1511 }, 1512 { 1513 name: "invalid credentials mode for nutanix", 1514 installConfig: func() *types.InstallConfig { 1515 c := validInstallConfig() 1516 c.Platform = types.Platform{ 1517 Nutanix: validNutanixPlatform(), 1518 } 1519 c.CredentialsMode = types.PassthroughCredentialsMode 1520 return c 1521 }(), 1522 expectedError: `credentialsMode: Unsupported value: "Passthrough": supported values: "Manual"$`, 1523 }, 1524 { 1525 name: "valid credentials mode for nutanix", 1526 installConfig: func() *types.InstallConfig { 1527 c := validInstallConfig() 1528 c.Platform = types.Platform{ 1529 Nutanix: validNutanixPlatform(), 1530 } 1531 c.CredentialsMode = types.ManualCredentialsMode 1532 return c 1533 }(), 1534 }, 1535 { 1536 name: "valid baseline capability set", 1537 installConfig: func() *types.InstallConfig { 1538 c := validInstallConfig() 1539 c.Capabilities = &types.Capabilities{BaselineCapabilitySet: "v4.11"} 1540 c.Capabilities.AdditionalEnabledCapabilities = append(c.Capabilities.AdditionalEnabledCapabilities, configv1.ClusterVersionCapabilityCloudCredential, configv1.ClusterVersionCapabilityCloudControllerManager, configv1.ClusterVersionCapabilityIngress, configv1.ClusterVersionCapabilityOperatorLifecycleManager) 1541 return c 1542 }(), 1543 }, 1544 { 1545 name: "invalid empty string baseline capability set", 1546 installConfig: func() *types.InstallConfig { 1547 c := validInstallConfig() 1548 c.Capabilities = &types.Capabilities{BaselineCapabilitySet: ""} 1549 return c 1550 }(), 1551 expectedError: `capabilities.baselineCapabilitySet: Unsupported value: "": supported values: .*`, 1552 }, 1553 { 1554 name: "invalid baseline capability set specified", 1555 installConfig: func() *types.InstallConfig { 1556 c := validInstallConfig() 1557 c.Capabilities = &types.Capabilities{BaselineCapabilitySet: "vNotValid"} 1558 return c 1559 }(), 1560 expectedError: `capabilities.baselineCapabilitySet: Unsupported value: "vNotValid": supported values: .*`, 1561 }, 1562 { 1563 name: "invalid capability marketplace specified without OperatorLifecycleManager", 1564 installConfig: func() *types.InstallConfig { 1565 c := validInstallConfig() 1566 c.Capabilities = &types.Capabilities{BaselineCapabilitySet: "None", 1567 AdditionalEnabledCapabilities: []configv1.ClusterVersionCapability{"marketplace"}} 1568 return c 1569 }(), 1570 expectedError: `additionalEnabledCapabilities: Invalid value: \[\]v1.ClusterVersionCapability{"marketplace"}: the marketplace capability requires the OperatorLifecycleManager capability`, 1571 }, 1572 { 1573 name: "valid additional enabled capability specified", 1574 installConfig: func() *types.InstallConfig { 1575 c := validInstallConfig() 1576 c.Capabilities = &types.Capabilities{BaselineCapabilitySet: "v4.11"} 1577 c.Capabilities.AdditionalEnabledCapabilities = append(c.Capabilities.AdditionalEnabledCapabilities, configv1.ClusterVersionCapabilityCloudCredential, configv1.ClusterVersionCapabilityOpenShiftSamples, configv1.ClusterVersionCapabilityCloudControllerManager, configv1.ClusterVersionCapabilityIngress, configv1.ClusterVersionCapabilityOperatorLifecycleManager) 1578 return c 1579 }(), 1580 }, 1581 { 1582 name: "invalid empty additional enabled capability specified", 1583 installConfig: func() *types.InstallConfig { 1584 c := validInstallConfig() 1585 c.Capabilities = &types.Capabilities{BaselineCapabilitySet: "v4.11", 1586 AdditionalEnabledCapabilities: []configv1.ClusterVersionCapability{""}} 1587 return c 1588 }(), 1589 expectedError: `capabilities.additionalEnabledCapabilities\[0\]: Unsupported value: "": supported values: .*`, 1590 }, 1591 { 1592 name: "invalid additional enabled capability specified", 1593 installConfig: func() *types.InstallConfig { 1594 c := validInstallConfig() 1595 c.Capabilities = &types.Capabilities{BaselineCapabilitySet: "v4.11", 1596 AdditionalEnabledCapabilities: []configv1.ClusterVersionCapability{"not-valid"}} 1597 return c 1598 }(), 1599 expectedError: `capabilities.additionalEnabledCapabilities\[0\]: Unsupported value: "not-valid": supported values: .*`, 1600 }, 1601 { 1602 name: "baremetal platform requires the baremetal capability", 1603 installConfig: func() *types.InstallConfig { 1604 c := validInstallConfig() 1605 c.Platform = types.Platform{ 1606 BareMetal: validBareMetalPlatform(), 1607 } 1608 c.Capabilities = &types.Capabilities{BaselineCapabilitySet: "None", AdditionalEnabledCapabilities: []configv1.ClusterVersionCapability{"marketplace"}} 1609 return c 1610 }(), 1611 expectedError: `additionalEnabledCapabilities: Invalid value: \[\]v1.ClusterVersionCapability{"marketplace"}: platform baremetal requires the baremetal capability`, 1612 }, 1613 //VIP tests 1614 { 1615 name: "apivip_v4_not_in_machinenetwork_cidr", 1616 installConfig: func() *types.InstallConfig { 1617 c := validInstallConfig() 1618 c.Networking.MachineNetwork = []types.MachineNetworkEntry{ 1619 {CIDR: *ipnet.MustParseCIDR("10.0.0.0/16")}, 1620 {CIDR: *ipnet.MustParseCIDR("fe80::/10")}, 1621 } 1622 c.Platform = types.Platform{ 1623 BareMetal: validBareMetalPlatform(), 1624 } 1625 c.Platform.BareMetal.APIVIPs = []string{"192.168.222.1"} 1626 1627 return c 1628 }(), 1629 expectedError: "platform.baremetal.apiVIPs: Invalid value: \"192.168.222.1\": IP expected to be in one of the machine networks: 10.0.0.0/16,fe80::/10", 1630 }, 1631 { 1632 name: "apivip_v4_not_in_machinenetwork_cidr_usermanaged_loadbalancer", 1633 installConfig: func() *types.InstallConfig { 1634 c := validInstallConfig() 1635 c.FeatureSet = configv1.TechPreviewNoUpgrade 1636 c.Networking.MachineNetwork = []types.MachineNetworkEntry{ 1637 {CIDR: *ipnet.MustParseCIDR("10.0.0.0/16")}, 1638 {CIDR: *ipnet.MustParseCIDR("fe80::/10")}, 1639 } 1640 c.Platform = types.Platform{ 1641 BareMetal: validBareMetalPlatform(), 1642 } 1643 c.Platform.BareMetal.LoadBalancer = &configv1.BareMetalPlatformLoadBalancer{Type: configv1.LoadBalancerTypeUserManaged} 1644 c.Platform.BareMetal.APIVIPs = []string{"192.168.222.1"} 1645 1646 return c 1647 }(), 1648 }, 1649 { 1650 name: "apivip_v6_not_in_machinenetwork_cidr", 1651 installConfig: func() *types.InstallConfig { 1652 c := validInstallConfig() 1653 c.Networking.MachineNetwork = []types.MachineNetworkEntry{ 1654 {CIDR: *ipnet.MustParseCIDR("10.0.0.0/16")}, 1655 {CIDR: *ipnet.MustParseCIDR("fe80::/10")}, 1656 } 1657 c.Platform = types.Platform{ 1658 BareMetal: validBareMetalPlatform(), 1659 } 1660 c.Platform.BareMetal.APIVIPs = []string{"2001::1"} 1661 1662 return c 1663 }(), 1664 expectedError: "platform.baremetal.apiVIPs: Invalid value: \"2001::1\": IP expected to be in one of the machine networks: 10.0.0.0/16,fe80::/10", 1665 }, 1666 { 1667 name: "apivips_v6_on_openshiftsdn", 1668 installConfig: func() *types.InstallConfig { 1669 c := validInstallConfig() 1670 c.Networking = validIPv6NetworkingConfig() 1671 c.Networking.NetworkType = string(operv1.NetworkTypeOpenShiftSDN) 1672 1673 c.Platform = types.Platform{ 1674 BareMetal: validBareMetalPlatform(), 1675 } 1676 c.Platform.BareMetal.APIVIPs = []string{"ffd0::1"} 1677 1678 return c 1679 }(), 1680 expectedError: "[networking.networkType: Invalid value: \"OpenShiftSDN\": networkType OpenShiftSDN is not supported, please use OVNKubernetes, platform.baremetal.ingressVIPs: Invalid value: \"10.0.0.4\": IP expected to be in one of the machine networks: ffd0::/48]", 1681 }, 1682 { 1683 name: "ingressvips_v6_on_openshiftsdn", 1684 installConfig: func() *types.InstallConfig { 1685 c := validInstallConfig() 1686 c.Networking = validIPv6NetworkingConfig() 1687 c.Networking.NetworkType = string(operv1.NetworkTypeOpenShiftSDN) 1688 1689 c.Platform = types.Platform{ 1690 BareMetal: validBareMetalPlatform(), 1691 } 1692 c.Platform.BareMetal.IngressVIPs = []string{"ffd0::1"} 1693 1694 return c 1695 }(), 1696 expectedError: "[networking.networkType: Invalid value: \"OpenShiftSDN\": networkType OpenShiftSDN is not supported, please use OVNKubernetes, platform.baremetal.apiVIPs: Invalid value: \"10.0.0.5\": IP expected to be in one of the machine networks: ffd0::/48]", 1697 }, 1698 { 1699 name: "too_many_apivips", 1700 installConfig: func() *types.InstallConfig { 1701 c := validInstallConfig() 1702 c.Platform = types.Platform{ 1703 BareMetal: validBareMetalPlatform(), 1704 } 1705 c.Platform.BareMetal.APIVIPs = []string{"fe80::1", "fe80::2", "fe80::3"} 1706 1707 return c 1708 }(), 1709 expectedError: "platform.baremetal.apiVIPs: Too many: 3: must have at most 2 items", 1710 }, 1711 { 1712 name: "invalid_apivip", 1713 installConfig: func() *types.InstallConfig { 1714 c := validInstallConfig() 1715 c.Platform = types.Platform{ 1716 BareMetal: validBareMetalPlatform(), 1717 } 1718 c.Platform.BareMetal.APIVIPs = []string{""} 1719 1720 return c 1721 }(), 1722 expectedError: "platform.baremetal.apiVIPs: Invalid value: \"\": \"\" is not a valid IP", 1723 }, 1724 { 1725 name: "invalid_apivip_2", 1726 installConfig: func() *types.InstallConfig { 1727 c := validInstallConfig() 1728 c.Platform = types.Platform{ 1729 BareMetal: validBareMetalPlatform(), 1730 } 1731 c.Platform.BareMetal.APIVIPs = []string{"123.456.789.000"} 1732 1733 return c 1734 }(), 1735 expectedError: "platform.baremetal.apiVIPs: Invalid value: \"123.456.789.000\": \"123.456.789.000\" is not a valid IP", 1736 }, 1737 { 1738 name: "invalid_apivip_format", 1739 installConfig: func() *types.InstallConfig { 1740 c := validInstallConfig() 1741 c.Platform = types.Platform{ 1742 BareMetal: validBareMetalPlatform(), 1743 } 1744 c.Platform.BareMetal.APIVIPs = []string{"foobar"} 1745 1746 return c 1747 }(), 1748 expectedError: "platform.baremetal.apiVIPs: Invalid value: \"foobar\": \"foobar\" is not a valid IP", 1749 }, 1750 { 1751 name: "invalid_apivip_format_one_of_many", 1752 installConfig: func() *types.InstallConfig { 1753 c := validInstallConfig() 1754 c.Platform = types.Platform{ 1755 BareMetal: validBareMetalPlatform(), 1756 } 1757 c.Platform.BareMetal.APIVIPs = []string{"192.168.1.0", "foobar"} 1758 1759 return c 1760 }(), 1761 expectedError: "platform.baremetal.apiVIPs: Invalid value: \"foobar\": \"foobar\" is not a valid IP", 1762 }, 1763 { 1764 name: "invalid_apivips_both_ipv4", 1765 installConfig: func() *types.InstallConfig { 1766 c := validInstallConfig() 1767 c.Platform = types.Platform{ 1768 BareMetal: validBareMetalPlatform(), 1769 } 1770 c.Platform.BareMetal.APIVIPs = []string{"192.168.111.1", "192.168.111.2"} 1771 1772 return c 1773 }(), 1774 expectedError: "platform.baremetal.apiVIPs: Invalid value: \\[\\]string\\{\"192.168.111.1\", \"192.168.111.2\"\\}: If two API VIPs are given, one must be an IPv4 address, the other an IPv6", 1775 }, 1776 { 1777 name: "invalid_apis_both_ipv6", 1778 installConfig: func() *types.InstallConfig { 1779 c := validInstallConfig() 1780 c.Platform = types.Platform{ 1781 BareMetal: validBareMetalPlatform(), 1782 } 1783 c.Platform.BareMetal.APIVIPs = []string{"fe80::1", "fe80::2"} 1784 1785 return c 1786 }(), 1787 expectedError: "platform.baremetal.apiVIPs: Invalid value: \\[\\]string\\{\"fe80::1\", \"fe80::2\"\\}: If two API VIPs are given, one must be an IPv4 address, the other an IPv6", 1788 }, 1789 { 1790 name: "ingressvip_v4_not_in_machinenetwork_cidr", 1791 installConfig: func() *types.InstallConfig { 1792 c := validInstallConfig() 1793 c.Networking.MachineNetwork = []types.MachineNetworkEntry{ 1794 {CIDR: *ipnet.MustParseCIDR("10.0.0.0/16")}, 1795 {CIDR: *ipnet.MustParseCIDR("fe80::/10")}, 1796 } 1797 c.Platform = types.Platform{ 1798 BareMetal: validBareMetalPlatform(), 1799 } 1800 c.Platform.BareMetal.IngressVIPs = []string{"192.168.222.4"} 1801 1802 return c 1803 }(), 1804 expectedError: "platform.baremetal.ingressVIPs: Invalid value: \"192.168.222.4\": IP expected to be in one of the machine networks: 10.0.0.0/16,fe80::/10", 1805 }, 1806 { 1807 name: "ingressvip_v6_not_in_machinenetwork_cidr", 1808 installConfig: func() *types.InstallConfig { 1809 c := validInstallConfig() 1810 c.Networking.MachineNetwork = []types.MachineNetworkEntry{ 1811 {CIDR: *ipnet.MustParseCIDR("10.0.0.0/16")}, 1812 {CIDR: *ipnet.MustParseCIDR("fe80::/10")}, 1813 } 1814 c.Platform = types.Platform{ 1815 BareMetal: validBareMetalPlatform(), 1816 } 1817 c.Platform.BareMetal.IngressVIPs = []string{"2001::1"} 1818 1819 return c 1820 }(), 1821 expectedError: "platform.baremetal.ingressVIPs: Invalid value: \"2001::1\": IP expected to be in one of the machine networks: 10.0.0.0/16,fe80::/10", 1822 }, 1823 { 1824 name: "vsphere_ingressvip_v4_not_in_machinenetwork_cidr", 1825 installConfig: func() *types.InstallConfig { 1826 c := validInstallConfig() 1827 c.Networking.MachineNetwork = []types.MachineNetworkEntry{ 1828 {CIDR: *ipnet.MustParseCIDR("10.0.0.0/16")}, 1829 {CIDR: *ipnet.MustParseCIDR("fe80::/10")}, 1830 } 1831 c.Platform = types.Platform{ 1832 VSphere: validVSpherePlatform(), 1833 } 1834 c.Platform.VSphere.IngressVIPs = []string{"192.168.222.4"} 1835 c.Platform.VSphere.APIVIPs = []string{"192.168.1.0"} 1836 1837 return c 1838 }(), 1839 }, 1840 { 1841 name: "too_many_ingressvips", 1842 installConfig: func() *types.InstallConfig { 1843 c := validInstallConfig() 1844 c.Platform = types.Platform{ 1845 BareMetal: validBareMetalPlatform(), 1846 } 1847 c.Platform.BareMetal.IngressVIPs = []string{"fe80::1", "fe80::2", "fe80::3"} 1848 1849 return c 1850 }(), 1851 expectedError: "platform.baremetal.ingressVIPs: Too many: 3: must have at most 2 items", 1852 }, 1853 { 1854 name: "invalid_ingressvip", 1855 installConfig: func() *types.InstallConfig { 1856 c := validInstallConfig() 1857 c.Platform = types.Platform{ 1858 BareMetal: validBareMetalPlatform(), 1859 } 1860 c.Platform.BareMetal.IngressVIPs = []string{""} 1861 1862 return c 1863 }(), 1864 expectedError: "platform.baremetal.ingressVIPs: Invalid value: \"\": \"\" is not a valid IP", 1865 }, 1866 { 1867 name: "invalid_ingressvip_format", 1868 installConfig: func() *types.InstallConfig { 1869 c := validInstallConfig() 1870 c.Platform = types.Platform{ 1871 BareMetal: validBareMetalPlatform(), 1872 } 1873 c.Platform.BareMetal.IngressVIPs = []string{"foobar"} 1874 1875 return c 1876 }(), 1877 expectedError: "platform.baremetal.ingressVIPs: Invalid value: \"foobar\": \"foobar\" is not a valid IP", 1878 }, 1879 { 1880 name: "invalid_ingressvip_format_one_of_many", 1881 installConfig: func() *types.InstallConfig { 1882 c := validInstallConfig() 1883 c.Platform = types.Platform{ 1884 BareMetal: validBareMetalPlatform(), 1885 } 1886 c.Platform.BareMetal.IngressVIPs = []string{"192.1.1.1", "foobar"} 1887 1888 return c 1889 }(), 1890 expectedError: "platform.baremetal.ingressVIPs: Invalid value: \"foobar\": \"foobar\" is not a valid IP", 1891 }, 1892 { 1893 name: "invalid_ingressvips_both_ipv4", 1894 installConfig: func() *types.InstallConfig { 1895 c := validInstallConfig() 1896 c.Platform = types.Platform{ 1897 BareMetal: validBareMetalPlatform(), 1898 } 1899 c.Platform.BareMetal.IngressVIPs = []string{"192.168.111.4", "192.168.111.5"} 1900 1901 return c 1902 }(), 1903 expectedError: "platform.baremetal.ingressVIPs: Invalid value: \\[\\]string\\{\"192.168.111.4\", \"192.168.111.5\"\\}: If two Ingress VIPs are given, one must be an IPv4 address, the other an IPv6", 1904 }, 1905 { 1906 name: "invalid_ingressvips_both_ipv6", 1907 installConfig: func() *types.InstallConfig { 1908 c := validInstallConfig() 1909 c.Platform = types.Platform{ 1910 BareMetal: validBareMetalPlatform(), 1911 } 1912 c.Platform.BareMetal.IngressVIPs = []string{"fe80::1", "fe80::2"} 1913 1914 return c 1915 }(), 1916 expectedError: "platform.baremetal.ingressVIPs: Invalid value: \\[\\]string\\{\"fe80::1\", \"fe80::2\"\\}: If two Ingress VIPs are given, one must be an IPv4 address, the other an IPv6", 1917 }, 1918 { 1919 name: "identical_apivip_ingressvip", 1920 installConfig: func() *types.InstallConfig { 1921 c := validInstallConfig() 1922 c.Platform = types.Platform{ 1923 BareMetal: validBareMetalPlatform(), 1924 } 1925 c.Platform.BareMetal.APIVIPs = []string{"fe80::1"} 1926 c.Platform.BareMetal.IngressVIPs = []string{"fe80::1"} 1927 1928 return c 1929 }(), 1930 expectedError: "platform.baremetal.apiVIPs: Invalid value: \"fe80::1\": VIP for API must not be one of the Ingress VIPs", 1931 }, 1932 { 1933 name: "identical_apivip_ingressvip_usermanaged_loadbalancer", 1934 installConfig: func() *types.InstallConfig { 1935 c := validInstallConfig() 1936 c.FeatureSet = configv1.TechPreviewNoUpgrade 1937 c.Platform = types.Platform{ 1938 BareMetal: validBareMetalPlatform(), 1939 } 1940 c.Platform.BareMetal.LoadBalancer = &configv1.BareMetalPlatformLoadBalancer{Type: configv1.LoadBalancerTypeUserManaged} 1941 c.Platform.BareMetal.APIVIPs = []string{"fe80::1"} 1942 c.Platform.BareMetal.IngressVIPs = []string{"fe80::1"} 1943 1944 return c 1945 }(), 1946 }, 1947 { 1948 name: "identical_apivips_ingressvips_multiple_ips", 1949 installConfig: func() *types.InstallConfig { 1950 c := validInstallConfig() 1951 c.Platform = types.Platform{ 1952 BareMetal: validBareMetalPlatform(), 1953 } 1954 c.Platform.BareMetal.APIVIPs = []string{"fe80::1", "192.1.2.3"} 1955 c.Platform.BareMetal.IngressVIPs = []string{"fe80::1", "192.1.2.4"} 1956 1957 return c 1958 }(), 1959 expectedError: "platform.baremetal.apiVIPs: Invalid value: \"fe80::1\": VIP for API must not be one of the Ingress VIPs", 1960 }, 1961 { 1962 name: "apivip_ingressvip_are_synonyms", 1963 installConfig: func() *types.InstallConfig { 1964 c := validInstallConfig() 1965 c.Platform = types.Platform{ 1966 BareMetal: validBareMetalPlatform(), 1967 } 1968 c.Platform.BareMetal.APIVIPs = []string{"2001:db8::5"} 1969 c.Platform.BareMetal.IngressVIPs = []string{"2001:db8:0::5"} 1970 1971 return c 1972 }(), 1973 expectedError: "platform.baremetal.apiVIPs: Invalid value: \"2001:db8::5\": VIP for API must not be one of the Ingress VIPs", 1974 }, 1975 { 1976 name: "empty_api_vip_fields", 1977 installConfig: func() *types.InstallConfig { 1978 c := validInstallConfig() 1979 c.Platform = types.Platform{ 1980 BareMetal: validBareMetalPlatform(), 1981 } 1982 c.Platform.BareMetal.DeprecatedAPIVIP = "" 1983 c.Platform.BareMetal.APIVIPs = []string{} 1984 1985 return c 1986 }(), 1987 expectedError: "platform.baremetal.apiVIPs: Required value: must specify at least one VIP for the API", 1988 }, 1989 { 1990 name: "empty_ingress_vip_fields", 1991 installConfig: func() *types.InstallConfig { 1992 c := validInstallConfig() 1993 c.Platform = types.Platform{ 1994 BareMetal: validBareMetalPlatform(), 1995 } 1996 c.Platform.BareMetal.DeprecatedIngressVIP = "" 1997 c.Platform.BareMetal.IngressVIPs = []string{} 1998 1999 return c 2000 }(), 2001 expectedError: "platform.baremetal.ingressVIPs: Required value: must specify at least one VIP for the Ingress", 2002 }, 2003 { 2004 name: "baremetal API VIP set to an incorrect IP Family", 2005 installConfig: func() *types.InstallConfig { 2006 c := validInstallConfig() 2007 c.Networking = validDualStackNetworkingConfig() 2008 c.Platform = types.Platform{ 2009 BareMetal: validBareMetalPlatform(), 2010 } 2011 c.Platform.BareMetal.APIVIPs = []string{"ffd0::"} 2012 return c 2013 }(), 2014 expectedError: `platform.baremetal.apiVIPs: Invalid value: "ffd0::": VIP for the API must be of the same IP family with machine network's primary IP Family for dual-stack IPv4/IPv6`, 2015 }, 2016 { 2017 name: "baremetal Ingress VIP set to an incorrect IP Family", 2018 installConfig: func() *types.InstallConfig { 2019 c := validInstallConfig() 2020 c.Networking = validDualStackNetworkingConfig() 2021 c.Platform = types.Platform{ 2022 BareMetal: validBareMetalPlatform(), 2023 } 2024 c.Platform.BareMetal.IngressVIPs = []string{"ffd0::"} 2025 return c 2026 }(), 2027 expectedError: `platform.baremetal.ingressVIPs: Invalid value: "ffd0::": VIP for the Ingress must be of the same IP family with machine network's primary IP Family for dual-stack IPv4/IPv6`, 2028 }, 2029 { 2030 name: "should validate vips on baremetal (required)", 2031 installConfig: func() *types.InstallConfig { 2032 c := validInstallConfig() 2033 c.Platform = types.Platform{ 2034 BareMetal: validBareMetalPlatform(), 2035 } 2036 c.Platform.BareMetal.DeprecatedAPIVIP = "" 2037 c.Platform.BareMetal.APIVIPs = []string{} 2038 2039 return c 2040 }(), 2041 expectedError: "platform.baremetal.apiVIPs: Required value: must specify at least one VIP for the API", 2042 }, 2043 { 2044 name: "should validate vips on OpenStack (vips are required on openstack)", 2045 installConfig: func() *types.InstallConfig { 2046 c := validInstallConfig() 2047 c.Platform = types.Platform{ 2048 OpenStack: validOpenStackPlatform(), 2049 } 2050 c.Platform.OpenStack.DeprecatedAPIVIP = "" 2051 c.Platform.OpenStack.APIVIPs = []string{} 2052 2053 return c 2054 }(), 2055 expectedError: "platform.openstack.apiVIPs: Required value: must specify at least one VIP for the API", 2056 }, 2057 // { 2058 // name: "should not validate vips on OpenStack if not set (vips are not required on openstack)", 2059 // installConfig: func() *types.InstallConfig { 2060 // c := validInstallConfig() 2061 // c.Platform = types.Platform{ 2062 // OpenStack: validOpenStackPlatform(), 2063 // } 2064 // c.Platform.OpenStack.DeprecatedAPIVIP = "" 2065 // c.Platform.OpenStack.APIVIPs = []string{} 2066 2067 // return c 2068 // }(), 2069 // }, 2070 { 2071 name: "should validate vips on OpenStack if set (vips are not required on openstack)", 2072 installConfig: func() *types.InstallConfig { 2073 c := validInstallConfig() 2074 c.Platform = types.Platform{ 2075 OpenStack: validOpenStackPlatform(), 2076 } 2077 c.Platform.OpenStack.APIVIPs = []string{"foobar"} 2078 2079 return c 2080 }(), 2081 expectedError: "platform.openstack.apiVIPs: Invalid value: \"foobar\": \"foobar\" is not a valid IP", 2082 }, 2083 { 2084 name: "should not validate vips on VSphere if not set (vips are not required on VSphere)", 2085 installConfig: func() *types.InstallConfig { 2086 c := validInstallConfig() 2087 c.Platform = types.Platform{ 2088 VSphere: validVSpherePlatform(), 2089 } 2090 c.Platform.VSphere.DeprecatedAPIVIP = "" 2091 c.Platform.VSphere.APIVIPs = []string{} 2092 2093 return c 2094 }(), 2095 }, 2096 { 2097 name: "should validate vips on VSphere if set (vips are not required on VSphere)", 2098 installConfig: func() *types.InstallConfig { 2099 c := validInstallConfig() 2100 c.Platform = types.Platform{ 2101 VSphere: validVSpherePlatform(), 2102 } 2103 c.Platform.VSphere.APIVIPs = []string{"foobar"} 2104 2105 return c 2106 }(), 2107 expectedError: "platform.vsphere.apiVIPs: Invalid value: \"foobar\": \"foobar\" is not a valid IP", 2108 }, 2109 { 2110 name: "should not validate vips on Nutanix if not set (vips are not required on Nutanix)", 2111 installConfig: func() *types.InstallConfig { 2112 c := validInstallConfig() 2113 c.Platform = types.Platform{ 2114 Nutanix: validNutanixPlatform(), 2115 } 2116 c.Platform.Nutanix.DeprecatedAPIVIP = "" 2117 c.Platform.Nutanix.APIVIPs = []string{} 2118 2119 return c 2120 }(), 2121 }, 2122 { 2123 name: "should validate vips on Nutanix if set (vips are not required on Nutanix)", 2124 installConfig: func() *types.InstallConfig { 2125 c := validInstallConfig() 2126 c.Platform = types.Platform{ 2127 Nutanix: validNutanixPlatform(), 2128 } 2129 c.Platform.Nutanix.APIVIPs = []string{"foobar"} 2130 2131 return c 2132 }(), 2133 expectedError: "platform.nutanix.apiVIPs: Invalid value: \"foobar\": \"foobar\" is not a valid IP", 2134 }, 2135 { 2136 name: "should return error if only API VIP is set", 2137 installConfig: func() *types.InstallConfig { 2138 c := validInstallConfig() 2139 c.Platform = types.Platform{ 2140 VSphere: validVSpherePlatform(), 2141 } 2142 c.Platform.VSphere.APIVIPs = []string{"10.0.0.1"} 2143 c.Platform.VSphere.IngressVIPs = []string{} 2144 2145 return c 2146 }(), 2147 expectedError: "platform.vsphere.ingressVIPs: Required value: must specify VIP for ingress, when VIP for API is set", 2148 }, 2149 { 2150 name: "should return error if only Ingress VIP is set", 2151 installConfig: func() *types.InstallConfig { 2152 c := validInstallConfig() 2153 c.Platform = types.Platform{ 2154 VSphere: validVSpherePlatform(), 2155 } 2156 c.Platform.VSphere.APIVIPs = []string{} 2157 c.Platform.VSphere.IngressVIPs = []string{"10.0.0.1"} 2158 2159 return c 2160 }(), 2161 expectedError: "platform.vsphere.apiVIPs: Required value: must specify VIP for API, when VIP for ingress is set", 2162 }, 2163 { 2164 name: "valid custom features", 2165 installConfig: func() *types.InstallConfig { 2166 c := validInstallConfig() 2167 c.FeatureSet = configv1.CustomNoUpgrade 2168 c.FeatureGates = []string{ 2169 "CustomFeature1=True", 2170 "CustomFeature2=False", 2171 } 2172 return c 2173 }(), 2174 }, 2175 { 2176 name: "invalid custom features", 2177 installConfig: func() *types.InstallConfig { 2178 c := validInstallConfig() 2179 c.FeatureSet = configv1.CustomNoUpgrade 2180 c.FeatureGates = []string{ 2181 "CustomFeature1=True", 2182 "CustomFeature2", 2183 } 2184 return c 2185 }(), 2186 expectedError: `featureGates\[1\]: Invalid value: "CustomFeature2": must match the format <feature-name>=<bool>`, 2187 }, 2188 { 2189 name: "invalid custom features bool", 2190 installConfig: func() *types.InstallConfig { 2191 c := validInstallConfig() 2192 c.FeatureSet = configv1.CustomNoUpgrade 2193 c.FeatureGates = []string{ 2194 "CustomFeature1=foo", 2195 "CustomFeature2=False", 2196 } 2197 return c 2198 }(), 2199 expectedError: `featureGates\[0\]: Invalid value: "CustomFeature1=foo": must match the format <feature-name>=<bool>, could not parse boolean value`, 2200 }, 2201 { 2202 name: "custom features supplied with non-custom featureset", 2203 installConfig: func() *types.InstallConfig { 2204 c := validInstallConfig() 2205 c.FeatureSet = configv1.TechPreviewNoUpgrade 2206 c.FeatureGates = []string{ 2207 "CustomFeature1=True", 2208 "CustomFeature2=False", 2209 } 2210 return c 2211 }(), 2212 expectedError: "featureGates: Forbidden: featureGates can only be used with the CustomNoUpgrade feature set", 2213 }, 2214 { 2215 name: "valid disabled MAPI with baseline none and baremetal enabled", 2216 installConfig: func() *types.InstallConfig { 2217 c := validInstallConfig() 2218 c.Capabilities = &types.Capabilities{ 2219 BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone, 2220 AdditionalEnabledCapabilities: []configv1.ClusterVersionCapability{configv1.ClusterVersionCapabilityBaremetal, configv1.ClusterVersionCapabilityIngress, configv1.ClusterVersionCapabilityCloudCredential, configv1.ClusterVersionCapabilityCloudControllerManager}, 2221 } 2222 return c 2223 }(), 2224 }, 2225 { 2226 name: "valid disabled MAPI capability configuration", 2227 installConfig: func() *types.InstallConfig { 2228 c := validInstallConfig() 2229 c.Capabilities = &types.Capabilities{ 2230 BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone, 2231 } 2232 c.Capabilities.AdditionalEnabledCapabilities = append(c.Capabilities.AdditionalEnabledCapabilities, configv1.ClusterVersionCapabilityCloudCredential, configv1.ClusterVersionCapabilityCloudControllerManager, configv1.ClusterVersionCapabilityIngress) 2233 return c 2234 }(), 2235 }, 2236 { 2237 name: "valid enabled MAPI capability configuration", 2238 installConfig: func() *types.InstallConfig { 2239 c := validInstallConfig() 2240 c.Capabilities = &types.Capabilities{ 2241 BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone, 2242 AdditionalEnabledCapabilities: []configv1.ClusterVersionCapability{configv1.ClusterVersionCapabilityBaremetal, configv1.ClusterVersionCapabilityMachineAPI, configv1.ClusterVersionCapabilityCloudCredential, configv1.ClusterVersionCapabilityCloudControllerManager, configv1.ClusterVersionCapabilityIngress}, 2243 } 2244 return c 2245 }(), 2246 }, 2247 { 2248 name: "valid enabled MAPI capability configuration 2", 2249 installConfig: func() *types.InstallConfig { 2250 c := validInstallConfig() 2251 c.Capabilities = &types.Capabilities{ 2252 BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone, 2253 AdditionalEnabledCapabilities: []configv1.ClusterVersionCapability{configv1.ClusterVersionCapabilityMachineAPI, configv1.ClusterVersionCapabilityCloudCredential, configv1.ClusterVersionCapabilityCloudControllerManager, configv1.ClusterVersionCapabilityIngress}, 2254 } 2255 return c 2256 }(), 2257 }, 2258 { 2259 name: "CloudCredential is enabled in cloud", 2260 installConfig: func() *types.InstallConfig { 2261 c := validInstallConfig() 2262 c.Capabilities = &types.Capabilities{ 2263 BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetCurrent, 2264 } 2265 return c 2266 }(), 2267 }, 2268 { 2269 name: "CloudCredential is disabled in cloud aws", 2270 installConfig: func() *types.InstallConfig { 2271 c := validInstallConfig() 2272 c.Capabilities = &types.Capabilities{ 2273 BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone, 2274 } 2275 return c 2276 }(), 2277 expectedError: "disabling CloudCredential capability available only for baremetal platforms", 2278 }, 2279 { 2280 name: "CloudCredential is disabled in cloud gcp", 2281 installConfig: func() *types.InstallConfig { 2282 c := validInstallConfig() 2283 c.GCP = validGCPPlatform() 2284 c.AWS = nil 2285 c.Capabilities = &types.Capabilities{ 2286 BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone, 2287 } 2288 return c 2289 }(), 2290 expectedError: "disabling CloudCredential capability available only for baremetal platforms", 2291 }, 2292 { 2293 name: "CloudCredential is enabled in baremetal", 2294 installConfig: func() *types.InstallConfig { 2295 c := validInstallConfig() 2296 c.BareMetal = validBareMetalPlatform() 2297 c.AWS = nil 2298 c.Capabilities = &types.Capabilities{ 2299 BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetCurrent, 2300 } 2301 return c 2302 }(), 2303 }, 2304 { 2305 name: "CloudCredential is disabled in baremetal", 2306 installConfig: func() *types.InstallConfig { 2307 c := validInstallConfig() 2308 c.BareMetal = validBareMetalPlatform() 2309 c.AWS = nil 2310 c.Capabilities = &types.Capabilities{ 2311 BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone, 2312 AdditionalEnabledCapabilities: []configv1.ClusterVersionCapability{configv1.ClusterVersionCapabilityBaremetal, configv1.ClusterVersionCapabilityMachineAPI, configv1.ClusterVersionCapabilityIngress}, 2313 } 2314 return c 2315 }(), 2316 }, 2317 { 2318 name: "CloudController can't be disabled on cloud", 2319 installConfig: func() *types.InstallConfig { 2320 c := validInstallConfig() 2321 c.Capabilities = &types.Capabilities{ 2322 BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone, 2323 } 2324 return c 2325 }(), 2326 expectedError: "disabling CloudControllerManager is only supported on the Baremetal, None, or External platform with cloudControllerManager value none", 2327 }, 2328 { 2329 name: "valid disabled CloudController configuration none platform", 2330 installConfig: func() *types.InstallConfig { 2331 c := validInstallConfig() 2332 c.Platform.AWS = nil 2333 c.Platform.None = &none.Platform{} 2334 c.Capabilities = &types.Capabilities{ 2335 BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone, 2336 AdditionalEnabledCapabilities: []configv1.ClusterVersionCapability{configv1.ClusterVersionCapabilityIngress}, 2337 } 2338 return c 2339 }(), 2340 }, 2341 { 2342 name: "valid disabled CloudController configuration platform baremetal", 2343 installConfig: func() *types.InstallConfig { 2344 c := validInstallConfig() 2345 c.Platform.AWS = nil 2346 c.Platform.BareMetal = validBareMetalPlatform() 2347 c.Capabilities = &types.Capabilities{ 2348 BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone, 2349 AdditionalEnabledCapabilities: []configv1.ClusterVersionCapability{configv1.ClusterVersionCapabilityBaremetal, configv1.ClusterVersionCapabilityMachineAPI, configv1.ClusterVersionCapabilityIngress}, 2350 } 2351 return c 2352 }(), 2353 }, 2354 { 2355 name: "valid disabled CloudController configuration platform External", 2356 installConfig: func() *types.InstallConfig { 2357 c := validInstallConfig() 2358 c.Platform.AWS = nil 2359 c.Platform.External = &external.Platform{} 2360 c.Capabilities = &types.Capabilities{ 2361 BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone, 2362 AdditionalEnabledCapabilities: []configv1.ClusterVersionCapability{configv1.ClusterVersionCapabilityCloudCredential, configv1.ClusterVersionCapabilityIngress}, 2363 } 2364 return c 2365 }(), 2366 }, 2367 { 2368 name: "valid disabled CloudController configuration platform External 2", 2369 installConfig: func() *types.InstallConfig { 2370 c := validInstallConfig() 2371 c.Platform.AWS = nil 2372 c.Platform.External = &external.Platform{ 2373 CloudControllerManager: external.CloudControllerManagerTypeNone, 2374 } 2375 c.Capabilities = &types.Capabilities{ 2376 BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone, 2377 AdditionalEnabledCapabilities: []configv1.ClusterVersionCapability{configv1.ClusterVersionCapabilityCloudCredential, configv1.ClusterVersionCapabilityIngress}, 2378 } 2379 return c 2380 }(), 2381 }, 2382 { 2383 name: "invalid disabled CloudController configuration platform External 2", 2384 installConfig: func() *types.InstallConfig { 2385 c := validInstallConfig() 2386 c.Platform.AWS = nil 2387 c.Platform.External = &external.Platform{ 2388 CloudControllerManager: external.CloudControllerManagerTypeExternal, 2389 } 2390 c.Capabilities = &types.Capabilities{ 2391 BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone, 2392 AdditionalEnabledCapabilities: []configv1.ClusterVersionCapability{configv1.ClusterVersionCapabilityCloudCredential}, 2393 } 2394 return c 2395 }(), 2396 expectedError: "disabling CloudControllerManager on External platform supported only with cloudControllerManager value none", 2397 }, 2398 { 2399 name: "Ingress can't be disabled", 2400 installConfig: func() *types.InstallConfig { 2401 c := validInstallConfig() 2402 c.Capabilities = &types.Capabilities{ 2403 BaselineCapabilitySet: configv1.ClusterVersionCapabilitySetNone, 2404 } 2405 return c 2406 }(), 2407 expectedError: "the Ingress capability is required", 2408 }, 2409 } 2410 for _, tc := range cases { 2411 t.Run(tc.name, func(t *testing.T) { 2412 err := ValidateInstallConfig(tc.installConfig, false).ToAggregate() 2413 if tc.expectedError == "" { 2414 assert.NoError(t, err) 2415 } else { 2416 assert.Regexp(t, tc.expectedError, err) 2417 } 2418 }) 2419 } 2420 } 2421 2422 func Test_ensureIPv4IsFirstInDualStackSlice(t *testing.T) { 2423 tests := []struct { 2424 name string 2425 vips []string 2426 want []string 2427 wantErr bool 2428 }{ 2429 { 2430 name: "should switch VIPs", 2431 vips: []string{"fe80::0", "192.168.1.1"}, 2432 want: []string{"192.168.1.1", "fe80::0"}, 2433 wantErr: false, 2434 }, 2435 { 2436 name: "should do nothing on single stack", 2437 vips: []string{"192.168.1.1"}, 2438 want: []string{"192.168.1.1"}, 2439 wantErr: false, 2440 }, 2441 { 2442 name: "should do nothing on correct order", 2443 vips: []string{"192.168.1.1", "fe80::0"}, 2444 want: []string{"192.168.1.1", "fe80::0"}, 2445 wantErr: false, 2446 }, 2447 { 2448 name: "return error on invalid number of vips", 2449 vips: []string{"192.168.1.1", "fe80::0", "192.168.1.1"}, 2450 want: nil, 2451 wantErr: true, 2452 }, 2453 } 2454 for _, tt := range tests { 2455 t.Run(tt.name, func(t *testing.T) { 2456 if err := ensureIPv4IsFirstInDualStackSlice(&tt.vips, field.NewPath("test")); (len(err) > 0) != tt.wantErr { 2457 t.Errorf("ensureIPv4IsFirstInDualStackSlice() error = %v, wantErr %v", err, tt.wantErr) 2458 } 2459 if !tt.wantErr && !utilsslice.Equal(tt.vips, tt.want) && len(tt.vips) == 2 { 2460 t.Errorf("ensureIPv4IsFirstInDualStackSlice() changed to %v, expected %v", tt.vips, tt.want) 2461 } 2462 }) 2463 } 2464 }