From e9d501cae6f5d3311bbab8736395fffa48d2347c Mon Sep 17 00:00:00 2001 From: Jad Haj Yahya Date: Thu, 12 Mar 2026 16:39:55 +0200 Subject: [PATCH] Add provisioningNetworkGateway field to install-config --- .../systemd/ironic-dnsmasq.container | 1 + .../baremetal/files/etc/ironic.env.template | 1 + .../install.openshift.io_installconfigs.yaml | 10 +++++++ ...aremetal-provisioning-config.yaml.template | 1 + .../ignition/bootstrap/baremetal/template.go | 4 +++ pkg/types/baremetal/platform.go | 11 ++++++++ pkg/types/baremetal/validation/platform.go | 27 +++++++++++++++++-- 7 files changed, 53 insertions(+), 2 deletions(-) diff --git a/data/data/bootstrap/baremetal/files/etc/containers/systemd/ironic-dnsmasq.container b/data/data/bootstrap/baremetal/files/etc/containers/systemd/ironic-dnsmasq.container index eaf450c9fbf..4d365f0e3f5 100644 --- a/data/data/bootstrap/baremetal/files/etc/containers/systemd/ironic-dnsmasq.container +++ b/data/data/bootstrap/baremetal/files/etc/containers/systemd/ironic-dnsmasq.container @@ -15,6 +15,7 @@ AddCapability=NET_ADMIN NET_RAW NET_BIND_SERVICE Volume=ironic.volume:/shared:z Environment="PROVISIONING_INTERFACE=${PROVISIONING_INTERFACE}" Environment="DHCP_RANGE=${DHCP_RANGE}" +Environment="GATEWAY_IP=${GATEWAY_IP}" Environment="HTTP_PORT=${HTTP_PORT}" [Service] diff --git a/data/data/bootstrap/baremetal/files/etc/ironic.env.template b/data/data/bootstrap/baremetal/files/etc/ironic.env.template index a523900840c..e3ee3c3dc02 100644 --- a/data/data/bootstrap/baremetal/files/etc/ironic.env.template +++ b/data/data/bootstrap/baremetal/files/etc/ironic.env.template @@ -3,6 +3,7 @@ HTTP_PORT=6180 # This DHCP range is used by dnsmasq to serve DHCP to the cluster. If empty # dnsmasq will only serve TFTP, and DHCP will be disabled. DHCP_RANGE="{{.PlatformData.BareMetal.ProvisioningDHCPRange}}" +GATEWAY_IP="{{.PlatformData.BareMetal.ProvisioningNetworkGateway}}" DHCP_ALLOW_MACS="{{.PlatformData.BareMetal.ProvisioningDHCPAllowList}}" # Used by ironic to allow ssh to running IPA instances IRONIC_RAMDISK_SSH_KEY="{{.SSHKey}}" diff --git a/data/data/install.openshift.io_installconfigs.yaml b/data/data/install.openshift.io_installconfigs.yaml index b287fd6f861..45d37f257b2 100644 --- a/data/data/install.openshift.io_installconfigs.yaml +++ b/data/data/install.openshift.io_installconfigs.yaml @@ -6107,6 +6107,16 @@ spec: description: ProvisioningNetworkCIDR defines the network to use for provisioning. type: string + provisioningNetworkGateway: + description: |- + ProvisioningNetworkGateway is the IP address of the default gateway + for the provisioning network. This gateway is provided to baremetal + hosts via DHCP to enable routing to external networks during + introspection and provisioning. This field is only honored when + provisioningNetwork is set to Managed (installer-managed DHCP). + It is ignored when provisioningNetwork is Unmanaged or Disabled. + format: ip + type: string provisioningNetworkInterface: description: |- ProvisioningNetworkInterface is the name of the network interface on a control plane diff --git a/data/data/manifests/openshift/baremetal-provisioning-config.yaml.template b/data/data/manifests/openshift/baremetal-provisioning-config.yaml.template index 11694f9efaf..eb425a7e405 100644 --- a/data/data/manifests/openshift/baremetal-provisioning-config.yaml.template +++ b/data/data/manifests/openshift/baremetal-provisioning-config.yaml.template @@ -8,6 +8,7 @@ spec: provisioningNetworkCIDR: "{{.Baremetal.ProvisioningNetworkCIDR}}" provisioningNetwork: "{{.Baremetal.ProvisioningNetwork}}" provisioningDHCPRange: "{{.Baremetal.ProvisioningDHCPRange}}" + provisioningNetworkGateway: "{{.Baremetal.ProvisioningNetworkGateway}}" provisioningOSDownloadURL: "{{.ProvisioningOSDownloadURL}}" additionalNTPServers: [{{range $index, $server := .Baremetal.AdditionalNTPServers}}{{if $index}},{{end}}"{{$server}}"{{end}}] watchAllNamespaces: false diff --git a/pkg/asset/ignition/bootstrap/baremetal/template.go b/pkg/asset/ignition/bootstrap/baremetal/template.go index cae7572ceb6..0669a9273d7 100644 --- a/pkg/asset/ignition/bootstrap/baremetal/template.go +++ b/pkg/asset/ignition/bootstrap/baremetal/template.go @@ -37,6 +37,9 @@ type TemplateData struct { // should be blank. ProvisioningDHCPRange string + // ProvisioningNetworkGateway is the IP address of the default gateway for the provisioning network. + ProvisioningNetworkGateway string + // ProvisioningDHCPAllowList contains a space-separated list of all of the control plane's boot // MAC addresses. Requests to bootstrap DHCP from other hosts will be ignored. ProvisioningDHCPAllowList string @@ -194,6 +197,7 @@ func GetTemplateData(config *baremetal.Platform, networks []types.MachineNetwork switch config.ProvisioningNetwork { case baremetal.ManagedProvisioningNetwork: cidr, _ := config.ProvisioningNetworkCIDR.Mask.Size() + templateData.ProvisioningNetworkGateway = config.ProvisioningNetworkGateway // When provisioning network is managed, we set a DHCP range including // netmask for dnsmasq. templateData.ProvisioningDHCPRange = fmt.Sprintf("%s,%d", config.ProvisioningDHCPRange, cidr) diff --git a/pkg/types/baremetal/platform.go b/pkg/types/baremetal/platform.go index 712a521cb50..081947b6545 100644 --- a/pkg/types/baremetal/platform.go +++ b/pkg/types/baremetal/platform.go @@ -166,6 +166,17 @@ type Platform struct { // +optional ProvisioningDHCPRange string `json:"provisioningDHCPRange,omitempty"` + // ProvisioningNetworkGateway is the IP address of the default gateway + // for the provisioning network. This gateway is provided to baremetal + // hosts via DHCP to enable routing to external networks during + // introspection and provisioning. This field is only honored when + // provisioningNetwork is set to Managed (installer-managed DHCP). + // It is ignored when provisioningNetwork is Unmanaged or Disabled. + // + // +kubebuilder:validation:Format=ip + // +optional + ProvisioningNetworkGateway string `json:"provisioningNetworkGateway,omitempty"` + // Hosts is the information needed to create the objects in Ironic. Hosts []*Host `json:"hosts"` diff --git a/pkg/types/baremetal/validation/platform.go b/pkg/types/baremetal/validation/platform.go index e765b9023f2..def3ccaf068 100644 --- a/pkg/types/baremetal/validation/platform.go +++ b/pkg/types/baremetal/validation/platform.go @@ -175,6 +175,10 @@ func validateDHCPRange(p *baremetal.Platform, fldPath *field.Path) (allErrs fiel allErrs = append(allErrs, field.Invalid(fldPath.Child("bootstrapProvisioningIP"), p.BootstrapProvisioningIP, fmt.Sprintf("%q overlaps with the allocated DHCP range", p.BootstrapProvisioningIP))) } } + // Validate ProvisioningNetworkGateway is not in DHCP range + if provisioningNetworkGateway := net.ParseIP(p.ProvisioningNetworkGateway); provisioningNetworkGateway != nil && bytes.Compare(provisioningNetworkGateway, start) >= 0 && bytes.Compare(provisioningNetworkGateway, end) <= 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("provisioningNetworkGateway"), p.ProvisioningNetworkGateway, fmt.Sprintf("%q overlaps with the allocated DHCP range", p.ProvisioningNetworkGateway))) + } return } @@ -461,6 +465,12 @@ func ValidatePlatform(p *baremetal.Platform, agentBasedInstallation bool, n *typ } } + if p.ProvisioningNetworkGateway != "" { + if err := validate.IP(p.ProvisioningNetworkGateway); err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("provisioningNetworkGateway"), p.ProvisioningNetworkGateway, err.Error())) + } + } + enabledCaps := c.GetEnabledCapabilities() if !agentBasedInstallation && enabledCaps.Has(configv1.ClusterVersionCapabilityMachineAPI) && p.Hosts == nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("hosts"), p.Hosts, "bare metal hosts are missing")) @@ -602,8 +612,21 @@ func ValidateProvisioningNetworking(p *baremetal.Platform, n *types.Networking, } // Ensure clusterProvisioningIP is in the provisioningNetworkCIDR - if !p.ProvisioningNetworkCIDR.Contains(net.ParseIP(p.ClusterProvisioningIP)) { - allErrs = append(allErrs, field.Invalid(fldPath.Child("clusterProvisioningIP"), p.ClusterProvisioningIP, fmt.Sprintf("%q is not in the provisioning network", p.ClusterProvisioningIP))) + if p.ClusterProvisioningIP != "" { + if !p.ProvisioningNetworkCIDR.Contains(net.ParseIP(p.ClusterProvisioningIP)) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("clusterProvisioningIP"), p.ClusterProvisioningIP, fmt.Sprintf("%q is not in the provisioning network", p.ClusterProvisioningIP))) + } + } + + // Ensure provisioningNetworkGateway is in the provisioningNetworkCIDR + if p.ProvisioningNetworkGateway != "" { + if !p.ProvisioningNetworkCIDR.Contains(net.ParseIP(p.ProvisioningNetworkGateway)) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("provisioningNetworkGateway"), p.ProvisioningNetworkGateway, fmt.Sprintf("%q is not in the provisioning network", p.ProvisioningNetworkGateway))) + } + // Ensure gateway is not the same as clusterProvisioningIP + if p.ProvisioningNetworkGateway == p.ClusterProvisioningIP { + allErrs = append(allErrs, field.Invalid(fldPath.Child("provisioningNetworkGateway"), p.ProvisioningNetworkGateway, "cannot be the same as clusterProvisioningIP")) + } } // Ensure provisioningNetworkCIDR does not have any host bits set