Skip to content

Commit

Permalink
Add support for optional provisioning networks in a Baremetal platform
Browse files Browse the repository at this point in the history
In addition to supporting fully managed provisioning networks with the
option of having external DHCP servers, baremetal installations should
also be able to support cases where a dedicated provisioning network
does not exist.
Details regarding the updates to the provisioning CR to accomodate the
above feature can be found in [1].
Also, removing support for the ConfigMap as way to pass provisioning
configuration to the MAO for baremetal installs.

[1] - https://github.com/openshift/enhancements/blob/master/enhancements/baremetal/baremetal-provisioning-optional.md
  • Loading branch information
sadasu committed Aug 5, 2020
1 parent 66047a5 commit 1b4fb3c
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 131 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,11 +59,11 @@ spec:
provisioningDHCPExternal:
description: provisioningDHCPExternal indicates whether the DHCP server for IP
addresses in the provisioning DHCP range is present within the metal3 cluster
or external to it.
or external to it. This field is being deprecated in favor of provisioningNetwork.
type: boolean
provisioningDHCPRange:
description: Needs to be interpreted along with provisioningDHCPExternal.
If the value of provisioningDHCPExternal is set to False, then
description: Needs to be interpreted along with provisioningDHCPExternal or
provisioningNetwork. If the value of provisioningDHCPExternal is set to False,
provisioningDHCPRange represents the range of IP addresses that the DHCP server
running within the metal3 cluster can use while provisioning baremetal servers.
If the value of provisioningDHCPExternal is set to True, then the value of
Expand All @@ -75,12 +75,31 @@ spec:
installer has created the CR. This value needs to be two comma sererated IP
addresses within the provisioningNetworkCIDR where the 1st address represents the
start of the range and the 2nd address represents the last usable address in the
range.
range. When the provisioningNetwork is set to `Managed`, the value of
provisioningDHCPRange would be used and ignored in the other 2 modes.
type: string
provisioningOSDownloadURL:
description: provisioningOSDownloadURL is the location from which the OS Image used to boot
baremetal host machines can be downloaded by the metal3 cluster.
type: string
provisioningNetwork:
description: provisioningNetwork provides a way to indicate the state of the underlying
network configuration for the provisioning network. This field can have one of the
following values -
`Managed`- when the provisioning network is completely managed by the Baremetal IPI
solution.
`Unmanaged`- when the provsioning network is present and used but the user is
responsible for managing DHCP. Virtual media provisioning is recommended but PXE
is still available if required.
`Disabled`- when the provisioning network is fully disabled. User can bring up the
baremetal cluster using virtual media or assisted installation. If using metal3 for
power management, BMCs must be accessible from the machine networks. User should
provide two IPs on the external network that would be used for provisioning services.
enum:
- Managed
- Unmanaged
- Disabled
type: string
status:
description: status holds observed values from the cluster. They may not
be overridden.
Expand Down
143 changes: 75 additions & 68 deletions pkg/operator/baremetal_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net"

"github.com/golang/glog"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
Expand All @@ -22,138 +23,144 @@ const (
baremetalKernelUrlSubPath = "images/ironic-python-agent.kernel"
baremetalRamdiskUrlSubPath = "images/ironic-python-agent.initramfs"
baremetalIronicEndpointSubpath = "v1/"
provisioningNetworkManaged = "Managed"
provisioningNetworkUnmanaged = "Unmanaged"
provisioningNetworkDisabled = "Disabled"
)

// Provisioning Config needed to deploy Metal3 pod
type BaremetalProvisioningConfig struct {
ProvisioningInterface string
ProvisioningIp string
ProvisioningNetworkCIDR string
ProvisioningDHCPExternal bool
ProvisioningDHCPRange string
ProvisioningOSDownloadURL string
ProvisioningNetwork string
}

