diff --git a/data/data/install.openshift.io_installconfigs.yaml b/data/data/install.openshift.io_installconfigs.yaml index ec229f4c2db..2553a285710 100644 --- a/data/data/install.openshift.io_installconfigs.yaml +++ b/data/data/install.openshift.io_installconfigs.yaml @@ -1448,6 +1448,8 @@ spec: type: string name: type: string + networkConfig: + type: string role: type: string rootDeviceHints: diff --git a/docs/user/metal/install_ipi.md b/docs/user/metal/install_ipi.md index 5d02d360b52..7cb82e6e947 100644 --- a/docs/user/metal/install_ipi.md +++ b/docs/user/metal/install_ipi.md @@ -233,6 +233,7 @@ should be used to build the cluster. The number of assets must be at least great | `bootMACAddress` | | The MAC address of the NIC the host will use to boot on the provisioning network. It must be unique. | | `rootDeviceHints` | | How to choose the target disk for the OS during provisioning - for more details see [upstream docs](https://github.com/metal3-io/baremetal-operator/blob/master/docs/api.md). | | `bootMode` | `UEFI` | Choose `legacy` (BIOS) or `UEFI` mode for booting. Use `UEFISecureBoot` to enable UEFI and secure boot on the server. Only some drivers support UEFI secure boot (notably, IPMI does not). | +| `networkConfig` | | Yaml string describing the desired host networking settings. Must be compatible with NMState (for more details see https://nmstate.io/) | The `bmc` parameter for each host is a set of values for accessing the baseboard management controller in the host. diff --git a/pkg/types/baremetal/platform.go b/pkg/types/baremetal/platform.go index ab2a2696988..9ca538eb61c 100644 --- a/pkg/types/baremetal/platform.go +++ b/pkg/types/baremetal/platform.go @@ -39,6 +39,7 @@ type Host struct { HardwareProfile string `json:"hardwareProfile"` RootDeviceHints *RootDeviceHints `json:"rootDeviceHints,omitempty"` BootMode BootMode `json:"bootMode,omitempty"` + NetworkConfig string `json:"networkConfig,omitempty"` } // IsMaster checks if the current host is a master diff --git a/pkg/types/baremetal/validation/platform.go b/pkg/types/baremetal/validation/platform.go index 200f6c5d9da..d9d39955bd0 100644 --- a/pkg/types/baremetal/validation/platform.go +++ b/pkg/types/baremetal/validation/platform.go @@ -10,6 +10,7 @@ import ( "strings" "github.com/apparentlymart/go-cidr/cidr" + "github.com/ghodss/yaml" "github.com/go-playground/validator/v10" "github.com/metal3-io/baremetal-operator/pkg/bmc" "github.com/pkg/errors" @@ -325,6 +326,18 @@ func validateHostsCount(hosts []*baremetal.Host, installConfig *types.InstallCon return nil } +// ensure that the NetworkConfig field contains a valid Yaml string +func validateNetworkConfig(hosts []*baremetal.Host, fldPath *field.Path) (errors field.ErrorList) { + for idx, host := range hosts { + networkConfig := make(map[string]interface{}) + err := yaml.Unmarshal([]byte(host.NetworkConfig), &networkConfig) + if err != nil { + errors = append(errors, field.Invalid(fldPath.Index(idx).Child("networkConfig"), host.NetworkConfig, fmt.Sprintf("Not a valid yaml: %s", err.Error()))) + } + } + return +} + // ensure that the bootMode field contains a valid value func validateBootMode(hosts []*baremetal.Host, fldPath *field.Path) (errors field.ErrorList) { for idx, host := range hosts { @@ -404,6 +417,7 @@ func ValidatePlatform(p *baremetal.Platform, n *types.Networking, fldPath *field allErrs = append(allErrs, validateHostsWithoutBMC(p.Hosts, fldPath)...) allErrs = append(allErrs, validateBootMode(p.Hosts, fldPath.Child("Hosts"))...) + allErrs = append(allErrs, validateNetworkConfig(p.Hosts, fldPath.Child("Hosts"))...) return allErrs } diff --git a/pkg/types/baremetal/validation/platform_test.go b/pkg/types/baremetal/validation/platform_test.go index 9e50fe68c07..fbb4d2c93d3 100644 --- a/pkg/types/baremetal/validation/platform_test.go +++ b/pkg/types/baremetal/validation/platform_test.go @@ -262,6 +262,39 @@ func TestValidatePlatform(t *testing.T) { platform: platform().ProvisioningNetwork("Invalid").build(), expected: `Unsupported value: "Invalid": supported values: "Disabled", "Managed", "Unmanaged"`, }, + { + name: "networkConfig_invalid", + platform: platform().Hosts(host1().NetworkConfig("Not a valid yaml content")).build(), + expected: ".*Invalid value.*Not a valid yaml: error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type map\\[string\\]interface \\{\\}", + }, + { + name: "networkConfig_valid_yml", + platform: platform().Hosts(host1().NetworkConfig(` +interfaces: +- name: eth1 + type: ethernet + state: up +- name: linux-br0 + type: linux-bridge + state: up + bridge: + options: + group-forward-mask: 0 + mac-ageing-time: 300 + multicast-snooping: true + stp: + enabled: true + forward-delay: 15 + hello-time: 2 + max-age: 20 + priority: 32768 + port: + - name: eth1 + stp-hairpin-mode: false + stp-path-cost: 100 + stp-priority: 32`)).build(), + expected: "", + }, } for _, tc := range cases { @@ -731,6 +764,11 @@ func (hb *hostBuilder) Role(value string) *hostBuilder { return hb } +func (hb *hostBuilder) NetworkConfig(value string) *hostBuilder { + hb.Host.NetworkConfig = value + return hb +} + type platformBuilder struct { baremetal.Platform }