Skip to content

Commit

Permalink
feat: provide a way to override IPMI PXE boot method on Server
Browse files Browse the repository at this point in the history
This adds a new field to the `Server` resource which can be used to
override default IPMI PXE boot method.

Fixes #987

Signed-off-by: Andrey Smirnov <andrey.smirnov@talos-systems.com>
  • Loading branch information
smira committed Nov 16, 2022
1 parent 831761a commit d8ef68b
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 50 deletions.
7 changes: 7 additions & 0 deletions app/sidero-controller-manager/api/v1alpha1/server_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@ type ServerSpec struct {
//
// +optional
BootFromDiskMethod siderotypes.BootFromDisk `json:"bootFromDiskMethod,omitempty"`
// PXEMode specifies the method to trigger PXE boot via IPMI.
//
// If not set, controller default is used.
// Valid values: uefi, bios.
//
// +optional
PXEMode siderotypes.PXEMode `json:"pxeMode,omitempty"`
}

const (
Expand Down
54 changes: 47 additions & 7 deletions app/sidero-controller-manager/api/v1alpha1/server_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@ func (r *Server) ValidateDelete() error {
func (r *Server) validate() error {
var allErrs field.ErrorList

allErrs = append(allErrs, r.validateBootFromDisk()...)
allErrs = append(allErrs, r.validatePXEMode()...)
allErrs = append(allErrs, r.validateConfigPatches()...)

if len(allErrs) == 0 {
return nil
}

return apierrors.NewInvalid(
schema.GroupKind{Group: GroupVersion.Group, Kind: "Server"},
r.Name, allErrs)
}

func (r *Server) validateBootFromDisk() (allErrs field.ErrorList) {
validValues := []siderotypes.BootFromDisk{
"",
siderotypes.BootIPXEExit,
Expand All @@ -82,6 +96,38 @@ func (r *Server) validate() error {
)
}

return allErrs
}

func (r *Server) validatePXEMode() (allErrs field.ErrorList) {
validValues := []siderotypes.PXEMode{
"",
siderotypes.PXEModeBIOS,
siderotypes.PXEModeUEFI,
}

var valid bool

for _, v := range validValues {
if r.Spec.PXEMode == v {
valid = true

break
}
}

if !valid {
allErrs = append(allErrs,
field.Invalid(field.NewPath("spec").Child("pxeMode"), r.Spec.BootFromDiskMethod,
fmt.Sprintf("valid values are: %q", validValues),
),
)
}

return allErrs
}

func (r *Server) validateConfigPatches() (allErrs field.ErrorList) {
for index, patch := range r.Spec.ConfigPatches {
if _, ok := operations[patch.Op]; !ok {
allErrs = append(allErrs,
Expand All @@ -92,13 +138,7 @@ func (r *Server) validate() error {
}
}

if len(allErrs) == 0 {
return nil
}

return apierrors.NewInvalid(
schema.GroupKind{Group: GroupVersion.Group, Kind: "Server"},
r.Name, allErrs)
return allErrs
}

func init() {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions app/sidero-controller-manager/api/v1alpha2/server_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,13 @@ type ServerSpec struct {
//
// +optional
BootFromDiskMethod siderotypes.BootFromDisk `json:"bootFromDiskMethod,omitempty"`
// PXEMode specifies the method to trigger PXE boot via IPMI.
//
// If not set, controller default is used.
// Valid values: uefi, bios.
//
// +optional
PXEMode siderotypes.PXEMode `json:"pxeMode,omitempty"`
}

const (
Expand Down
54 changes: 47 additions & 7 deletions app/sidero-controller-manager/api/v1alpha2/server_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,20 @@ func (r *Server) ValidateDelete() error {
func (r *Server) validate() error {
var allErrs field.ErrorList

allErrs = append(allErrs, r.validateBootFromDisk()...)
allErrs = append(allErrs, r.validatePXEMode()...)
allErrs = append(allErrs, r.validateConfigPatches()...)

if len(allErrs) == 0 {
return nil
}

return apierrors.NewInvalid(
schema.GroupKind{Group: GroupVersion.Group, Kind: "Server"},
r.Name, allErrs)
}

func (r *Server) validateBootFromDisk() (allErrs field.ErrorList) {
validValues := []siderotypes.BootFromDisk{
"",
siderotypes.BootIPXEExit,
Expand All @@ -88,6 +102,38 @@ func (r *Server) validate() error {
)
}

return allErrs
}

func (r *Server) validatePXEMode() (allErrs field.ErrorList) {
validValues := []siderotypes.PXEMode{
"",
siderotypes.PXEModeBIOS,
siderotypes.PXEModeUEFI,
}

var valid bool

for _, v := range validValues {
if r.Spec.PXEMode == v {
valid = true

break
}
}

if !valid {
allErrs = append(allErrs,
field.Invalid(field.NewPath("spec").Child("pxeMode"), r.Spec.BootFromDiskMethod,
fmt.Sprintf("valid values are: %q", validValues),
),
)
}

return allErrs
}

func (r *Server) validateConfigPatches() (allErrs field.ErrorList) {
for index, patch := range r.Spec.ConfigPatches {
if _, ok := operations[patch.Op]; !ok {
allErrs = append(allErrs,
Expand All @@ -98,13 +144,7 @@ func (r *Server) validate() error {
}
}

if len(allErrs) == 0 {
return nil
}

return apierrors.NewInvalid(
schema.GroupKind{Group: GroupVersion.Group, Kind: "Server"},
r.Name, allErrs)
return allErrs
}

func init() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,11 @@ spec:
type: object
pxeBootAlways:
type: boolean
pxeMode:
description: "PXEMode specifies the method to trigger PXE boot via
IPMI. \n If not set, controller default is used. Valid values: uefi,
bios."
type: string
system:
properties:
family:
Expand Down Expand Up @@ -702,6 +707,11 @@ spec:
type: object
pxeBootAlways:
type: boolean
pxeMode:
description: "PXEMode specifies the method to trigger PXE boot via
IPMI. \n If not set, controller default is used. Valid values: uefi,
bios."
type: string
required:
- accepted
type: object
Expand Down
13 changes: 9 additions & 4 deletions app/sidero-controller-manager/controllers/server_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import (
infrav1 "github.com/talos-systems/sidero/app/caps-controller-manager/api/v1alpha3"
metalv1 "github.com/talos-systems/sidero/app/sidero-controller-manager/api/v1alpha2"
"github.com/talos-systems/sidero/app/sidero-controller-manager/internal/power"
"github.com/talos-systems/sidero/app/sidero-controller-manager/internal/power/metal"
"github.com/talos-systems/sidero/app/sidero-controller-manager/pkg/constants"
siderotypes "github.com/talos-systems/sidero/app/sidero-controller-manager/pkg/types"
)

const (
Expand All @@ -49,7 +49,7 @@ type ServerReconciler struct {
Recorder record.EventRecorder

RebootTimeout time.Duration
PXEMode metal.PXEMode
PXEMode siderotypes.PXEMode
}

// +kubebuilder:rbac:groups=metal.sidero.dev,resources=servers,verbs=get;list;watch;create;update;patch;delete
Expand Down Expand Up @@ -102,6 +102,11 @@ func (r *ServerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
s.Status.Power = "on"
}

pxeMode := r.PXEMode
if s.Spec.PXEMode != "" {
pxeMode = s.Spec.PXEMode
}

f := func(ready bool, result ctrl.Result) (ctrl.Result, error) {
s.Status.Ready = ready

Expand Down Expand Up @@ -203,7 +208,7 @@ func (r *ServerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr

if !poweredOn {
// it's safe to set server to PXE boot even if it's already installed, as PXE server makes sure server is PXE booted only once
err = mgmtClient.SetPXE(r.PXEMode)
err = mgmtClient.SetPXE(pxeMode)
if err != nil {
log.Error(err, "failed to set PXE")
r.Recorder.Event(serverRef, corev1.EventTypeWarning, "Server Management", fmt.Sprintf("Failed to set to PXE boot once: %s.", err))
Expand Down Expand Up @@ -246,7 +251,7 @@ func (r *ServerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctr
return f(false, ctrl.Result{RequeueAfter: constants.DefaultRequeueAfter})
}

err = mgmtClient.SetPXE(r.PXEMode)
err = mgmtClient.SetPXE(pxeMode)
if err != nil {
log.Error(err, "failed to set PXE")
r.Recorder.Event(serverRef, corev1.EventTypeWarning, "Server Management", fmt.Sprintf("Failed to set to PXE boot once: %s.", err))
Expand Down
4 changes: 2 additions & 2 deletions app/sidero-controller-manager/internal/power/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
"time"

metalv1 "github.com/talos-systems/sidero/app/sidero-controller-manager/api/v1alpha2"
"github.com/talos-systems/sidero/app/sidero-controller-manager/internal/power/metal"
"github.com/talos-systems/sidero/app/sidero-controller-manager/pkg/types"
)

// Client provides management over simple API.
Expand Down Expand Up @@ -88,7 +88,7 @@ func (c *Client) PowerCycle() error {
}

// SetPXE makes sure the node will pxe boot next time.
func (c *Client) SetPXE(mode metal.PXEMode) error {
func (c *Client) SetPXE(mode types.PXEMode) error {
// no way to enforce mode via QEMU API
return c.postRequest("/pxeboot")
}
Expand Down
4 changes: 2 additions & 2 deletions app/sidero-controller-manager/internal/power/fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

package power

import "github.com/talos-systems/sidero/app/sidero-controller-manager/internal/power/metal"
import "github.com/talos-systems/sidero/app/sidero-controller-manager/pkg/types"

type fakeClient struct{}

Expand All @@ -20,7 +20,7 @@ func (fakeClient) PowerCycle() error {
return nil
}

func (fakeClient) SetPXE(mode metal.PXEMode) error {
func (fakeClient) SetPXE(mode types.PXEMode) error {
return nil
}

Expand Down
8 changes: 4 additions & 4 deletions app/sidero-controller-manager/internal/power/ipmi/ipmi.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
goipmi "github.com/pensando/goipmi"

metalv1 "github.com/talos-systems/sidero/app/sidero-controller-manager/api/v1alpha2"
"github.com/talos-systems/sidero/app/sidero-controller-manager/internal/power/metal"
"github.com/talos-systems/sidero/app/sidero-controller-manager/pkg/types"
)

// Link to the IPMI spec: https://www.intel.com/content/dam/www/public/us/en/documents/product-briefs/ipmi-second-gen-interface-spec-v2-rev1-1.pdf
Expand Down Expand Up @@ -93,11 +93,11 @@ func (c *Client) Status() (*goipmi.ChassisStatusResponse, error) {
}

// SetPXE makes sure the node will pxe boot next time.
func (c *Client) SetPXE(mode metal.PXEMode) error {
func (c *Client) SetPXE(mode types.PXEMode) error {
switch mode {
case metal.PXEModeBIOS:
case types.PXEModeBIOS:
return c.IPMIClient.SetBootDevice(goipmi.BootDevicePxe)
case metal.PXEModeUEFI:
case types.PXEModeUEFI:
return c.IPMIClient.SetBootDeviceEFI(goipmi.BootDevicePxe)
default:
return fmt.Errorf("unsupported mode %q", mode)
Expand Down
23 changes: 3 additions & 20 deletions app/sidero-controller-manager/internal/power/metal/metal.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,15 @@
// Package metal provides interfaces to manage metal machines.
package metal

import "github.com/talos-systems/sidero/app/sidero-controller-manager/pkg/types"

// ManagementClient control power and boot order of metal machine.
type ManagementClient interface {
PowerOn() error
PowerOff() error
PowerCycle() error
IsPoweredOn() (bool, error)
SetPXE(mode PXEMode) error
SetPXE(mode types.PXEMode) error
IsFake() bool
Close() error
}

// PXEMode specifies PXE boot mode.
type PXEMode string

const (
PXEModeBIOS = "bios"
PXEModeUEFI = "uefi"
)

func (mode PXEMode) IsValid() bool {
switch mode {
case PXEModeBIOS:
return true
case PXEModeUEFI:
return true
default:
return false
}
}
7 changes: 3 additions & 4 deletions app/sidero-controller-manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import (
"github.com/talos-systems/sidero/app/sidero-controller-manager/internal/ipxe"
"github.com/talos-systems/sidero/app/sidero-controller-manager/internal/metadata"
"github.com/talos-systems/sidero/app/sidero-controller-manager/internal/power/api"
"github.com/talos-systems/sidero/app/sidero-controller-manager/internal/power/metal"
"github.com/talos-systems/sidero/app/sidero-controller-manager/internal/server"
"github.com/talos-systems/sidero/app/sidero-controller-manager/internal/siderolink"
"github.com/talos-systems/sidero/app/sidero-controller-manager/internal/tftp"
Expand Down Expand Up @@ -103,7 +102,7 @@ func main() {
flag.BoolVar(&insecureWipe, "insecure-wipe", true, "Wipe head of the disk only (if false, wipe whole disk).")
flag.BoolVar(&autoBMCSetup, "auto-bmc-setup", true, "Attempt to setup BMC info automatically when agent boots.")
flag.DurationVar(&serverRebootTimeout, "server-reboot-timeout", constants.DefaultServerRebootTimeout, "Timeout to wait for the server to restart and start wipe.")
flag.StringVar(&ipmiPXEMethod, "ipmi-pxe-method", string(metal.PXEModeUEFI), fmt.Sprintf("Default method to use to set server to boot from PXE via IPMI: %s.", []string{metal.PXEModeUEFI, metal.PXEModeBIOS}))
flag.StringVar(&ipmiPXEMethod, "ipmi-pxe-method", string(siderotypes.PXEModeUEFI), fmt.Sprintf("Default method to use to set server to boot from PXE via IPMI: %s.", []string{siderotypes.PXEModeUEFI, siderotypes.PXEModeBIOS}))
flag.Float64Var(&testPowerSimulatedExplicitFailureProb, "test-power-simulated-explicit-failure-prob", 0, "Test failure simulation setting.")
flag.Float64Var(&testPowerSimulatedSilentFailureProb, "test-power-simulated-silent-failure-prob", 0, "Test failure simulation setting.")

Expand Down Expand Up @@ -132,7 +131,7 @@ func main() {
}
}

if !metal.PXEMode(ipmiPXEMethod).IsValid() {
if !siderotypes.PXEMode(ipmiPXEMethod).IsValid() {
setupLog.Error(fmt.Errorf("ipmi-pxe-method is invalid"), "")
os.Exit(1)
}
Expand Down Expand Up @@ -204,7 +203,7 @@ func main() {
APIReader: mgr.GetAPIReader(),
Recorder: recorder,
RebootTimeout: serverRebootTimeout,
PXEMode: metal.PXEMode(ipmiPXEMethod),
PXEMode: siderotypes.PXEMode(ipmiPXEMethod),
}).SetupWithManager(ctx, mgr, controller.Options{MaxConcurrentReconciles: defaultMaxConcurrentReconciles}); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Server")
os.Exit(1)
Expand Down
Loading

0 comments on commit d8ef68b

Please sign in to comment.