Repository: metal3-io/baremetal-operator (go)

Issue:
BMH architecture is hard-coded from the profile before inspection
**User Story**

In a multi-architecture scenario, I want to get the cpu architecture from bmh to set the Architecture field in PreprovisioningImage. Now the implementation in getHostArchitecture is obtained from the host.Status.HardwareDetails.CPU.Arch field, or from the profile. This It does not meet the needs of my scene.

Can you provide a way to manually set the cpu architecture when creating bmh？

https://github.com/metal3-io/baremetal-operator/pull/1252

**Detailed Description**

[A clear and concise description of what you want to happen.]

**Anything else you would like to add:**

[Miscellaneous information that will assist in solving the issue.]

/kind feature


Pull request description:
✨ Add an explicit Architecture field (closes #1270)

Patch:
diff --git a/apis/metal3.io/v1alpha1/baremetalhost_types.go b/apis/metal3.io/v1alpha1/baremetalhost_types.go
index ec2a68e3..1a8ba23e 100644
--- a/apis/metal3.io/v1alpha1/baremetalhost_types.go
+++ b/apis/metal3.io/v1alpha1/baremetalhost_types.go
@@ -431,6 +431,10 @@ type BareMetalHostSpec struct {
 	// A custom deploy procedure.
 	// +optional
 	CustomDeploy *CustomDeploy `json:"customDeploy,omitempty"`
+
+	// CPU architecture of the host, e.g. "x86_64" or "aarch64". If unset, eventually populated by inspection.
+	// +optional
+	Architecture string `json:"architecture,omitempty"`
 }
 
 // AutomatedCleaningMode is the interface to enable/disable automated cleaning
diff --git a/apis/metal3.io/v1alpha1/profile/profile.go b/apis/metal3.io/v1alpha1/profile/profile.go
index 68511685..190f3452 100644
--- a/apis/metal3.io/v1alpha1/profile/profile.go
+++ b/apis/metal3.io/v1alpha1/profile/profile.go
@@ -20,15 +20,6 @@ type Profile struct {
 	// RootDeviceHints holds the suggestions for placing the storage
 	// for the root filesystem.
 	RootDeviceHints metal3v1alpha1.RootDeviceHints
-
-	// RootGB is the size of the root volume in GB
-	RootGB int
-
-	// LocalGB is the size of something(?)
-	LocalGB int
-
-	// CPUArch is the architecture of the CPU.
-	CPUArch string
 }
 
 var profiles = make(map[string]Profile)
@@ -39,9 +30,6 @@ func init() {
 		RootDeviceHints: metal3v1alpha1.RootDeviceHints{
 			DeviceName: "/dev/sda",
 		},
-		RootGB:  10,
-		LocalGB: 50,
-		CPUArch: "x86_64",
 	}
 
 	profiles["libvirt"] = Profile{
@@ -49,9 +37,6 @@ func init() {
 		RootDeviceHints: metal3v1alpha1.RootDeviceHints{
 			DeviceName: "/dev/vda",
 		},
-		RootGB:  10,
-		LocalGB: 50,
-		CPUArch: "x86_64",
 	}
 
 	profiles["dell"] = Profile{
@@ -59,9 +44,6 @@ func init() {
 		RootDeviceHints: metal3v1alpha1.RootDeviceHints{
 			HCTL: "0:0:0:0",
 		},
-		RootGB:  10,
-		LocalGB: 50,
-		CPUArch: "x86_64",
 	}
 
 	profiles["dell-raid"] = Profile{
@@ -69,9 +51,6 @@ func init() {
 		RootDeviceHints: metal3v1alpha1.RootDeviceHints{
 			HCTL: "0:2:0:0",
 		},
-		RootGB:  10,
-		LocalGB: 50,
-		CPUArch: "x86_64",
 	}
 
 	profiles["openstack"] = Profile{
@@ -79,9 +58,6 @@ func init() {
 		RootDeviceHints: metal3v1alpha1.RootDeviceHints{
 			DeviceName: "/dev/vdb",
 		},
-		RootGB:  10,
-		LocalGB: 50,
-		CPUArch: "x86_64",
 	}
 }
 
diff --git a/config/crd/bases/metal3.io_baremetalhosts.yaml b/config/crd/bases/metal3.io_baremetalhosts.yaml
index 065ccd97..c286453a 100644
--- a/config/crd/bases/metal3.io_baremetalhosts.yaml
+++ b/config/crd/bases/metal3.io_baremetalhosts.yaml
@@ -74,6 +74,10 @@ spec:
           spec:
             description: BareMetalHostSpec defines the desired state of BareMetalHost
             properties:
+              architecture:
+                description: CPU architecture of the host, e.g. "x86_64" or "aarch64".
+                  If unset, eventually populated by inspection.
+                type: string
               automatedCleaningMode:
                 default: metadata
                 description: When set to disabled, automated cleaning will be avoided
diff --git a/config/render/capm3.yaml b/config/render/capm3.yaml
index 47b41837..f276e5a0 100644
--- a/config/render/capm3.yaml
+++ b/config/render/capm3.yaml
@@ -73,6 +73,10 @@ spec:
           spec:
             description: BareMetalHostSpec defines the desired state of BareMetalHost
             properties:
+              architecture:
+                description: CPU architecture of the host, e.g. "x86_64" or "aarch64".
+                  If unset, eventually populated by inspection.
+                type: string
               automatedCleaningMode:
                 default: metadata
                 description: When set to disabled, automated cleaning will be avoided
diff --git a/controllers/metal3.io/baremetalhost_controller.go b/controllers/metal3.io/baremetalhost_controller.go
index 5beae8ef..58dd17a1 100644
--- a/controllers/metal3.io/baremetalhost_controller.go
+++ b/controllers/metal3.io/baremetalhost_controller.go
@@ -678,14 +678,16 @@ func (r *BareMetalHostReconciler) preprovImageAvailable(info *reconcileInfo, ima
 }
 
 func getHostArchitecture(host *metal3api.BareMetalHost) string {
+	if host.Spec.Architecture != "" {
+		return host.Spec.Architecture
+	}
+	// FIXME(dtantsur): this relies on the essentially deprecated HardwareDetails field.
 	if host.Status.HardwareDetails != nil &&
 		host.Status.HardwareDetails.CPU.Arch != "" {
 		return host.Status.HardwareDetails.CPU.Arch
 	}
-	if hwprof, err := profile.GetProfile(host.Status.HardwareProfile); err == nil {
-		return hwprof.CPUArch
-	}
-	return ""
+	// This is probably the case for most hardware, and is useful for compatibility with hardware profiles.
+	return "x86_64"
 }
 
 func (r *BareMetalHostReconciler) getPreprovImage(info *reconcileInfo, formats []metal3api.ImageFormat) (*provisioner.PreprovisioningImage, error) {
@@ -1183,6 +1185,7 @@ func (r *BareMetalHostReconciler) actionProvisioning(prov provisioner.Provisione
 		BootMode:        info.host.Status.Provisioning.BootMode,
 		HardwareProfile: hwProf,
 		RootDeviceHints: info.host.Status.Provisioning.RootDeviceHints.DeepCopy(),
+		CPUArchitecture: getHostArchitecture(info.host),
 	}, forceReboot)
 	if err != nil {
 		return actionError{errors.Wrap(err, "failed to provision")}
@@ -1843,6 +1846,16 @@ func (r *BareMetalHostReconciler) reconciletHostData(ctx context.Context, host *
 		reqLogger.Info("deleted status annotation")
 		return ctrl.Result{Requeue: true}, nil
 	}
+
+	if host.Spec.Architecture == "" && hardwareData != nil && hardwareData.Spec.HardwareDetails != nil && hardwareData.Spec.HardwareDetails.CPU.Arch != "" {
+		newArch := hardwareData.Spec.HardwareDetails.CPU.Arch
+		reqLogger.Info("updating architecture", "Architecture", newArch)
+		host.Spec.Architecture = newArch
+		if err := r.Client.Update(context.Background(), host); err != nil {
+			return ctrl.Result{}, errors.Wrap(err, "failed to update architecture")
+		}
+		return ctrl.Result{Requeue: true}, nil
+	}
 	return ctrl.Result{}, nil
 
 }
diff --git a/pkg/provisioner/ironic/ironic.go b/pkg/provisioner/ironic/ironic.go
index 44736334..4a9d902c 100644
--- a/pkg/provisioner/ironic/ironic.go
+++ b/pkg/provisioner/ironic/ironic.go
@@ -1033,15 +1033,12 @@ func (p *ironicProvisioner) getUpdateOptsForNode(ironicNode *nodes.Node, data pr
 	p.getImageUpdateOptsForNode(ironicNode, &data.Image, data.BootMode, hasCustomDeploy, updater)
 
 	opts := optionsData{
-		"root_device": devicehints.MakeHintMap(data.RootDeviceHints),
-
-		// FIXME(dhellmann): This should come from inspecting the host.
-		"cpu_arch": data.HardwareProfile.CPUArch,
-
-		"local_gb": data.HardwareProfile.LocalGB,
-
+		"root_device":  devicehints.MakeHintMap(data.RootDeviceHints),
 		"capabilities": buildCapabilitiesValue(ironicNode, data.BootMode),
 	}
+	if data.CPUArchitecture != "" {
+		opts["cpu_arch"] = data.CPUArchitecture
+	}
 	updater.SetPropertiesOpts(opts, ironicNode)
 
 	return updater
diff --git a/pkg/provisioner/provisioner.go b/pkg/provisioner/provisioner.go
index a538c2f3..b4a52d8e 100644
--- a/pkg/provisioner/provisioner.go
+++ b/pkg/provisioner/provisioner.go
@@ -112,6 +112,7 @@ type ProvisionData struct {
 	HardwareProfile profile.Profile
 	RootDeviceHints *metal3api.RootDeviceHints
 	CustomDeploy    *metal3api.CustomDeploy
+	CPUArchitecture string
 }
 
 type HTTPHeaders []map[string]string


Test patch:
diff --git a/controllers/metal3.io/baremetalhost_controller_test.go b/controllers/metal3.io/baremetalhost_controller_test.go
index c29c7aa7..e0343d91 100644
--- a/controllers/metal3.io/baremetalhost_controller_test.go
+++ b/controllers/metal3.io/baremetalhost_controller_test.go
@@ -2263,6 +2263,10 @@ func TestGetHostArchitecture(t *testing.T) {
 	host := newDefaultHost(t)
 	assert.Equal(t, "x86_64", getHostArchitecture(host))
 
+	host.Spec.Architecture = "aarch64"
+	assert.Equal(t, "aarch64", getHostArchitecture(host))
+
+	host.Spec.Architecture = ""
 	host.Status.HardwareDetails = &metal3api.HardwareDetails{
 		CPU: metal3api.CPU{
 			Arch: "aarch64",
diff --git a/pkg/provisioner/ironic/updateopts_test.go b/pkg/provisioner/ironic/updateopts_test.go
index 7d5173af..0bf6fc2f 100644
--- a/pkg/provisioner/ironic/updateopts_test.go
+++ b/pkg/provisioner/ironic/updateopts_test.go
@@ -599,14 +599,6 @@ func TestGetUpdateOptsForNodeVirtual(t *testing.T) {
 			Path:  "/instance_uuid",
 			Value: "27720611-e5d1-45d3-ba3a-222dcfaa4ca2",
 		},
-		{
-			Path:  "/properties/cpu_arch",
-			Value: "x86_64",
-		},
-		{
-			Path:  "/properties/local_gb",
-			Value: 50,
-		},
 	}
 
 	for _, e := range expected {
@@ -672,6 +664,7 @@ func TestGetUpdateOptsForNodeDell(t *testing.T) {
 		Image:           *host.Spec.Image,
 		BootMode:        metal3api.DefaultBootMode,
 		HardwareProfile: hwProf,
+		CPUArchitecture: "x86_64",
 	}
 	patches := prov.getUpdateOptsForNode(ironicNode, provData).Updates
 
@@ -702,10 +695,6 @@ func TestGetUpdateOptsForNodeDell(t *testing.T) {
 			Path:  "/properties/cpu_arch",
 			Value: "x86_64",
 		},
-		{
-			Path:  "/properties/local_gb",
-			Value: 50,
-		},
 	}
 
 	for _, e := range expected {