func reportError(found bool, err error, configItem string, configName string) error {
if err != nil {
return fmt.Errorf("Error while reading %s from Baremetal provisioning CR %s: %w", configItem, configName, err)
}
if !found {
return fmt.Errorf("%s not found in Baremetal provisioning CR %s", configItem, configName)
}
return fmt.Errorf("Unknown Error while reading %s from Baremetal provisioning CR %s", configItem, configName)
}

func getBaremetalProvisioningConfig(dc dynamic.Interface, configName string) (BaremetalProvisioningConfig, error) {
provisioningClient := dc.Resource(provisioningGVR)
provisioningConfig, err := provisioningClient.Get(context.Background(), configName, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
glog.V(3).Infof("Baremetal provisioning CR %s is not found", configName)
return BaremetalProvisioningConfig{}, nil
}
if err != nil {
glog.Errorf("Error getting config from Baremetal provisioning CR %s", configName)
return BaremetalProvisioningConfig{}, err
return BaremetalProvisioningConfig{}, reportError(true, err, "provisioning configuration", configName)
}
provisioningSpec, found, err := unstructured.NestedMap(provisioningConfig.UnstructuredContent(), "spec")
if !found || err != nil {
glog.Errorf("Nested Spec not found in Baremetal provisioning CR %s", configName)
return BaremetalProvisioningConfig{}, err
return BaremetalProvisioningConfig{}, reportError(found, err, "Spec field", configName)
}
provisioningInterface, found, err := unstructured.NestedString(provisioningSpec, "provisioningInterface")
if !found || err != nil {
glog.Errorf("provisioningInterface not found in Baremetal provisioning CR %s", configName)
return BaremetalProvisioningConfig{}, err
return BaremetalProvisioningConfig{}, reportError(found, err, "provisioningInterface", configName)
}
provisioningIP, found, err := unstructured.NestedString(provisioningSpec, "provisioningIP")
if !found || err != nil {
glog.Errorf("provisioningIP not found in Baremetal provisioning CR %s", configName)
return BaremetalProvisioningConfig{}, err
return BaremetalProvisioningConfig{}, reportError(found, err, "provisioningIP", configName)
}
provisioningNetworkCIDR, found, err := unstructured.NestedString(provisioningSpec, "provisioningNetworkCIDR")
provisioningDHCPRange, found, err := unstructured.NestedString(provisioningSpec, "provisioningDHCPRange")
if !found || err != nil {
glog.Errorf("provisioningNetworkCIDR not found in Baremetal provisioning CR %s", configName)
return BaremetalProvisioningConfig{}, err
return BaremetalProvisioningConfig{}, reportError(found, err, "provisioningDHCPRange", configName)
}
provisioningDHCPExternal, found, err := unstructured.NestedBool(provisioningSpec, "provisioningDHCPExternal")
provisioningOSDownloadURL, found, err := unstructured.NestedString(provisioningSpec, "provisioningOSDownloadURL")
if !found || err != nil {
glog.Errorf("provisioningDHCPExternal not found in Baremetal provisioning CR %s", configName)
return BaremetalProvisioningConfig{}, err
return BaremetalProvisioningConfig{}, reportError(found, err, "provisioningOSDownloadURL", configName)
}
provisioningDHCPRange, found, err := unstructured.NestedString(provisioningSpec, "provisioningDHCPRange")
if !found || err != nil {
glog.Errorf("provisioningDHCPRange not found in Baremetal provisioning CR %s", configName)
return BaremetalProvisioningConfig{}, err
// If provisioningNetwork is not provided, set its value based on provisioningDHCPExternal
provisioningNetwork, foundNetworkState, err := unstructured.NestedString(provisioningSpec, "provisioningNetwork")
if err != nil {
return BaremetalProvisioningConfig{}, reportError(true, err, "provisioningNetwork", configName)
}
if !foundNetworkState {
// Check if provisioningDHCPExternal is present in the config
provisioningDHCPExternal, foundDHCP, err := unstructured.NestedBool(provisioningSpec, "provisioningDHCPExternal")
if !foundDHCP || err != nil {
// Both the new provisioningNetwork and the old provisioningDHCPExternal configs are not found.
return BaremetalProvisioningConfig{}, reportError(foundDHCP, err, "provisioningNetwork and provisioningDHCPExternal", configName)
}
if !provisioningDHCPExternal {
provisioningNetwork = provisioningNetworkManaged
} else {
provisioningNetwork = provisioningNetworkUnmanaged
}
}
provisioningOSDownloadURL, found, err := unstructured.NestedString(provisioningSpec, "provisioningOSDownloadURL")
// provisioningNetworkCIDR needs to be present for all provisioningNetwork states (even Disabled).
// The CIDR of the network needs to be extracted to form the provisioningIPCIDR
provisioningNetworkCIDR, found, err := unstructured.NestedString(provisioningSpec, "provisioningNetworkCIDR")
if !found || err != nil {
glog.Errorf("provisioningOSDownloadURL not found in Baremetal provisioning CR %s", configName)
return BaremetalProvisioningConfig{}, err
return BaremetalProvisioningConfig{}, reportError(found, err, "provisioningNetworkCIDR", configName)
}
// Check if the other config values make sense for the provisioningNetwork configured.
if provisioningInterface == "" && provisioningNetwork == provisioningNetworkManaged {
return BaremetalProvisioningConfig{}, fmt.Errorf("provisioningInterface cannot be empty when provisioningNetwork is Managed.")
}
if provisioningDHCPRange == "" && provisioningNetwork == provisioningNetworkManaged {
return BaremetalProvisioningConfig{}, fmt.Errorf("provisioningDHCPRange cannot be empty when provisioningNetwork is Managed or when the DHCP server needs to run with the metal3 cluster.")
}

return BaremetalProvisioningConfig{
ProvisioningInterface: provisioningInterface,
ProvisioningIp: provisioningIP,
ProvisioningNetworkCIDR: provisioningNetworkCIDR,
ProvisioningDHCPExternal: provisioningDHCPExternal,
ProvisioningDHCPRange: provisioningDHCPRange,
ProvisioningOSDownloadURL: provisioningOSDownloadURL,
ProvisioningNetwork: provisioningNetwork,
}, nil
}

func getProvisioningIPCIDR(baremetalConfig BaremetalProvisioningConfig) *string {
ipCIDR := ""
if baremetalConfig.ProvisioningNetworkCIDR != "" && baremetalConfig.ProvisioningIp != "" {
_, net, err := net.ParseCIDR(baremetalConfig.ProvisioningNetworkCIDR)
if err == nil {
cidr, _ := net.Mask.Size()
generatedConfig := fmt.Sprintf("%s/%d", baremetalConfig.ProvisioningIp, cidr)
return &generatedConfig
ipCIDR = fmt.Sprintf("%s/%d", baremetalConfig.ProvisioningIp, cidr)
}
}
return nil
return &ipCIDR
}

func getDeployKernelUrl(baremetalConfig BaremetalProvisioningConfig) *string {
deployKernelUrl := ""
if baremetalConfig.ProvisioningIp != "" {
generatedConfig := fmt.Sprintf("http://%s/%s", net.JoinHostPort(baremetalConfig.ProvisioningIp, baremetalHttpPort), baremetalKernelUrlSubPath)
return &generatedConfig
deployKernelUrl = fmt.Sprintf("http://%s/%s", net.JoinHostPort(baremetalConfig.ProvisioningIp, baremetalHttpPort), baremetalKernelUrlSubPath)
}
return nil
return &deployKernelUrl
}

func getDeployRamdiskUrl(baremetalConfig BaremetalProvisioningConfig) *string {
deployRamdiskUrl := ""
if baremetalConfig.ProvisioningIp != "" {
generatedConfig := fmt.Sprintf("http://%s/%s", net.JoinHostPort(baremetalConfig.ProvisioningIp, baremetalHttpPort), baremetalRamdiskUrlSubPath)
return &generatedConfig
deployRamdiskUrl = fmt.Sprintf("http://%s/%s", net.JoinHostPort(baremetalConfig.ProvisioningIp, baremetalHttpPort), baremetalRamdiskUrlSubPath)
}
return nil
return &deployRamdiskUrl
}

func getIronicEndpoint(baremetalConfig BaremetalProvisioningConfig) *string {
ironicEndpoint := ""
if baremetalConfig.ProvisioningIp != "" {
generatedConfig := fmt.Sprintf("http://%s/%s", net.JoinHostPort(baremetalConfig.ProvisioningIp, baremetalIronicPort), baremetalIronicEndpointSubpath)
return &generatedConfig
ironicEndpoint = fmt.Sprintf("http://%s/%s", net.JoinHostPort(baremetalConfig.ProvisioningIp, baremetalIronicPort), baremetalIronicEndpointSubpath)
}
return nil
return &ironicEndpoint
}

func getIronicInspectorEndpoint(baremetalConfig BaremetalProvisioningConfig) *string {
inspectorEndpoint := ""
if baremetalConfig.ProvisioningIp != "" {
generatedConfig := fmt.Sprintf("http://%s/%s", net.JoinHostPort(baremetalConfig.ProvisioningIp, baremetalIronicInspectorPort), baremetalIronicEndpointSubpath)
return &generatedConfig
}
return nil
}

func getProvisioningDHCPRange(baremetalConfig BaremetalProvisioningConfig) *string {
// When the DHCP server is external, it is OK for the DHCP range in the CR
// to be empty.
if baremetalConfig.ProvisioningDHCPRange != "" {
return &(baremetalConfig.ProvisioningDHCPRange)
} else if baremetalConfig.ProvisioningDHCPExternal {
return &(baremetalConfig.ProvisioningDHCPRange)
}
return nil
}

func getProvisioningInterface(baremetalConfig BaremetalProvisioningConfig) *string {
if baremetalConfig.ProvisioningInterface != "" {
return &(baremetalConfig.ProvisioningInterface)
}
return nil
}

func getProvisioningOSDownloadURL(baremetalConfig BaremetalProvisioningConfig) *string {
if baremetalConfig.ProvisioningOSDownloadURL != "" {
return &(baremetalConfig.ProvisioningOSDownloadURL)
inspectorEndpoint = fmt.Sprintf("http://%s/%s", net.JoinHostPort(baremetalConfig.ProvisioningIp, baremetalIronicInspectorPort), baremetalIronicEndpointSubpath)
}
return nil
return &inspectorEndpoint
}

func getMetal3DeploymentConfig(name string, baremetalConfig BaremetalProvisioningConfig) *string {
Expand All @@ -162,7 +169,7 @@ func getMetal3DeploymentConfig(name string, baremetalConfig BaremetalProvisionin
case "PROVISIONING_IP":
return getProvisioningIPCIDR(baremetalConfig)
case "PROVISIONING_INTERFACE":
return getProvisioningInterface(baremetalConfig)
return &baremetalConfig.ProvisioningInterface
case "DEPLOY_KERNEL_URL":
return getDeployKernelUrl(baremetalConfig)
case "DEPLOY_RAMDISK_URL":
Expand All @@ -175,9 +182,9 @@ func getMetal3DeploymentConfig(name string, baremetalConfig BaremetalProvisionin
configValue = baremetalHttpPort
return &configValue
case "DHCP_RANGE":
return getProvisioningDHCPRange(baremetalConfig)
return &baremetalConfig.ProvisioningDHCPRange
case "RHCOS_IMAGE_URL":
return getProvisioningOSDownloadURL(baremetalConfig)
return &baremetalConfig.ProvisioningOSDownloadURL
}
return nil
return &configValue
}

0 comments on commit 1b4fb3c

Please sign in to comment.