diff --git a/data/data/manifests/openshift/baremetal-deployment-crd.yaml b/data/data/manifests/openshift/baremetal-deployment-crd.yaml new file mode 100644 index 00000000000..23d6caee5a9 --- /dev/null +++ b/data/data/manifests/openshift/baremetal-deployment-crd.yaml @@ -0,0 +1,34 @@ +# This CRD is used to configure the baremetal provisioning system. +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: baremetal.operator.openshift.io +spec: + group: operator.openshift.io + names: + kind: BaremetalDeployment + listKind: BaremetalDeployments + plural: baremetaldeployment + singular: baremetaldeployments + scope: Cluster + versions: + - name: v1 + served: true + storage: true + schema: + openAPIV3Schema: + type: object + properties: + spec: + type: object + properties: + provisioningInterface: + type: string + provisioningIP: + type: string + ProvisioningNetworkCIDR: + type: string + ProvisioningDHCPOperatorStatus: + type: string + ProvisioningDHCPRange: + type: string diff --git a/pkg/asset/manifests/baremetal.go b/pkg/asset/manifests/baremetal.go new file mode 100644 index 00000000000..34b2df92427 --- /dev/null +++ b/pkg/asset/manifests/baremetal.go @@ -0,0 +1,109 @@ +package manifests + +import ( + "fmt" + "path/filepath" + + "github.com/ghodss/yaml" + "github.com/pkg/errors" + + operatorv1 "github.com/openshift/api/operator/v1" + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/installconfig" + "github.com/openshift/installer/pkg/asset/templates/content/openshift" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +var ( + baremetalCrdFilename = filepath.Join(manifestDir, "baremetal-deployment-01-crd.yml") + baremetalCfgFilename = filepath.Join(manifestDir, "baremetal-deployment-02-config.yml") +) + +// We need to manually create our CRDs first, so we can create the +// configuration instance of it in the installer. + +// Baremetal generates the baremetal-deployment-*.yml files. +type Baremetal struct { + Config *operatorv1.Metal3Provisioning + FileList []*asset.File +} + +var _ asset.WritableAsset = (*Baremetal)(nil) + +// Name returns a human friendly name for the operator. +func (bmo *Baremetal) Name() string { + return "Baremetal Deployment Config" +} + +// Dependencies returns all of the dependencies directly needed to generate +// baremetal configuration. +func (bmo *Baremetal) Dependencies() []asset.Asset { + return []asset.Asset{ + &installconfig.InstallConfig{}, + &openshift.BaremetalCRDs{}, + } +} + +// Generate generates the baremetal operator config and its CRD. +func (bmo *Baremetal) Generate(dependencies asset.Parents) error { + installConfig := &installconfig.InstallConfig{} + crds := &openshift.BaremetalCRDs{} + dependencies.Get(installConfig, crds) + + baremetalConfig := installConfig.Config.Platform.BareMetal + + config := &operatorv1.Metal3Provisioning{ + TypeMeta: metav1.TypeMeta{ + APIVersion: operatorv1.SchemeGroupVersion.String(), + Kind: "Baremetal", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "cluster", + // not namespaced + }, + Spec: operatorv1.Metal3ProvisioningSpec{ + ProvisioningInterface: baremetalConfig.ProvisioningInterface, + ProvisioningIP: baremetalConfig.ClusterProvisioningIP, + ProvisioningNetworkCIDR: baremetalConfig.ProvisioningNetworkCIDR, + ProvisioningDHCP: operatorv1.ProvisioningDHCP{ + DHCPRange: baremetalConfig.ProvisioningDHCPRange, + }, + }, + } + // Can't initialize this in the struct literal above as it's embedded. + config.Spec.ProvisioningDHCP.ManagementState = baremetalConfig.ProvisioningDHCPManagementState + + configData, err := yaml.Marshal(config) + if err != nil { + return errors.Wrapf(err, "failed to create %s manifests from InstallConfig", bmo.Name()) + } + + crdContents := "" + for _, crdFile := range crds.Files() { + crdContents = fmt.Sprintf("%s\n---\n%s", crdContents, crdFile.Data) + } + + bmo.FileList = []*asset.File{ + { + Filename: baremetalCrdFilename, + Data: []byte(crdContents), + }, + { + Filename: baremetalCfgFilename, + Data: configData, + }, + } + + return nil +} + +// Files returns the files generated by the asset. +func (bmo *Baremetal) Files() []*asset.File { + return bmo.FileList +} + +// Load returns false since this asset is not written to disk by the installer. +func (bmo *Baremetal) Load(f asset.FileFetcher) (bool, error) { + return false, nil +} diff --git a/pkg/asset/templates/content/openshift/baremetal-deployment-crds.go b/pkg/asset/templates/content/openshift/baremetal-deployment-crds.go new file mode 100644 index 00000000000..ba39e7d9b49 --- /dev/null +++ b/pkg/asset/templates/content/openshift/baremetal-deployment-crds.go @@ -0,0 +1,62 @@ +package openshift + +import ( + "os" + "path/filepath" + + "github.com/openshift/installer/pkg/asset" + "github.com/openshift/installer/pkg/asset/templates/content" +) + +const ( + baremetalCRDfilename = "baremetal-deployment-crd.yaml" +) + +var _ asset.WritableAsset = (*BaremetalCRDs)(nil) + +// BaremetalCRDs is the custom resource definitions for the baremetal deployment +type BaremetalCRDs struct { + FileList []*asset.File +} + +// Dependencies returns all of the dependencies directly needed by the asset +func (t *BaremetalCRDs) Dependencies() []asset.Asset { + return []asset.Asset{} +} + +// Name returns the human-friendly name of the asset. +func (t *BaremetalCRDs) Name() string { + return "Baremetal CRDs" +} + +// Generate generates the actual files by this asset +func (t *BaremetalCRDs) Generate(parents asset.Parents) error { + data, err := content.GetOpenshiftTemplate(baremetalCRDfilename) + if err != nil { + return err + } + t.FileList = append(t.FileList, &asset.File{ + Filename: filepath.Join(content.TemplateDir, baremetalCRDfilename), + Data: []byte(data), + }) + return nil +} + +// Files returns the files generated by the asset. +func (t *BaremetalCRDs) Files() []*asset.File { + return t.FileList +} + +// Load returns the asset from disk. +func (t *BaremetalCRDs) Load(f asset.FileFetcher) (bool, error) { + file, err := f.FetchByName(filepath.Join(content.TemplateDir, baremetalCRDfilename)) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, err + } + t.FileList = append(t.FileList, file) + + return true, nil +} diff --git a/pkg/types/baremetal/platform.go b/pkg/types/baremetal/platform.go index 84fad0fa96b..533b4407db6 100644 --- a/pkg/types/baremetal/platform.go +++ b/pkg/types/baremetal/platform.go @@ -1,5 +1,9 @@ package baremetal +import ( + operatorv1 "github.com/openshift/api/operator/v1" +) + // BMC stores the information about a baremetal host's management controller. type BMC struct { Username string `json:"username"` @@ -30,6 +34,25 @@ type Platform struct { // +optional ClusterProvisioningIP string `json:"provisioningHostIP,omitempty"` + // ProvisioningInterface is the network interface used to provision new hosts. + // +optional + ProvisioningInterface string `json:"provisioningInterface"` + + // ProvisioningNetworkCIDR defines the network to use for provisioning. + // +optional + ProvisioningNetworkCIDR string `json:"provisioningNetworkCIDR"` + + // ProvisioningDHCPOperatorStatus defines whether or not we should be running + // DHCP on the provisioning network or if it will be handled by an outside source + // +optional + ProvisioningDHCPManagementState operatorv1.ManagementState `json:"provisioningDHCPManagementState"` + + // ProvisioningDHCPRange is a comma separated start,end of the DHCP range used to + // assign hosts during provisioning. + // Defaults to the .20 address of ProvisioningNetworkCIDR + // +optional + ProvisioningDHCPRange string `json:"provisioningDHCPRange"` + // BootstrapProvisioningIP is the IP used on the bootstrap VM to // bring up provisioning services that are used to create the // control-plane machines diff --git a/pkg/types/baremetal/validation/platform.go b/pkg/types/baremetal/validation/platform.go index 2ea4ccc2acf..b24718dea32 100644 --- a/pkg/types/baremetal/validation/platform.go +++ b/pkg/types/baremetal/validation/platform.go @@ -4,6 +4,7 @@ import ( "fmt" "net" "net/url" + "strings" "github.com/openshift/installer/pkg/types" "github.com/openshift/installer/pkg/types/baremetal" @@ -71,6 +72,46 @@ func ValidatePlatform(p *baremetal.Platform, n *types.Networking, fldPath *field allErrs = append(allErrs, field.Invalid(fldPath.Child("bootstrapProvisioningIP"), p.BootstrapProvisioningIP, err.Error())) } + // Make sure the provisioning interface is set. Very little we can do to validate this + // as it's not on this machine. + if p.ProvisioningInterface == "" { + allErrs = append(allErrs, field.Invalid(fldPath.Child("provisioningInterface"), p.ProvisioningInterface, "Baremetal provisioning interface unset.")) + } + + // FIXME: Need to have better checking here. + if p.ProvisioningDHCPManagementState == "" { + allErrs = append(allErrs, field.Invalid(fldPath.Child("provisioningInterface"), p.ProvisioningInterface, "Baremetal provisioning interface unset.")) + } + + _, provNetwork, err := net.ParseCIDR(p.ProvisioningNetworkCIDR) + if err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("provisioningNetworkCIDR"), p.ProvisioningNetworkCIDR, err.Error())) + } + + ranges := strings.Split(p.ProvisioningDHCPRange, ",") + if len(ranges) != 2 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("provisioningDHCPRange"), p.ProvisioningDHCPRange, "DHCP Range must be two IPs separated by a comma")) + } else { + provisioningDHCPStart := ranges[0] + provisioningDHCPEnd := ranges[1] + + if err := validate.IP(provisioningDHCPStart); err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("provisioningDHCPRange"), p.ProvisioningDHCPRange, err.Error())) + } + + if provNetwork.Contains(net.ParseIP(provisioningDHCPStart)) != true { + allErrs = append(allErrs, field.Invalid(fldPath.Child("provisioningDHCPRange"), p.ProvisioningDHCPRange, "First IP not in provisioning network.")) + } + + if err := validate.IP(provisioningDHCPEnd); err != nil { + allErrs = append(allErrs, field.Invalid(fldPath.Child("provisioningDHCPRange"), p.ProvisioningDHCPRange, err.Error())) + } + + if provNetwork.Contains(net.ParseIP(provisioningDHCPEnd)) != true { + allErrs = append(allErrs, field.Invalid(fldPath.Child("provisioningDHCPRange"), p.ProvisioningDHCPRange, "Second IP not in provisioning network.")) + } + } + if p.Hosts == nil { allErrs = append(allErrs, field.Invalid(fldPath.Child("hosts"), p.Hosts, "bare metal hosts are missing")) } diff --git a/vendor/github.com/openshift/api/operator/v1/types_metal3provisioning.go b/vendor/github.com/openshift/api/operator/v1/types_metal3provisioning.go new file mode 100644 index 00000000000..2f57a25086d --- /dev/null +++ b/vendor/github.com/openshift/api/operator/v1/types_metal3provisioning.go @@ -0,0 +1,109 @@ +package v1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status + +// Metal3Provisioning contains configuration used by the Provisioning +// service (Ironic) to provision baremetal hosts. +// +// Metal3Provisioning is created by the Openshift installer using admin +// or user provided information about the provisioning network and the NIC +// on the server that can be used to PXE boot it. +// +// This CR is a singleton, created by the installer and currently only +// consumed by the machine-api-operator to bring up and update containers +// in a metal3 cluster. +// +type Metal3Provisioning struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + // spec is the specification of the desired behavior of the + // Metal3Provisioning. + Spec Metal3ProvisioningSpec `json:"spec,omitempty"` + + // status is the most recently observed status of the + // Metal3Provisioning. + Status Metal3ProvisioningStatus `json:"status,omitempty"` +} + +// ProvisioningDHCP represents just the configuration required to fully +// identify the way DHCP would be handled during baremetal host bringup. +// +// DHCP services could be provided external to the metal3 cluster, in +// which case, IP address assignment for the baremetal servers should +// happen via this external DHCP server and not via a DHCP server started +// within the metal3 cluster. +// If IP address assignment needs to happen via the DHCP server within the +// metal3 cluster, then the CR needs to contain the DHCP address range that +// this internal DHCP server needs to use. +// +type ProvisioningDHCP struct { + // ManagementState within the OperatorSpec needs to be set to + // indicate if the DHCP server is internal or external to the + // metal3 cluster. ManagementState set to "Removed" indicates + // that the DHCP server is outside the metal3 cluster. And a + // value of "Managed" indicates that the DHCP services are + // managed within the metal3 cluster. + // The other fields of OperatorSpec retain their existing + // semantics. + OperatorSpec `json:",inline"` + + // If the ManagementState within the OperatorStatus is set to + // "Managed", then the DHCPRange 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 ManagementState + // is set to "Removed", then the value of DHCPRange will be ignored. + // If the ManagementState is "Managed" and the value of DHCPRange is + // not set, then the DHCP range is taken to be the default range which + // goes from .10 to .100 of the ProvisioningNetworkCIDR. This is the only + // value in all of the provisioning configuration that can be changed + // after the installer has created the CR. + DHCPRange string `json:"DHCPRange,omitempty"` +} + +// Metal3ProvisioningSpec is the specification of the desired behavior of the +// Metal3Provisioning. +type Metal3ProvisioningSpec struct { + // provisioningInterface is the name of the network interface on a Baremetal + // server to the provisioning network. It can have values like "eth1" or "ens3". + ProvisioningInterface string `json:"provisioningInterface"` + + // provisioningIP is the IP address assigned to the provisioningInterface of + // the baremetal server. This IP address should be within the provisioning + // subnet, and outside of the DHCP range. + ProvisioningIP string `json:"provisioningIP"` + + // provisioningNetworkCIDR is the network on which the baremetal nodes are + // provisioned. The provisioningIP and the IPs in the dhcpRange all come from + // within this network. + ProvisioningNetworkCIDR string `json:"provisioningNetworkCIDR"` + + // provisioningDHCP consists of two parameters that represents whether the DHCP + // server is internal or external to the metal3 cluster. If it is internal, + // the second parameter represents the DHCP address range to be provided + // to the baremetal hosts. + ProvisioningDHCP ProvisioningDHCP `json:"provisioningDHCP"` +} + +// Metal3ProvisioningStatus defines the observed status of Metal3Provisioning. +type Metal3ProvisioningStatus struct { + OperatorStatus `json:",inline"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +// +kubebuilder:object:root=true + +// Metal3ProvisioningList contains a list of Metal3Provisioning. +type Metal3ProvisioningList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Metal3Provisioning `json:"items"` +}