diff --git a/apis/metal3.io/v1alpha1/baremetalhost_types.go b/apis/metal3.io/v1alpha1/baremetalhost_types.go index bcfd6bf87a..8ea60224ca 100644 --- a/apis/metal3.io/v1alpha1/baremetalhost_types.go +++ b/apis/metal3.io/v1alpha1/baremetalhost_types.go @@ -410,6 +410,11 @@ type BareMetalHostSpec struct { // without hardware profiles. HardwareProfile string `json:"hardwareProfile,omitempty"` + // The value of the kernel commandline argument list that will be passed + // to the pre provisioning agent's kernel during boot. + // +optional + PreprovisioningExtraKernelParams string `json:"preprovisioningExtraKernelParams,omitempty"` + // Provide guidance about how to choose the device for the image // being provisioned. The default is currently to use /dev/sda as // the root device. diff --git a/config/base/crds/bases/metal3.io_baremetalhosts.yaml b/config/base/crds/bases/metal3.io_baremetalhosts.yaml index c66e4f7df5..8fe40b94c3 100644 --- a/config/base/crds/bases/metal3.io_baremetalhosts.yaml +++ b/config/base/crds/bases/metal3.io_baremetalhosts.yaml @@ -343,6 +343,11 @@ spec: state (e.g. provisioned), its power state will be forced to match this value. type: boolean + preprovisioningExtraKernelParams: + description: |- + The value of the kernel commandline argument list that will be passed + to the pre provisioning agent's kernel during boot. + type: string preprovisioningNetworkDataName: description: |- PreprovisioningNetworkDataName is the name of the Secret in the diff --git a/config/render/capm3.yaml b/config/render/capm3.yaml index 5a457d6f61..44ee7f3eab 100644 --- a/config/render/capm3.yaml +++ b/config/render/capm3.yaml @@ -343,6 +343,11 @@ spec: state (e.g. provisioned), its power state will be forced to match this value. type: boolean + preprovisioningExtraKernelParams: + description: |- + The value of the kernel commandline argument list that will be passed + to the pre provisioning agent's kernel during boot. + type: string preprovisioningNetworkDataName: description: |- PreprovisioningNetworkDataName is the name of the Secret in the diff --git a/internal/controller/metal3.io/baremetalhost_controller.go b/internal/controller/metal3.io/baremetalhost_controller.go index 6b12023320..a0c28bf797 100644 --- a/internal/controller/metal3.io/baremetalhost_controller.go +++ b/internal/controller/metal3.io/baremetalhost_controller.go @@ -84,6 +84,33 @@ func (info *reconcileInfo) publishEvent(reason, message string) { info.events = append(info.events, info.host.NewEvent(reason, message)) } +// return the PreprovisioningExtraKernelParams from the reconciliation info. +func (r *BareMetalHostReconciler) retrievePreprovisioningExtraKernelParamsSpec(info *reconcileInfo, prov provisioner.Provisioner) string { + if info == nil || info.host == nil { + return "" + } + kernelExtraPreprovParams := info.host.Spec.PreprovisioningExtraKernelParams + preprovImgFormats, err := prov.PreprovisioningImageFormats() + if err != nil { + return kernelExtraPreprovParams + } + preprovImg, err := r.getPreprovImage(info, preprovImgFormats) + if err != nil { + return kernelExtraPreprovParams + } + // Make sure kernel extra params coming from dynamically generater preprov + // Image are also represented in every life cycle operation + if preprovImg != nil { + trimmedParams := strings.TrimSpace(kernelExtraPreprovParams) + if trimmedParams == "" { + kernelExtraPreprovParams = preprovImg.GeneratedImage.ExtraKernelParams + } else { + kernelExtraPreprovParams += " " + preprovImg.GeneratedImage.ExtraKernelParams + } + } + return kernelExtraPreprovParams +} + // +kubebuilder:rbac:groups=metal3.io,resources=baremetalhosts,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=metal3.io,resources=baremetalhosts/status,verbs=get;update;patch // +kubebuilder:rbac:groups=metal3.io,resources=baremetalhosts/finalizers,verbs=update @@ -868,16 +895,16 @@ func (r *BareMetalHostReconciler) registerHost(prov provisioner.Provisioner, inf provResult, provID, err := prov.Register( provisioner.ManagementAccessData{ - BootMode: info.host.Status.Provisioning.BootMode, - AutomatedCleaningMode: info.host.Spec.AutomatedCleaningMode, - State: info.host.Status.Provisioning.State, - OperationalStatus: info.host.Status.OperationalStatus, - CurrentImage: getCurrentImage(info.host), - PreprovisioningImage: preprovImg, - PreprovisioningNetworkData: preprovisioningNetworkData, - HasCustomDeploy: hasCustomDeploy(info.host), - DisablePowerOff: info.host.Spec.DisablePowerOff, - CPUArchitecture: getHostArchitecture(info.host), + BootMode: info.host.Status.Provisioning.BootMode, + AutomatedCleaningMode: info.host.Spec.AutomatedCleaningMode, + State: info.host.Status.Provisioning.State, + CurrentImage: getCurrentImage(info.host), + PreprovisioningImage: preprovImg, + PreprovisioningNetworkData: preprovisioningNetworkData, + PreprovisioningExtraKernelParams: r.retrievePreprovisioningExtraKernelParamsSpec(info, prov), + HasCustomDeploy: hasCustomDeploy(info.host), + DisablePowerOff: info.host.Spec.DisablePowerOff, + CPUArchitecture: getHostArchitecture(info.host), }, credsChanged, info.host.Status.ErrorType == metal3api.RegistrationError) @@ -1017,7 +1044,8 @@ func (r *BareMetalHostReconciler) actionInspecting(prov provisioner.Provisioner, provResult, started, details, err := prov.InspectHardware( provisioner.InspectData{ - BootMode: info.host.Status.Provisioning.BootMode, + BootMode: info.host.Status.Provisioning.BootMode, + PreprovisioningExtraKernelParams: r.retrievePreprovisioningExtraKernelParamsSpec(info, prov), }, info.host.Status.ErrorType == metal3api.InspectionError, refresh, @@ -1187,10 +1215,11 @@ func (r *BareMetalHostReconciler) actionPreparing(prov provisioner.Provisioner, } prepareData := provisioner.PrepareData{ - TargetRAIDConfig: newStatus.Provisioning.RAID.DeepCopy(), - ActualRAIDConfig: info.host.Status.Provisioning.RAID.DeepCopy(), - RootDeviceHints: newStatus.Provisioning.RootDeviceHints.DeepCopy(), - FirmwareConfig: newStatus.Provisioning.Firmware.DeepCopy(), + TargetRAIDConfig: newStatus.Provisioning.RAID.DeepCopy(), + ActualRAIDConfig: info.host.Status.Provisioning.RAID.DeepCopy(), + RootDeviceHints: newStatus.Provisioning.RootDeviceHints.DeepCopy(), + FirmwareConfig: newStatus.Provisioning.Firmware.DeepCopy(), + PreprovisioningExtraKernelParams: r.retrievePreprovisioningExtraKernelParamsSpec(info, prov), } // When manual cleaning fails, we think that the existing RAID configuration // is invalid and needs to be reconfigured. @@ -1312,12 +1341,13 @@ func (r *BareMetalHostReconciler) actionProvisioning(prov provisioner.Provisione } provResult, err := prov.Provision(provisioner.ProvisionData{ - Image: image, - CustomDeploy: info.host.Spec.CustomDeploy.DeepCopy(), - HostConfig: hostConf, - BootMode: info.host.Status.Provisioning.BootMode, - HardwareProfile: hwProf, - RootDeviceHints: info.host.Status.Provisioning.RootDeviceHints.DeepCopy(), + Image: image, + CustomDeploy: info.host.Spec.CustomDeploy.DeepCopy(), + HostConfig: hostConf, + BootMode: info.host.Status.Provisioning.BootMode, + HardwareProfile: hwProf, + RootDeviceHints: info.host.Status.Provisioning.RootDeviceHints.DeepCopy(), + PreprovisioningExtraKernelParams: r.retrievePreprovisioningExtraKernelParamsSpec(info, prov), }, forceReboot) if err != nil { return actionError{fmt.Errorf("failed to provision: %w", err)} @@ -1396,11 +1426,12 @@ func (r *BareMetalHostReconciler) actionDeprovisioning(prov provisioner.Provisio } } + DeprovisionData := provisioner.DeprovisionData{ + PreprovisioningExtraKernelParams: r.retrievePreprovisioningExtraKernelParamsSpec(info, prov), + } info.log.Info("deprovisioning") - provResult, err := prov.Deprovision( - info.host.Status.ErrorType == metal3api.ProvisioningError, - info.host.Spec.AutomatedCleaningMode) + provResult, err := prov.Deprovision(DeprovisionData, info.host.Status.ErrorType == metal3api.ProvisioningError, info.host.Spec.AutomatedCleaningMode) if err != nil { return actionError{fmt.Errorf("failed to deprovision: %w", err)} } @@ -1435,6 +1466,7 @@ func (r *BareMetalHostReconciler) actionDeprovisioning(prov provisioner.Provisio func (r *BareMetalHostReconciler) doServiceIfNeeded(prov provisioner.Provisioner, info *reconcileInfo, hup *metal3api.HostUpdatePolicy) (result actionResult) { servicingData := provisioner.ServicingData{} + servicingData.PreprovisioningExtraKernelParams = r.retrievePreprovisioningExtraKernelParamsSpec(info, prov) // (NOTE)janders: since Servicing is an opt-in feature that requires HostUpdatePolicy to be created and set to onReboot // set below booleans to false by default and change to true based on policy settings diff --git a/internal/controller/metal3.io/host_state_machine_test.go b/internal/controller/metal3.io/host_state_machine_test.go index a2229c220d..0389531223 100644 --- a/internal/controller/metal3.io/host_state_machine_test.go +++ b/internal/controller/metal3.io/host_state_machine_test.go @@ -1374,7 +1374,7 @@ func (m *mockProvisioner) Provision(_ provisioner.ProvisionData, _ bool) (result return m.getNextResultByMethod("Provision"), err } -func (m *mockProvisioner) Deprovision(_ bool, _ metal3api.AutomatedCleaningMode) (result provisioner.Result, err error) { +func (m *mockProvisioner) Deprovision(_ provisioner.DeprovisionData, _ bool, _ metal3api.AutomatedCleaningMode) (result provisioner.Result, err error) { return m.getNextResultByMethod("Deprovision"), err } diff --git a/pkg/hardwareutils/bmc/access.go b/pkg/hardwareutils/bmc/access.go index d3860286d7..45512a3abd 100644 --- a/pkg/hardwareutils/bmc/access.go +++ b/pkg/hardwareutils/bmc/access.go @@ -68,7 +68,7 @@ type AccessDetails interface { // pre-populated with the access information, and the caller is // expected to add any other information that might be needed // (such as the kernel and ramdisk locations). - DriverInfo(bmcCreds Credentials) map[string]interface{} + DriverInfo(bmcCreds Credentials, preProvExtraKernParams string) map[string]interface{} BIOSInterface() string diff --git a/pkg/hardwareutils/bmc/access_test.go b/pkg/hardwareutils/bmc/access_test.go index 4faafae5b8..0c0edac7ec 100644 --- a/pkg/hardwareutils/bmc/access_test.go +++ b/pkg/hardwareutils/bmc/access_test.go @@ -507,12 +507,13 @@ func TestDriverInfo(t *testing.T) { Scenario: "ipmi default port", input: "ipmi://192.168.122.1", expects: map[string]interface{}{ - "ipmi_port": ipmiDefaultPort, - "ipmi_password": "", - "ipmi_username": "", - "ipmi_address": "192.168.122.1", - "ipmi_verify_ca": false, - "ipmi_priv_level": "ADMINISTRATOR", + "ipmi_port": ipmiDefaultPort, + "ipmi_password": "", + "ipmi_username": "", + "ipmi_address": "192.168.122.1", + "ipmi_verify_ca": false, + "ipmi_priv_level": "ADMINISTRATOR", + "kernel_append_params": "", }, }, @@ -520,12 +521,13 @@ func TestDriverInfo(t *testing.T) { Scenario: "ipmi setting privilege level", input: "ipmi://192.168.122.1?privilegelevel=OPERATOR", expects: map[string]interface{}{ - "ipmi_port": ipmiDefaultPort, - "ipmi_password": "", - "ipmi_username": "", - "ipmi_address": "192.168.122.1", - "ipmi_verify_ca": false, - "ipmi_priv_level": "OPERATOR", + "ipmi_port": ipmiDefaultPort, + "ipmi_password": "", + "ipmi_username": "", + "ipmi_address": "192.168.122.1", + "ipmi_verify_ca": false, + "ipmi_priv_level": "OPERATOR", + "kernel_append_params": "", }, }, @@ -533,13 +535,14 @@ func TestDriverInfo(t *testing.T) { Scenario: "irmc", input: "irmc://192.168.122.1", expects: map[string]interface{}{ - "irmc_address": "192.168.122.1", - "irmc_password": "", - "irmc_username": "", - "ipmi_address": "192.168.122.1", - "ipmi_password": "", - "ipmi_username": "", - "irmc_verify_ca": false, + "irmc_address": "192.168.122.1", + "irmc_password": "", + "irmc_username": "", + "ipmi_address": "192.168.122.1", + "ipmi_password": "", + "ipmi_username": "", + "irmc_verify_ca": false, + "kernel_append_params": "", }, }, @@ -547,14 +550,15 @@ func TestDriverInfo(t *testing.T) { Scenario: "irmc port", input: "irmc://192.168.122.1:8080", expects: map[string]interface{}{ - "irmc_address": "192.168.122.1", - "irmc_port": "8080", - "irmc_password": "", - "irmc_username": "", - "ipmi_address": "192.168.122.1", - "ipmi_password": "", - "ipmi_username": "", - "irmc_verify_ca": false, + "irmc_address": "192.168.122.1", + "irmc_port": "8080", + "irmc_password": "", + "irmc_username": "", + "ipmi_address": "192.168.122.1", + "ipmi_password": "", + "ipmi_username": "", + "irmc_verify_ca": false, + "kernel_append_params": "", }, }, @@ -562,13 +566,14 @@ func TestDriverInfo(t *testing.T) { Scenario: "irmc ipv6", input: "irmc://[fe80::fc33:62ff:fe83:8a76]", expects: map[string]interface{}{ - "irmc_address": "fe80::fc33:62ff:fe83:8a76", - "irmc_password": "", - "irmc_username": "", - "ipmi_address": "fe80::fc33:62ff:fe83:8a76", - "ipmi_password": "", - "ipmi_username": "", - "irmc_verify_ca": false, + "irmc_address": "fe80::fc33:62ff:fe83:8a76", + "irmc_password": "", + "irmc_username": "", + "ipmi_address": "fe80::fc33:62ff:fe83:8a76", + "ipmi_password": "", + "ipmi_username": "", + "irmc_verify_ca": false, + "kernel_append_params": "", }, }, @@ -576,14 +581,15 @@ func TestDriverInfo(t *testing.T) { Scenario: "irmc ipv6 port", input: "irmc://[fe80::fc33:62ff:fe83:8a76]:8080", expects: map[string]interface{}{ - "irmc_address": "fe80::fc33:62ff:fe83:8a76", - "irmc_port": "8080", - "irmc_password": "", - "irmc_username": "", - "ipmi_address": "fe80::fc33:62ff:fe83:8a76", - "ipmi_password": "", - "ipmi_username": "", - "irmc_verify_ca": false, + "irmc_address": "fe80::fc33:62ff:fe83:8a76", + "irmc_port": "8080", + "irmc_password": "", + "irmc_username": "", + "ipmi_address": "fe80::fc33:62ff:fe83:8a76", + "ipmi_password": "", + "ipmi_username": "", + "irmc_verify_ca": false, + "kernel_append_params": "", }, }, @@ -591,11 +597,12 @@ func TestDriverInfo(t *testing.T) { Scenario: "Redfish", input: "redfish://192.168.122.1/redfish/v1/foo/bar", expects: map[string]interface{}{ - "redfish_address": "https://192.168.122.1", - "redfish_system_id": "/redfish/v1/foo/bar", - "redfish_password": "", - "redfish_username": "", - "redfish_verify_ca": false, + "redfish_address": "https://192.168.122.1", + "redfish_system_id": "/redfish/v1/foo/bar", + "redfish_password": "", + "redfish_username": "", + "redfish_verify_ca": false, + "kernel_append_params": "", }, }, @@ -603,11 +610,12 @@ func TestDriverInfo(t *testing.T) { Scenario: "Redfish http", input: "redfish+http://192.168.122.1/foo/bar", expects: map[string]interface{}{ - "redfish_address": "http://192.168.122.1", - "redfish_system_id": "/foo/bar", - "redfish_password": "", - "redfish_username": "", - "redfish_verify_ca": false, + "redfish_address": "http://192.168.122.1", + "redfish_system_id": "/foo/bar", + "redfish_password": "", + "redfish_username": "", + "redfish_verify_ca": false, + "kernel_append_params": "", }, }, @@ -615,11 +623,12 @@ func TestDriverInfo(t *testing.T) { Scenario: "Redfish https", input: "redfish+https://192.168.122.1/foo/bar", expects: map[string]interface{}{ - "redfish_address": "https://192.168.122.1", - "redfish_system_id": "/foo/bar", - "redfish_password": "", - "redfish_username": "", - "redfish_verify_ca": false, + "redfish_address": "https://192.168.122.1", + "redfish_system_id": "/foo/bar", + "redfish_password": "", + "redfish_username": "", + "redfish_verify_ca": false, + "kernel_append_params": "", }, }, @@ -627,11 +636,12 @@ func TestDriverInfo(t *testing.T) { Scenario: "Redfish port", input: "redfish://192.168.122.1:8080/foo/bar", expects: map[string]interface{}{ - "redfish_address": "https://192.168.122.1:8080", - "redfish_system_id": "/foo/bar", - "redfish_password": "", - "redfish_username": "", - "redfish_verify_ca": false, + "redfish_address": "https://192.168.122.1:8080", + "redfish_system_id": "/foo/bar", + "redfish_password": "", + "redfish_username": "", + "redfish_verify_ca": false, + "kernel_append_params": "", }, }, @@ -639,11 +649,12 @@ func TestDriverInfo(t *testing.T) { Scenario: "Redfish ipv6", input: "redfish://[fe80::fc33:62ff:fe83:8a76]/foo/bar", expects: map[string]interface{}{ - "redfish_address": "https://[fe80::fc33:62ff:fe83:8a76]", - "redfish_system_id": "/foo/bar", - "redfish_password": "", - "redfish_username": "", - "redfish_verify_ca": false, + "redfish_address": "https://[fe80::fc33:62ff:fe83:8a76]", + "redfish_system_id": "/foo/bar", + "redfish_password": "", + "redfish_username": "", + "redfish_verify_ca": false, + "kernel_append_params": "", }, }, @@ -651,11 +662,12 @@ func TestDriverInfo(t *testing.T) { Scenario: "Redfish ipv6 port", input: "redfish://[fe80::fc33:62ff:fe83:8a76]:8080/foo", expects: map[string]interface{}{ - "redfish_address": "https://[fe80::fc33:62ff:fe83:8a76]:8080", - "redfish_system_id": "/foo", - "redfish_password": "", - "redfish_username": "", - "redfish_verify_ca": false, + "redfish_address": "https://[fe80::fc33:62ff:fe83:8a76]:8080", + "redfish_system_id": "/foo", + "redfish_password": "", + "redfish_username": "", + "redfish_verify_ca": false, + "kernel_append_params": "", }, }, @@ -663,10 +675,11 @@ func TestDriverInfo(t *testing.T) { Scenario: "Redfish no system ID", input: "redfish+https://192.168.122.1/", expects: map[string]interface{}{ - "redfish_address": "https://192.168.122.1", - "redfish_password": "", - "redfish_username": "", - "redfish_verify_ca": false, + "redfish_address": "https://192.168.122.1", + "redfish_password": "", + "redfish_username": "", + "redfish_verify_ca": false, + "kernel_append_params": "", }, }, @@ -674,10 +687,11 @@ func TestDriverInfo(t *testing.T) { Scenario: "Redfish wrong system ID", input: "redfish+https://192.168.122.1/redfish/v1/", expects: map[string]interface{}{ - "redfish_address": "https://192.168.122.1", - "redfish_password": "", - "redfish_username": "", - "redfish_verify_ca": false, + "redfish_address": "https://192.168.122.1", + "redfish_password": "", + "redfish_username": "", + "redfish_verify_ca": false, + "kernel_append_params": "", }, }, @@ -685,11 +699,12 @@ func TestDriverInfo(t *testing.T) { Scenario: "Redfish virtual media", input: "redfish-virtualmedia://192.168.122.1/redfish/v1/foo/bar", expects: map[string]interface{}{ - "redfish_address": "https://192.168.122.1", - "redfish_system_id": "/redfish/v1/foo/bar", - "redfish_password": "", - "redfish_username": "", - "redfish_verify_ca": false, + "redfish_address": "https://192.168.122.1", + "redfish_system_id": "/redfish/v1/foo/bar", + "redfish_password": "", + "redfish_username": "", + "redfish_verify_ca": false, + "kernel_append_params": "", }, }, @@ -697,10 +712,11 @@ func TestDriverInfo(t *testing.T) { Scenario: "Redfish virtual media wrong system ID", input: "redfish-virtualmedia://192.168.122.1/redfish/v1/", expects: map[string]interface{}{ - "redfish_address": "https://192.168.122.1", - "redfish_password": "", - "redfish_username": "", - "redfish_verify_ca": false, + "redfish_address": "https://192.168.122.1", + "redfish_password": "", + "redfish_username": "", + "redfish_verify_ca": false, + "kernel_append_params": "", }, }, @@ -708,11 +724,12 @@ func TestDriverInfo(t *testing.T) { Scenario: "ilo5 virtual media", input: "ilo5-virtualmedia://192.168.122.1/foo/bar", expects: map[string]interface{}{ - "redfish_address": "https://192.168.122.1", - "redfish_system_id": "/foo/bar", - "redfish_password": "", - "redfish_username": "", - "redfish_verify_ca": false, + "redfish_address": "https://192.168.122.1", + "redfish_system_id": "/foo/bar", + "redfish_password": "", + "redfish_username": "", + "redfish_verify_ca": false, + "kernel_append_params": "", }, }, @@ -720,11 +737,12 @@ func TestDriverInfo(t *testing.T) { Scenario: "idrac redfish", input: "idrac-redfish://192.168.122.1/foo/bar", expects: map[string]interface{}{ - "redfish_address": "https://192.168.122.1", - "redfish_system_id": "/foo/bar", - "redfish_password": "", - "redfish_username": "", - "redfish_verify_ca": false, + "redfish_address": "https://192.168.122.1", + "redfish_system_id": "/foo/bar", + "redfish_password": "", + "redfish_username": "", + "redfish_verify_ca": false, + "kernel_append_params": "", }, }, @@ -732,11 +750,12 @@ func TestDriverInfo(t *testing.T) { Scenario: "idrac virtual media", input: "idrac-virtualmedia://192.168.122.1/redfish/v1/foo/bar", expects: map[string]interface{}{ - "redfish_address": "https://192.168.122.1", - "redfish_system_id": "/redfish/v1/foo/bar", - "redfish_password": "", - "redfish_username": "", - "redfish_verify_ca": false, + "redfish_address": "https://192.168.122.1", + "redfish_system_id": "/redfish/v1/foo/bar", + "redfish_password": "", + "redfish_username": "", + "redfish_verify_ca": false, + "kernel_append_params": "", }, }, @@ -744,10 +763,11 @@ func TestDriverInfo(t *testing.T) { Scenario: "idrac virtual media wrong system ID", input: "idrac-virtualmedia://192.168.122.1/redfish/v1/", expects: map[string]interface{}{ - "redfish_address": "https://192.168.122.1", - "redfish_password": "", - "redfish_username": "", - "redfish_verify_ca": false, + "redfish_address": "https://192.168.122.1", + "redfish_password": "", + "redfish_username": "", + "redfish_verify_ca": false, + "kernel_append_params": "", }, }, } { @@ -756,7 +776,7 @@ func TestDriverInfo(t *testing.T) { if err != nil { t.Fatalf("unexpected parse error: %v", err) } - di := acc.DriverInfo(Credentials{}) + di := acc.DriverInfo(Credentials{}, "") // If a key is present when it should not, this will catch it if len(di) != len(tc.expects) { t.Fatalf("Number of items do not match: %v and %v, %#v", len(di), diff --git a/pkg/hardwareutils/bmc/idrac_virtualmedia.go b/pkg/hardwareutils/bmc/idrac_virtualmedia.go index 838d62fa0f..53cc8113ed 100644 --- a/pkg/hardwareutils/bmc/idrac_virtualmedia.go +++ b/pkg/hardwareutils/bmc/idrac_virtualmedia.go @@ -50,8 +50,8 @@ func (a *redfishiDracVirtualMediaAccessDetails) DisableCertificateVerification() // pre-populated with the access information, and the caller is // expected to add any other information that might be needed (such as // the kernel and ramdisk locations). -func (a *redfishiDracVirtualMediaAccessDetails) DriverInfo(bmcCreds Credentials) map[string]interface{} { - return a.redfishAccessDetails.DriverInfo(bmcCreds) +func (a *redfishiDracVirtualMediaAccessDetails) DriverInfo(bmcCreds Credentials, preProvExtraKernParams string) map[string]interface{} { + return a.redfishAccessDetails.DriverInfo(bmcCreds, preProvExtraKernParams) } // iDrac Virtual Media Overrides diff --git a/pkg/hardwareutils/bmc/ipmi.go b/pkg/hardwareutils/bmc/ipmi.go index 1d66551793..aba81ef8c8 100644 --- a/pkg/hardwareutils/bmc/ipmi.go +++ b/pkg/hardwareutils/bmc/ipmi.go @@ -71,13 +71,14 @@ func (a *ipmiAccessDetails) DisableCertificateVerification() bool { // pre-populated with the access information, and the caller is // expected to add any other information that might be needed (such as // the kernel and ramdisk locations). -func (a *ipmiAccessDetails) DriverInfo(bmcCreds Credentials) map[string]interface{} { +func (a *ipmiAccessDetails) DriverInfo(bmcCreds Credentials, preProvExtraKernParams string) map[string]interface{} { result := map[string]interface{}{ - "ipmi_port": a.portNum, - "ipmi_username": bmcCreds.Username, - "ipmi_password": bmcCreds.Password, - "ipmi_address": a.hostname, - "ipmi_priv_level": a.privilegelevel, + "ipmi_port": a.portNum, + "ipmi_username": bmcCreds.Username, + "ipmi_password": bmcCreds.Password, + "ipmi_address": a.hostname, + "ipmi_priv_level": a.privilegelevel, + "kernel_append_params": preProvExtraKernParams, } if a.disableCertificateVerification { diff --git a/pkg/hardwareutils/bmc/irmc.go b/pkg/hardwareutils/bmc/irmc.go index 06a9741c59..5c358435ab 100644 --- a/pkg/hardwareutils/bmc/irmc.go +++ b/pkg/hardwareutils/bmc/irmc.go @@ -54,14 +54,15 @@ func (a *iRMCAccessDetails) DisableCertificateVerification() bool { // pre-populated with the access information, and the caller is // expected to add any other information that might be needed (such as // the kernel and ramdisk locations). -func (a *iRMCAccessDetails) DriverInfo(bmcCreds Credentials) map[string]interface{} { +func (a *iRMCAccessDetails) DriverInfo(bmcCreds Credentials, preProvExtraKernParams string) map[string]interface{} { result := map[string]interface{}{ - "irmc_username": bmcCreds.Username, - "irmc_password": bmcCreds.Password, - "irmc_address": a.hostname, - "ipmi_username": bmcCreds.Username, - "ipmi_password": bmcCreds.Password, - "ipmi_address": a.hostname, + "irmc_username": bmcCreds.Username, + "irmc_password": bmcCreds.Password, + "irmc_address": a.hostname, + "ipmi_username": bmcCreds.Username, + "ipmi_password": bmcCreds.Password, + "ipmi_address": a.hostname, + "kernel_append_params": preProvExtraKernParams, } if a.disableCertificateVerification { diff --git a/pkg/hardwareutils/bmc/redfish.go b/pkg/hardwareutils/bmc/redfish.go index bcf78cb7a3..9dcd473892 100644 --- a/pkg/hardwareutils/bmc/redfish.go +++ b/pkg/hardwareutils/bmc/redfish.go @@ -83,11 +83,12 @@ func getRedfishAddress(bmcType, host string) string { // pre-populated with the access information, and the caller is // expected to add any other information that might be needed (such as // the kernel and ramdisk locations). -func (a *redfishAccessDetails) DriverInfo(bmcCreds Credentials) map[string]interface{} { +func (a *redfishAccessDetails) DriverInfo(bmcCreds Credentials, preProvExtraKernParams string) map[string]interface{} { result := map[string]interface{}{ - "redfish_username": bmcCreds.Username, - "redfish_password": bmcCreds.Password, - "redfish_address": getRedfishAddress(a.bmcType, a.host), + "redfish_username": bmcCreds.Username, + "redfish_password": bmcCreds.Password, + "redfish_address": getRedfishAddress(a.bmcType, a.host), + "kernel_append_params": preProvExtraKernParams, } trimmedPath := strings.Trim(a.path, "/") if trimmedPath != "" && trimmedPath != "redfish/v1" { diff --git a/pkg/hardwareutils/bmc/redfish_https.go b/pkg/hardwareutils/bmc/redfish_https.go index b51a972ca3..cbea390b86 100644 --- a/pkg/hardwareutils/bmc/redfish_https.go +++ b/pkg/hardwareutils/bmc/redfish_https.go @@ -51,12 +51,13 @@ func (a *redfishHTTPBootMediaAccessDetails) DisableCertificateVerification() boo // pre-populated with the access information, and the caller is // expected to add any other information that might be needed (such as // the kernel and ramdisk locations). -func (a *redfishHTTPBootMediaAccessDetails) DriverInfo(bmcCreds Credentials) map[string]interface{} { +func (a *redfishHTTPBootMediaAccessDetails) DriverInfo(bmcCreds Credentials, preProvExtraKernParams string) map[string]interface{} { result := map[string]interface{}{ - "redfish_system_id": a.path, - "redfish_username": bmcCreds.Username, - "redfish_password": bmcCreds.Password, - "redfish_address": getRedfishAddress(a.bmcType, a.host), + "redfish_system_id": a.path, + "redfish_username": bmcCreds.Username, + "redfish_password": bmcCreds.Password, + "redfish_address": getRedfishAddress(a.bmcType, a.host), + "kernel_append_params": preProvExtraKernParams, } if a.disableCertificateVerification { diff --git a/pkg/hardwareutils/bmc/redfish_virtualmedia.go b/pkg/hardwareutils/bmc/redfish_virtualmedia.go index 1178a1f4e2..edb660f1cc 100644 --- a/pkg/hardwareutils/bmc/redfish_virtualmedia.go +++ b/pkg/hardwareutils/bmc/redfish_virtualmedia.go @@ -49,8 +49,8 @@ func (a *redfishVirtualMediaAccessDetails) DisableCertificateVerification() bool // pre-populated with the access information, and the caller is // expected to add any other information that might be needed (such as // the kernel and ramdisk locations). -func (a *redfishVirtualMediaAccessDetails) DriverInfo(bmcCreds Credentials) map[string]interface{} { - return a.redfishAccessDetails.DriverInfo(bmcCreds) +func (a *redfishVirtualMediaAccessDetails) DriverInfo(bmcCreds Credentials, preProvExtraKernParams string) map[string]interface{} { + return a.redfishAccessDetails.DriverInfo(bmcCreds, preProvExtraKernParams) } func (a *redfishVirtualMediaAccessDetails) BIOSInterface() string { diff --git a/pkg/provisioner/demo/demo.go b/pkg/provisioner/demo/demo.go index 9234e99217..c5ef69f2b2 100644 --- a/pkg/provisioner/demo/demo.go +++ b/pkg/provisioner/demo/demo.go @@ -269,7 +269,7 @@ func (p *demoProvisioner) Provision(_ provisioner.ProvisionData, _ bool) (result // Deprovision removes the host from the image. It may be called // multiple times, and should return true for its dirty flag until the // deprovisioning operation is completed. -func (p *demoProvisioner) Deprovision(_ bool, _ metal3api.AutomatedCleaningMode) (result provisioner.Result, err error) { +func (p *demoProvisioner) Deprovision(_ provisioner.DeprovisionData, _ bool, _ metal3api.AutomatedCleaningMode) (result provisioner.Result, err error) { p.log.Info("deprovisioning host") return result, nil } diff --git a/pkg/provisioner/fixture/fixture.go b/pkg/provisioner/fixture/fixture.go index 9ddea89533..35bd9da947 100644 --- a/pkg/provisioner/fixture/fixture.go +++ b/pkg/provisioner/fixture/fixture.go @@ -296,7 +296,7 @@ func (p *fixtureProvisioner) Provision(data provisioner.ProvisionData, _ bool) ( // Deprovision removes the host from the image. It may be called // multiple times, and should return true for its dirty flag until the // deprovisioning operation is completed. -func (p *fixtureProvisioner) Deprovision(_ bool, _ metal3api.AutomatedCleaningMode) (result provisioner.Result, err error) { +func (p *fixtureProvisioner) Deprovision(_ provisioner.DeprovisionData, _ bool, _ metal3api.AutomatedCleaningMode) (result provisioner.Result, err error) { p.log.Info("ensuring host is deprovisioned") result.RequeueAfter = deprovisionRequeueDelay diff --git a/pkg/provisioner/ironic/inspecthardware.go b/pkg/provisioner/ironic/inspecthardware.go index 642abd6042..1ca0ec57b1 100644 --- a/pkg/provisioner/ironic/inspecthardware.go +++ b/pkg/provisioner/ironic/inspecthardware.go @@ -24,14 +24,21 @@ func (p *ironicProvisioner) abortInspection(ironicNode *nodes.Node) (result prov } func (p *ironicProvisioner) startInspection(data provisioner.InspectData, ironicNode *nodes.Node) (result provisioner.Result, started bool, err error) { - _, started, result, err = p.tryUpdateNode( - ironicNode, - clients.UpdateOptsBuilder(p.log). - SetPropertiesOpts(clients.UpdateOptsData{ - "capabilities": buildCapabilitiesValue(ironicNode, data.BootMode), - }, ironicNode), - ) + inspectionNodeUpdater := clients.UpdateOptsBuilder(p.log) + inspectionNodeUpdater.SetPropertiesOpts(clients.UpdateOptsData{ + "capabilities": buildCapabilitiesValue(ironicNode, data.BootMode), + }, ironicNode) + inspectionNodeUpdater.SetDriverInfoOpts(clients.UpdateOptsData{ + "kernel_append_params": fmtPreprovExtraKernParams(data.PreprovisioningExtraKernelParams), + }, ironicNode) + + _, started, result, err = p.tryUpdateNode(ironicNode, inspectionNodeUpdater) + if err != nil { + p.log.Error(err, "failed to update node", "node", ironicNode.UUID) + return + } if !started { + p.log.Info("node update not started", "node", ironicNode.UUID) return } diff --git a/pkg/provisioner/ironic/ironic.go b/pkg/provisioner/ironic/ironic.go index 6c42c97995..38d8138fda 100644 --- a/pkg/provisioner/ironic/ironic.go +++ b/pkg/provisioner/ironic/ironic.go @@ -41,10 +41,11 @@ const ( nameSeparator = "~" customDeployPriority = 80 - deployKernelKey = "deploy_kernel" - deployRamdiskKey = "deploy_ramdisk" - deployISOKey = "deploy_iso" - kernelParamsKey = "kernel_append_params" + deployKernelKey = "deploy_kernel" + deployRamdiskKey = "deploy_ramdisk" + deployISOKey = "deploy_iso" + kernelParamsKey = "kernel_append_params" + defaultKernelParam = "%default%" ) type macAddressConflictError struct { @@ -318,6 +319,15 @@ func (p *ironicProvisioner) createPXEEnabledNodePort(uuid, macAddress string) er return nil } +func fmtPreprovExtraKernParams(params string) string { + trimmedParams := strings.TrimSpace(params) + if trimmedParams == "" { + return defaultKernelParam + } + // spaceing matters for kernel params + return defaultKernelParam + " " + params +} + // configureNode configures Node properties that are not related to any specific provisioning phase. // It populates the AutomatedClean field, as well as capabilities and architecture in Properties. // It also calls setDeployImage to populate IPA parameters in DriverInfo and @@ -325,7 +335,7 @@ func (p *ironicProvisioner) createPXEEnabledNodePort(uuid, macAddress string) er func (p *ironicProvisioner) configureNode(data provisioner.ManagementAccessData, ironicNode *nodes.Node, bmcAccess bmc.AccessDetails) (result provisioner.Result, err error) { updater := clients.UpdateOptsBuilder(p.log) - deployImageInfo := setDeployImage(p.config, bmcAccess, data.PreprovisioningImage) + deployImageInfo := setDeployImage(p.config, bmcAccess, data.PreprovisioningImage, data) updater.SetDriverInfoOpts(deployImageInfo, ironicNode) updater.SetTopLevelOpt("automated_clean", @@ -447,7 +457,7 @@ func setExternalURL(p *ironicProvisioner, driverInfo map[string]any) map[string] // setDeployImage configures the IPA ramdisk parameters in the Node's DriverInfo. // It can use either the provided PreprovisioningImage or the global configuration from ironicConfig. -func setDeployImage(config ironicConfig, accessDetails bmc.AccessDetails, hostImage *provisioner.PreprovisioningImage) clients.UpdateOptsData { +func setDeployImage(config ironicConfig, accessDetails bmc.AccessDetails, hostImage *provisioner.PreprovisioningImage, data provisioner.ManagementAccessData) clients.UpdateOptsData { deployImageInfo := clients.UpdateOptsData{ deployKernelKey: nil, deployRamdiskKey: nil, @@ -473,9 +483,22 @@ func setDeployImage(config ironicConfig, accessDetails bmc.AccessDetails, hostIm deployImageInfo[deployKernelKey] = config.deployKernelURL } deployImageInfo[deployRamdiskKey] = hostImage.ImageURL + // Extra params from the kernel have been already posted to + // Ironic node API in the Register function, so extra kernel args in driver info need + // update only if the dynamically generated preprov image requires it if hostImage.ExtraKernelParams != "" { - // Using %default% prevents overriding the config in ironic-image - deployImageInfo[kernelParamsKey] = "%default% " + hostImage.ExtraKernelParams + // Combine the default ironic node kernel params with the extra + // params from the BMH and the dynamically generated preprov image + comExtraKernelParams := defaultKernelParam + trimDataKParams := strings.TrimSpace(data.PreprovisioningExtraKernelParams) + trimPreprovImgKParams := strings.TrimSpace(hostImage.ExtraKernelParams) + if trimDataKParams != "" { + comExtraKernelParams += " " + data.PreprovisioningExtraKernelParams + } + if trimPreprovImgKParams != "" { + comExtraKernelParams += " " + hostImage.ExtraKernelParams + } + deployImageInfo[kernelParamsKey] = comExtraKernelParams } return deployImageInfo } @@ -691,8 +714,8 @@ func (p *ironicProvisioner) setCustomDeployUpdateOptsForNode(ironicNode *nodes.N SetTopLevelOpt("deploy_interface", "custom-agent", ironicNode.DeployInterface) } -// getInstanceUpdateOpts constructs InstanceInfo options required to provision a Node in Ironic. -func (p *ironicProvisioner) getInstanceUpdateOpts(ironicNode *nodes.Node, data provisioner.ProvisionData) *clients.NodeUpdater { +// getProvisioningInstanceUpdateOptsForNode constructs InstanceInfo and DriverInfo options required to provision a Node in Ironic. +func (p *ironicProvisioner) getProvisioningInstanceUpdateOptsForNode(ironicNode *nodes.Node, data provisioner.ProvisionData) *clients.NodeUpdater { updater := clients.UpdateOptsBuilder(p.log) hasCustomDeploy := data.CustomDeploy != nil && data.CustomDeploy.Method != "" @@ -716,6 +739,41 @@ func (p *ironicProvisioner) getInstanceUpdateOpts(ironicNode *nodes.Node, data p p.setDirectDeployUpdateOptsForNode(ironicNode, &data.Image, updater) } + driverOpts := clients.UpdateOptsData{ + "kernel_append_params": fmtPreprovExtraKernParams(data.PreprovisioningExtraKernelParams), + } + updater.SetDriverInfoOpts(driverOpts, ironicNode) + + return updater +} + +func (p *ironicProvisioner) getPrepareOptsForNode(ironicNode *nodes.Node, data provisioner.PrepareData) *clients.NodeUpdater { + updater := clients.UpdateOptsBuilder(p.log) + driverOpts := clients.UpdateOptsData{ + "kernel_append_params": fmtPreprovExtraKernParams(data.PreprovisioningExtraKernelParams), + } + updater.SetDriverInfoOpts(driverOpts, ironicNode) + + return updater +} + +func (p *ironicProvisioner) getDeprovisionOptsForNode(ironicNode *nodes.Node, data provisioner.DeprovisionData) *clients.NodeUpdater { + updater := clients.UpdateOptsBuilder(p.log) + driverOpts := clients.UpdateOptsData{ + "kernel_append_params": fmtPreprovExtraKernParams(data.PreprovisioningExtraKernelParams), + } + updater.SetDriverInfoOpts(driverOpts, ironicNode) + + return updater +} + +func (p *ironicProvisioner) getServicingOptsForNode(ironicNode *nodes.Node, data provisioner.ServicingData) *clients.NodeUpdater { + updater := clients.UpdateOptsBuilder(p.log) + driverOpts := clients.UpdateOptsData{ + "kernel_append_params": fmtPreprovExtraKernParams(data.PreprovisioningExtraKernelParams), + } + updater.SetDriverInfoOpts(driverOpts, ironicNode) + return updater } @@ -811,9 +869,8 @@ func (p *ironicProvisioner) GetFirmwareComponents() ([]metal3api.FirmwareCompone func (p *ironicProvisioner) setUpForProvisioning(ironicNode *nodes.Node, data provisioner.ProvisionData) (result provisioner.Result, err error) { p.log.Info("starting provisioning", "node properties", ironicNode.Properties) - ironicNode, success, result, err := p.tryUpdateNode(ironicNode, - p.getInstanceUpdateOpts(ironicNode, data)) + p.getProvisioningInstanceUpdateOptsForNode(ironicNode, data)) if !success { return result, err } @@ -1064,14 +1121,22 @@ func (p *ironicProvisioner) startManualCleaning(bmcAccess bmc.AccessDetails, iro // Set raid configuration result, err = setTargetRAIDCfg(p, bmcAccess.RAIDInterface(), ironicNode, data) if result.Dirty || result.ErrorMessage != "" || err != nil { - return + return success, result, err } // Build manual clean steps cleanSteps, err := p.buildManualCleaningSteps(bmcAccess, data) if err != nil { result, err = operationFailed(err.Error()) - return + return success, result, err + } + + // Updating the kernel_append_params field of the Ironic node's + // API drive_info endpoint + _, updateSuccess, updateResult, updateErr := p.tryUpdateNode(ironicNode, + p.getPrepareOptsForNode(ironicNode, data)) + if !updateSuccess { + return updateSuccess, updateResult, updateErr } // Start manual clean @@ -1086,7 +1151,7 @@ func (p *ironicProvisioner) startManualCleaning(bmcAccess bmc.AccessDetails, iro ) } result, err = operationComplete() - return + return success, result, err } // Prepare remove existing configuration and set new configuration. @@ -1409,7 +1474,7 @@ func (p *ironicProvisioner) syncAutomatedClean(ironicNode *nodes.Node, automated // Deprovision removes the host from the image. It may be called // multiple times, and should return true for its dirty flag until the // deprovisioning operation is completed. -func (p *ironicProvisioner) Deprovision(restartOnFailure bool, automatedCleaningMode metal3api.AutomatedCleaningMode) (result provisioner.Result, err error) { +func (p *ironicProvisioner) Deprovision(data provisioner.DeprovisionData, restartOnFailure bool, automatedCleaningMode metal3api.AutomatedCleaningMode) (result provisioner.Result, err error) { p.log.Info("deprovisioning") ironicNode, err := p.getNode() @@ -1417,6 +1482,13 @@ func (p *ironicProvisioner) Deprovision(restartOnFailure bool, automatedCleaning return transientError(err) } + // Updating the kernel_append_params field of the Ironic node's + // API drive_info endpoint + _, updateSuccess, result, err := p.tryUpdateNode(ironicNode, p.getDeprovisionOptsForNode(ironicNode, data)) + if !updateSuccess { + return result, err + } + p.log.Info("deprovisioning host", "ID", ironicNode.UUID, "lastError", ironicNode.LastError, diff --git a/pkg/provisioner/ironic/prepare_test.go b/pkg/provisioner/ironic/prepare_test.go index abe9e6e851..e7fed5ddbc 100644 --- a/pkg/provisioner/ironic/prepare_test.go +++ b/pkg/provisioner/ironic/prepare_test.go @@ -17,21 +17,21 @@ import ( type RAIDTestBMC struct{} -func (r *RAIDTestBMC) Type() string { return "raid-test" } -func (r *RAIDTestBMC) NeedsMAC() bool { return false } -func (r *RAIDTestBMC) Driver() string { return "raid-test" } -func (r *RAIDTestBMC) DisableCertificateVerification() bool { return false } -func (r *RAIDTestBMC) DriverInfo(bmc.Credentials) (i map[string]any) { return } -func (r *RAIDTestBMC) SupportsISOPreprovisioningImage() bool { return false } -func (r *RAIDTestBMC) BIOSInterface() string { return "" } -func (r *RAIDTestBMC) BootInterface() string { return "" } -func (r *RAIDTestBMC) FirmwareInterface() string { return "" } -func (r *RAIDTestBMC) ManagementInterface() string { return "" } -func (r *RAIDTestBMC) PowerInterface() string { return "" } -func (r *RAIDTestBMC) RAIDInterface() string { return "" } -func (r *RAIDTestBMC) VendorInterface() string { return "" } -func (r *RAIDTestBMC) SupportsSecureBoot() bool { return false } -func (r *RAIDTestBMC) RequiresProvisioningNetwork() bool { return true } +func (r *RAIDTestBMC) Type() string { return "raid-test" } +func (r *RAIDTestBMC) NeedsMAC() bool { return false } +func (r *RAIDTestBMC) Driver() string { return "raid-test" } +func (r *RAIDTestBMC) DisableCertificateVerification() bool { return false } +func (r *RAIDTestBMC) DriverInfo(bmc.Credentials, string) (i map[string]interface{}) { return } +func (r *RAIDTestBMC) SupportsISOPreprovisioningImage() bool { return false } +func (r *RAIDTestBMC) BIOSInterface() string { return "" } +func (r *RAIDTestBMC) BootInterface() string { return "" } +func (r *RAIDTestBMC) FirmwareInterface() string { return "" } +func (r *RAIDTestBMC) ManagementInterface() string { return "" } +func (r *RAIDTestBMC) PowerInterface() string { return "" } +func (r *RAIDTestBMC) RAIDInterface() string { return "" } +func (r *RAIDTestBMC) VendorInterface() string { return "" } +func (r *RAIDTestBMC) SupportsSecureBoot() bool { return false } +func (r *RAIDTestBMC) RequiresProvisioningNetwork() bool { return true } func (r *RAIDTestBMC) BuildBIOSSettings(_ *bmc.FirmwareConfig) ([]map[string]string, error) { return nil, nil } diff --git a/pkg/provisioner/ironic/provision_test.go b/pkg/provisioner/ironic/provision_test.go index edda184597..1123d7f1fc 100644 --- a/pkg/provisioner/ironic/provision_test.go +++ b/pkg/provisioner/ironic/provision_test.go @@ -291,7 +291,7 @@ func TestDeprovision(t *testing.T) { }, { name: "deleting state", - ironic: testserver.NewIronic(t).Node(nodes.Node{ + ironic: testserver.NewIronic(t).WithDefaultResponses().Node(nodes.Node{ ProvisionState: string(nodes.Deleting), UUID: nodeUUID, }), @@ -300,7 +300,7 @@ func TestDeprovision(t *testing.T) { }, { name: "cleaning state", - ironic: testserver.NewIronic(t).Node(nodes.Node{ + ironic: testserver.NewIronic(t).WithDefaultResponses().Node(nodes.Node{ ProvisionState: string(nodes.Cleaning), UUID: nodeUUID, }), @@ -309,7 +309,7 @@ func TestDeprovision(t *testing.T) { }, { name: "cleanWait state", - ironic: testserver.NewIronic(t).Node(nodes.Node{ + ironic: testserver.NewIronic(t).WithDefaultResponses().Node(nodes.Node{ ProvisionState: string(nodes.CleanWait), UUID: nodeUUID, }), @@ -351,7 +351,7 @@ func TestDeprovision(t *testing.T) { t.Fatalf("could not create provisioner: %s", err) } - result, err := prov.Deprovision(false, metal3api.CleaningModeMetadata) + result, err := prov.Deprovision(provisioner.DeprovisionData{}, false, metal3api.CleaningModeMetadata) assert.Equal(t, tc.expectedDirty, result.Dirty) assert.Equal(t, tc.expectedErrorMessage, result.ErrorMessage != "") @@ -859,7 +859,7 @@ func TestGetUpdateOptsForNodeWithRootHints(t *testing.T) { BootMode: metal3api.DefaultBootMode, RootDeviceHints: host.Status.Provisioning.RootDeviceHints, } - patches := prov.getInstanceUpdateOpts(ironicNode, provData).Updates + patches := prov.getProvisioningInstanceUpdateOptsForNode(ironicNode, provData).Updates t.Logf("patches: %v", patches) @@ -955,7 +955,7 @@ func TestGetUpdateOptsForNodeVirtual(t *testing.T) { BootMode: metal3api.DefaultBootMode, HardwareProfile: hwProf, } - patches := prov.getInstanceUpdateOpts(ironicNode, provData).Updates + patches := prov.getProvisioningInstanceUpdateOptsForNode(ironicNode, provData).Updates t.Logf("patches: %v", patches) @@ -1054,7 +1054,7 @@ func TestGetUpdateOptsForNodeDell(t *testing.T) { BootMode: metal3api.DefaultBootMode, HardwareProfile: hwProf, } - patches := prov.getInstanceUpdateOpts(ironicNode, provData).Updates + patches := prov.getProvisioningInstanceUpdateOptsForNode(ironicNode, provData).Updates t.Logf("patches: %v", patches) @@ -1118,7 +1118,7 @@ func TestGetUpdateOptsForNodeLiveIso(t *testing.T) { Image: *host.Spec.Image, BootMode: metal3api.DefaultBootMode, } - patches := prov.getInstanceUpdateOpts(ironicNode, provData).Updates + patches := prov.getProvisioningInstanceUpdateOptsForNode(ironicNode, provData).Updates t.Logf("patches: %v", patches) @@ -1187,7 +1187,7 @@ func TestGetUpdateOptsForNodeImageToLiveIso(t *testing.T) { Image: *host.Spec.Image, BootMode: metal3api.DefaultBootMode, } - patches := prov.getInstanceUpdateOpts(ironicNode, provData).Updates + patches := prov.getProvisioningInstanceUpdateOptsForNode(ironicNode, provData).Updates t.Logf("patches: %v", patches) @@ -1267,7 +1267,7 @@ func TestGetUpdateOptsForNodeLiveIsoToImage(t *testing.T) { Image: *host.Spec.Image, BootMode: metal3api.DefaultBootMode, } - patches := prov.getInstanceUpdateOpts(ironicNode, provData).Updates + patches := prov.getProvisioningInstanceUpdateOptsForNode(ironicNode, provData).Updates t.Logf("patches: %v", patches) @@ -1341,7 +1341,7 @@ func TestGetUpdateOptsForNodeCustomDeploy(t *testing.T) { BootMode: metal3api.DefaultBootMode, CustomDeploy: host.Spec.CustomDeploy, } - patches := prov.getInstanceUpdateOpts(ironicNode, provData).Updates + patches := prov.getProvisioningInstanceUpdateOptsForNode(ironicNode, provData).Updates t.Logf("patches: %v", patches) @@ -1400,7 +1400,7 @@ func TestGetUpdateOptsForNodeCustomDeployWithImage(t *testing.T) { BootMode: metal3api.DefaultBootMode, CustomDeploy: host.Spec.CustomDeploy, } - patches := prov.getInstanceUpdateOpts(ironicNode, provData).Updates + patches := prov.getProvisioningInstanceUpdateOptsForNode(ironicNode, provData).Updates t.Logf("patches: %v", patches) @@ -1469,7 +1469,7 @@ func TestGetUpdateOptsForNodeImageToCustomDeploy(t *testing.T) { BootMode: metal3api.DefaultBootMode, CustomDeploy: host.Spec.CustomDeploy, } - patches := prov.getInstanceUpdateOpts(ironicNode, provData).Updates + patches := prov.getProvisioningInstanceUpdateOptsForNode(ironicNode, provData).Updates t.Logf("patches: %v", patches) @@ -1564,7 +1564,7 @@ func TestGetUpdateOptsForNodeSecureBoot(t *testing.T) { BootMode: metal3api.UEFISecureBoot, HardwareProfile: hwProf, } - patches := prov.getInstanceUpdateOpts(ironicNode, provData).Updates + patches := prov.getProvisioningInstanceUpdateOptsForNode(ironicNode, provData).Updates t.Logf("patches: %v", patches) diff --git a/pkg/provisioner/ironic/register.go b/pkg/provisioner/ironic/register.go index 7e2b5cf40b..1432b32e5c 100644 --- a/pkg/provisioner/ironic/register.go +++ b/pkg/provisioner/ironic/register.go @@ -101,7 +101,7 @@ func (p *ironicProvisioner) Register(data provisioner.ManagementAccessData, cred return result, "", err } - driverInfo := bmcAccess.DriverInfo(p.bmcCreds) + driverInfo := bmcAccess.DriverInfo(p.bmcCreds, fmtPreprovExtraKernParams(data.PreprovisioningExtraKernelParams)) driverInfo = setExternalURL(p, driverInfo) // If we have not found a node yet, we need to create one diff --git a/pkg/provisioner/ironic/register_test.go b/pkg/provisioner/ironic/register_test.go index 9675e26134..5365805379 100644 --- a/pkg/provisioner/ironic/register_test.go +++ b/pkg/provisioner/ironic/register_test.go @@ -994,6 +994,7 @@ func TestSetDeployImage(t *testing.T) { Config ironicConfig Driver bmc.AccessDetails Image *provisioner.PreprovisioningImage + Data provisioner.ManagementAccessData ExpectBuild bool ExpectISO bool ExpectPXE bool @@ -1210,7 +1211,7 @@ func TestSetDeployImage(t *testing.T) { for _, tc := range testCases { t.Run(tc.Scenario, func(t *testing.T) { - opts := setDeployImage(tc.Config, tc.Driver, tc.Image) + opts := setDeployImage(tc.Config, tc.Driver, tc.Image, tc.Data) switch { case tc.ExpectISO: diff --git a/pkg/provisioner/ironic/servicing.go b/pkg/provisioner/ironic/servicing.go index b31d9d0bc2..5c1b897b83 100644 --- a/pkg/provisioner/ironic/servicing.go +++ b/pkg/provisioner/ironic/servicing.go @@ -61,6 +61,14 @@ func (p *ironicProvisioner) startServicing(bmcAccess bmc.AccessDetails, ironicNo return } + // Updating the kernel_append_params field of the Ironic node's + // API drive_info endpoint + _, updateSuccess, updateResult, updateErr := p.tryUpdateNode(ironicNode, + p.getServicingOptsForNode(ironicNode, data)) + if !updateSuccess { + return updateSuccess, updateResult, updateErr + } + // Start servicing if len(serviceSteps) != 0 { p.log.Info("remove existing configuration and set new configuration", "serviceSteps", serviceSteps) diff --git a/pkg/provisioner/ironic/servicing_test.go b/pkg/provisioner/ironic/servicing_test.go index a728c709bc..5fde6220ed 100644 --- a/pkg/provisioner/ironic/servicing_test.go +++ b/pkg/provisioner/ironic/servicing_test.go @@ -18,21 +18,21 @@ import ( type BIOSTestBMC struct{} -func (r *BIOSTestBMC) Type() string { return "bios-test" } -func (r *BIOSTestBMC) NeedsMAC() bool { return false } -func (r *BIOSTestBMC) Driver() string { return "bios-test" } -func (r *BIOSTestBMC) DisableCertificateVerification() bool { return false } -func (r *BIOSTestBMC) DriverInfo(bmc.Credentials) (i map[string]any) { return } -func (r *BIOSTestBMC) SupportsISOPreprovisioningImage() bool { return false } -func (r *BIOSTestBMC) BIOSInterface() string { return "" } -func (r *BIOSTestBMC) BootInterface() string { return "" } -func (r *BIOSTestBMC) FirmwareInterface() string { return "" } -func (r *BIOSTestBMC) ManagementInterface() string { return "" } -func (r *BIOSTestBMC) PowerInterface() string { return "" } -func (r *BIOSTestBMC) RAIDInterface() string { return "" } -func (r *BIOSTestBMC) VendorInterface() string { return "" } -func (r *BIOSTestBMC) SupportsSecureBoot() bool { return false } -func (r *BIOSTestBMC) RequiresProvisioningNetwork() bool { return true } +func (r *BIOSTestBMC) Type() string { return "bios-test" } +func (r *BIOSTestBMC) NeedsMAC() bool { return false } +func (r *BIOSTestBMC) Driver() string { return "bios-test" } +func (r *BIOSTestBMC) DisableCertificateVerification() bool { return false } +func (r *BIOSTestBMC) DriverInfo(bmc.Credentials, string) (i map[string]interface{}) { return } +func (r *BIOSTestBMC) SupportsISOPreprovisioningImage() bool { return false } +func (r *BIOSTestBMC) BIOSInterface() string { return "" } +func (r *BIOSTestBMC) BootInterface() string { return "" } +func (r *BIOSTestBMC) FirmwareInterface() string { return "" } +func (r *BIOSTestBMC) ManagementInterface() string { return "" } +func (r *BIOSTestBMC) PowerInterface() string { return "" } +func (r *BIOSTestBMC) RAIDInterface() string { return "" } +func (r *BIOSTestBMC) VendorInterface() string { return "" } +func (r *BIOSTestBMC) SupportsSecureBoot() bool { return false } +func (r *BIOSTestBMC) RequiresProvisioningNetwork() bool { return true } func (r *BIOSTestBMC) BuildBIOSSettings(_ *bmc.FirmwareConfig) ([]map[string]string, error) { return nil, nil } diff --git a/pkg/provisioner/ironic/testbmc/testbmc.go b/pkg/provisioner/ironic/testbmc/testbmc.go index 362e0d8bd1..d92e341dd7 100644 --- a/pkg/provisioner/ironic/testbmc/testbmc.go +++ b/pkg/provisioner/ironic/testbmc/testbmc.go @@ -16,6 +16,7 @@ func NewTestBMCAccessDetails(parsedURL *url.URL, disableCertificateVerification bmcType: parsedURL.Scheme, hostname: parsedURL.Hostname(), disableCertificateVerification: disableCertificateVerification, + driverInfo: "", }, nil } @@ -23,6 +24,7 @@ type testAccessDetails struct { bmcType string hostname string disableCertificateVerification bool + driverInfo string } func (a *testAccessDetails) Type() string { @@ -48,12 +50,13 @@ func (a *testAccessDetails) DisableCertificateVerification() bool { // pre-populated with the access information, and the caller is // expected to add any other information that might be needed (such as // the kernel and ramdisk locations). -func (a *testAccessDetails) DriverInfo(bmcCreds bmc.Credentials) map[string]any { - result := map[string]any{ - "test_port": "42", - "test_username": bmcCreds.Username, - "test_password": bmcCreds.Password, - "test_address": a.hostname, +func (a *testAccessDetails) DriverInfo(bmcCreds bmc.Credentials, preProvExtraKernParams string) map[string]interface{} { + result := map[string]interface{}{ + "test_port": "42", + "test_username": bmcCreds.Username, + "test_password": bmcCreds.Password, + "test_address": a.hostname, + "kernel_append_params": preProvExtraKernParams, } if a.disableCertificateVerification { diff --git a/pkg/provisioner/provisioner.go b/pkg/provisioner/provisioner.go index 83892deea0..9eada6ef75 100644 --- a/pkg/provisioner/provisioner.go +++ b/pkg/provisioner/provisioner.go @@ -74,24 +74,29 @@ type PreprovisioningImage struct { } type ManagementAccessData struct { - BootMode metal3api.BootMode - AutomatedCleaningMode metal3api.AutomatedCleaningMode - State metal3api.ProvisioningState - OperationalStatus metal3api.OperationalStatus - CurrentImage *metal3api.Image - PreprovisioningImage *PreprovisioningImage - PreprovisioningNetworkData string - HasCustomDeploy bool - DisablePowerOff bool - CPUArchitecture string + BootMode metal3api.BootMode + AutomatedCleaningMode metal3api.AutomatedCleaningMode + State metal3api.ProvisioningState + CurrentImage *metal3api.Image + PreprovisioningImage *PreprovisioningImage + PreprovisioningNetworkData string + PreprovisioningExtraKernelParams string + HasCustomDeploy bool + DisablePowerOff bool + CPUArchitecture string } type AdoptData struct { State metal3api.ProvisioningState } +type DeprovisionData struct { + PreprovisioningExtraKernelParams string +} + type InspectData struct { - BootMode metal3api.BootMode + BootMode metal3api.BootMode + PreprovisioningExtraKernelParams string } // FirmwareConfig and FirmwareSettings are used for implementation of similar functionality @@ -101,29 +106,32 @@ type InspectData struct { // values are vendor specific. // TargetFirmwareSettings contains values that the user has changed. type PrepareData struct { - TargetRAIDConfig *metal3api.RAIDConfig - ActualRAIDConfig *metal3api.RAIDConfig - RootDeviceHints *metal3api.RootDeviceHints - FirmwareConfig *metal3api.FirmwareConfig - TargetFirmwareSettings metal3api.DesiredSettingsMap - ActualFirmwareSettings metal3api.SettingsMap - TargetFirmwareComponents []metal3api.FirmwareUpdate + TargetRAIDConfig *metal3api.RAIDConfig + ActualRAIDConfig *metal3api.RAIDConfig + RootDeviceHints *metal3api.RootDeviceHints + FirmwareConfig *metal3api.FirmwareConfig + TargetFirmwareSettings metal3api.DesiredSettingsMap + ActualFirmwareSettings metal3api.SettingsMap + TargetFirmwareComponents []metal3api.FirmwareUpdate + PreprovisioningExtraKernelParams string } type ServicingData struct { - FirmwareConfig *metal3api.FirmwareConfig - TargetFirmwareSettings metal3api.DesiredSettingsMap - ActualFirmwareSettings metal3api.SettingsMap - TargetFirmwareComponents []metal3api.FirmwareUpdate + FirmwareConfig *metal3api.FirmwareConfig + TargetFirmwareSettings metal3api.DesiredSettingsMap + ActualFirmwareSettings metal3api.SettingsMap + TargetFirmwareComponents []metal3api.FirmwareUpdate + PreprovisioningExtraKernelParams string } type ProvisionData struct { - Image metal3api.Image - HostConfig HostConfigData - BootMode metal3api.BootMode - HardwareProfile profile.Profile - RootDeviceHints *metal3api.RootDeviceHints - CustomDeploy *metal3api.CustomDeploy + Image metal3api.Image + HostConfig HostConfigData + BootMode metal3api.BootMode + HardwareProfile profile.Profile + RootDeviceHints *metal3api.RootDeviceHints + CustomDeploy *metal3api.CustomDeploy + PreprovisioningExtraKernelParams string } type HTTPHeaders []map[string]string @@ -177,7 +185,7 @@ type Provisioner interface { // the deprovisioning operation is completed. // The automatedCleaningMode parameter is used to ensure the Ironic node's // automated_clean setting is synchronized before deprovisioning starts. - Deprovision(restartOnFailure bool, automatedCleaningMode metal3api.AutomatedCleaningMode) (result Result, err error) + Deprovision(data DeprovisionData, restartOnFailure bool, automatedCleaningMode metal3api.AutomatedCleaningMode) (result Result, err error) // Delete removes the host from the provisioning system. It may be // called multiple times, and should return true for its dirty