From c6f4c29e2d5d6312e0365f7f486e849706a0570b Mon Sep 17 00:00:00 2001 From: Andrea Fasano Date: Mon, 6 Apr 2020 11:30:09 +0200 Subject: [PATCH] baremetal: validate platform hosts amount New validation rule to verify that the hosts specified in the baremetal platform configuration is at least the amount of configured replicas for ControlPlane and Compute nodes --- pkg/types/baremetal/validation/platform.go | 26 +++- .../baremetal/validation/platform_test.go | 116 +++++++++++++++--- pkg/types/validation/installconfig.go | 2 +- pkg/types/validation/installconfig_test.go | 33 +++-- 4 files changed, 154 insertions(+), 23 deletions(-) diff --git a/pkg/types/baremetal/validation/platform.go b/pkg/types/baremetal/validation/platform.go index d48acd7dee9..00c671010b0 100644 --- a/pkg/types/baremetal/validation/platform.go +++ b/pkg/types/baremetal/validation/platform.go @@ -153,8 +153,28 @@ func validateOSImages(p *baremetal.Platform, fldPath *field.Path) field.ErrorLis return platformErrs } +func validateHostsCount(hosts []*baremetal.Host, installConfig *types.InstallConfig) error { + + hostsNum := int64(len(hosts)) + counter := int64(0) + + for _, worker := range installConfig.Compute { + if worker.Replicas != nil { + counter += *worker.Replicas + } + } + if installConfig.ControlPlane != nil && installConfig.ControlPlane.Replicas != nil { + counter += *installConfig.ControlPlane.Replicas + } + if hostsNum < counter { + return fmt.Errorf("not enough hosts found (%v) to support all the configured ControlPlane and Compute replicas (%v)", hostsNum, counter) + } + + return nil +} + // ValidatePlatform checks that the specified platform is valid. -func ValidatePlatform(p *baremetal.Platform, n *types.Networking, fldPath *field.Path) field.ErrorList { +func ValidatePlatform(p *baremetal.Platform, n *types.Networking, fldPath *field.Path, c *types.InstallConfig) field.ErrorList { allErrs := field.ErrorList{} if err := validate.URI(p.LibvirtURI); err != nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("libvirtURI"), p.LibvirtURI, err.Error())) @@ -238,6 +258,10 @@ func ValidatePlatform(p *baremetal.Platform, n *types.Networking, fldPath *field allErrs = append(allErrs, field.Invalid(fldPath.Child("bootstrapHostIP"), p.BootstrapProvisioningIP, err.Error())) } + if err := validateHostsCount(p.Hosts, c); err != nil { + allErrs = append(allErrs, field.Required(fldPath.Child("Hosts"), err.Error())) + } + allErrs = append(allErrs, validateOSImages(p, fldPath)...) allErrs = append(allErrs, validateHosts(p.Hosts, fldPath)...) diff --git a/pkg/types/baremetal/validation/platform_test.go b/pkg/types/baremetal/validation/platform_test.go index ffd5d97736c..1bda85d5f66 100644 --- a/pkg/types/baremetal/validation/platform_test.go +++ b/pkg/types/baremetal/validation/platform_test.go @@ -51,6 +51,7 @@ func TestValidatePlatform(t *testing.T) { cases := []struct { name string + config *types.InstallConfig platform *baremetal.Platform expected string }{ @@ -218,61 +219,91 @@ func TestValidatePlatform(t *testing.T) { name: "duplicate_bmc_address", platform: platform(). Hosts( - host1().BMCAddress("ipmi://192.168.111.1").build(), - host2().BMCAddress("ipmi://192.168.111.1").build()).build(), + host1().BMCAddress("ipmi://192.168.111.1"), + host2().BMCAddress("ipmi://192.168.111.1")).build(), expected: "baremetal.hosts\\[1\\].BMC.Address: Duplicate value: \"ipmi://192.168.111.1\"", }, { name: "bmc_address_required", platform: platform(). - Hosts(host1().BMCAddress("").build()).build(), + Hosts(host1().BMCAddress("")).build(), expected: "baremetal.hosts\\[0\\].BMC.Address: Required value: missing Address", }, { name: "bmc_username_required", platform: platform(). - Hosts(host1().BMCUsername("").build()).build(), + Hosts(host1().BMCUsername("")).build(), expected: "baremetal.hosts\\[0\\].BMC.Username: Required value: missing Username", }, { name: "bmc_password_required", platform: platform(). - Hosts(host1().BMCPassword("").build()).build(), + Hosts(host1().BMCPassword("")).build(), expected: "baremetal.hosts\\[0\\].BMC.Password: Required value: missing Password", }, { name: "duplicate_host_name", platform: platform(). Hosts( - host1().Name("host1").build(), - host2().Name("host1").build()).build(), + host1().Name("host1"), + host2().Name("host1")).build(), expected: "baremetal.hosts\\[1\\].Name: Duplicate value: \"host1\"", }, { name: "duplicate_host_mac", platform: platform(). Hosts( - host1().BootMACAddress("CA:FE:CA:FE:CA:FE").build(), - host2().BootMACAddress("CA:FE:CA:FE:CA:FE").build()).build(), + host1().BootMACAddress("CA:FE:CA:FE:CA:FE"), + host2().BootMACAddress("CA:FE:CA:FE:CA:FE")).build(), expected: "baremetal.hosts\\[1\\].BootMACAddress: Duplicate value: \"CA:FE:CA:FE:CA:FE\"", }, { name: "missing_name", platform: platform(). - Hosts(host1().Name("").build()).build(), + Hosts(host1().Name("")).build(), expected: "baremetal.hosts\\[0\\].Name: Required value: missing Name", }, { name: "missing_mac", platform: platform(). - Hosts(host1().BootMACAddress("").build()).build(), + Hosts(host1().BootMACAddress("")).build(), expected: "baremetal.hosts\\[0\\].BootMACAddress: Required value: missing BootMACAddress", }, + { + name: "toofew_hosts", + config: installConfig(). + BareMetalPlatform( + platform().Hosts( + host1())). + ControlPlane( + machinePool().Replicas(3)). + Compute( + machinePool().Replicas(2), + machinePool().Replicas(3)).build(), + expected: "baremetal.Hosts: Required value: not enough hosts found \\(1\\) to support all the configured ControlPlane and Compute replicas \\(8\\)", + }, + { + name: "enough_hosts", + config: installConfig(). + BareMetalPlatform( + platform().Hosts( + host1(), + host2())). + ControlPlane( + machinePool().Replicas(2)).build(), + }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { - err := ValidatePlatform(tc.platform, network(), field.NewPath("baremetal")).ToAggregate() + //Build default wrapping installConfig + if tc.config == nil { + tc.config = installConfig().build() + tc.config.BareMetal = tc.platform + } + + err := ValidatePlatform(tc.config.BareMetal, network(), field.NewPath("baremetal"), tc.config).ToAggregate() + if tc.expected == "" { assert.NoError(t, err) } else { @@ -413,8 +444,11 @@ func (pb *platformBuilder) IngressVIP(value string) *platformBuilder { return pb } -func (pb *platformBuilder) Hosts(value ...*baremetal.Host) *platformBuilder { - pb.Platform.Hosts = value +func (pb *platformBuilder) Hosts(builders ...*hostBuilder) *platformBuilder { + pb.Platform.Hosts = nil + for _, builder := range builders { + pb.Platform.Hosts = append(pb.Platform.Hosts, builder.build()) + } return pb } @@ -441,3 +475,57 @@ func (pb *platformBuilder) ProvisioningNetworkInterface(value string) *platformB func network() *types.Networking { return &types.Networking{MachineNetwork: []types.MachineNetworkEntry{{CIDR: *ipnet.MustParseCIDR("192.168.111.0/24")}}} } + +type installConfigBuilder struct { + types.InstallConfig +} + +func installConfig() *installConfigBuilder { + return &installConfigBuilder{ + InstallConfig: types.InstallConfig{}, + } +} + +func (icb *installConfigBuilder) build() *types.InstallConfig { + return &icb.InstallConfig +} + +func (icb *installConfigBuilder) BareMetalPlatform(builder *platformBuilder) *installConfigBuilder { + icb.InstallConfig.Platform = types.Platform{ + BareMetal: builder.build(), + } + return icb +} + +func (icb *installConfigBuilder) ControlPlane(builder *machinePoolBuilder) *installConfigBuilder { + icb.InstallConfig.ControlPlane = builder.build() + + return icb +} + +func (icb *installConfigBuilder) Compute(builders ...*machinePoolBuilder) *installConfigBuilder { + icb.InstallConfig.Compute = nil + for _, builder := range builders { + icb.InstallConfig.Compute = append(icb.InstallConfig.Compute, *builder.build()) + } + return icb +} + +type machinePoolBuilder struct { + types.MachinePool +} + +func machinePool() *machinePoolBuilder { + return &machinePoolBuilder{ + MachinePool: types.MachinePool{}, + } +} + +func (mpb *machinePoolBuilder) build() *types.MachinePool { + return &mpb.MachinePool +} + +func (mpb *machinePoolBuilder) Replicas(count int64) *machinePoolBuilder { + mpb.MachinePool.Replicas = &count + return mpb +} diff --git a/pkg/types/validation/installconfig.go b/pkg/types/validation/installconfig.go index 0453693c8e0..cdf76a71e75 100644 --- a/pkg/types/validation/installconfig.go +++ b/pkg/types/validation/installconfig.go @@ -378,7 +378,7 @@ func validatePlatform(platform *types.Platform, fldPath *field.Path, openStackVa } if platform.BareMetal != nil { validate(baremetal.Name, platform.BareMetal, func(f *field.Path) field.ErrorList { - return baremetalvalidation.ValidatePlatform(platform.BareMetal, network, f) + return baremetalvalidation.ValidatePlatform(platform.BareMetal, network, f, c) }) } return allErrs diff --git a/pkg/types/validation/installconfig_test.go b/pkg/types/validation/installconfig_test.go index 15345fb2582..a2809390ba2 100644 --- a/pkg/types/validation/installconfig_test.go +++ b/pkg/types/validation/installconfig_test.go @@ -97,13 +97,32 @@ func validBareMetalPlatform() *baremetal.Platform { ProvisioningNetworkCIDR: ipnet.MustParseCIDR("192.168.111.0/24"), BootstrapProvisioningIP: "192.168.111.1", ClusterProvisioningIP: "192.168.111.2", - Hosts: []*baremetal.Host{}, - ExternalBridge: iface[0].Name, - ProvisioningBridge: iface[0].Name, - DefaultMachinePlatform: &baremetal.MachinePool{}, - APIVIP: "10.0.0.5", - IngressVIP: "10.0.0.4", - DNSVIP: "10.0.0.2", + Hosts: []*baremetal.Host{ + { + Name: "host1", + BootMACAddress: "CA:FE:CA:FE:00:00", + BMC: baremetal.BMC{ + Username: "root", + Password: "password", + Address: "ipmi://192.168.111.1", + }, + }, + { + Name: "host2", + BootMACAddress: "CA:FE:CA:FE:00:01", + BMC: baremetal.BMC{ + Username: "root", + Password: "password", + Address: "ipmi://192.168.111.2", + }, + }, + }, + ExternalBridge: iface[0].Name, + ProvisioningBridge: iface[0].Name, + DefaultMachinePlatform: &baremetal.MachinePool{}, + APIVIP: "10.0.0.5", + IngressVIP: "10.0.0.4", + DNSVIP: "10.0.0.2", } }