Skip to content

Commit

Permalink
Enable hardware Provisioning through ISO booting for baremetal Provid…
Browse files Browse the repository at this point in the history
…er (aws#9213)

Upstream CAPT/Tink recently added support for booting the hardware
through ISO. This change adds the necessary changes to our cluster spec
notably TinkerbellDataCenterConfig to be able to provision the hardware
using an ISO. ISO booting removes the dependency of the admin machine to
provision the cluster to be in the same L2 as the hardware. Additionally
offers static IPAM. The PR adds necessary changes required in our
TinkerbellmachineTemplate to be able to ISO boot. The default behavior
would still be to netboot. PR also adds required changes at the time of
Tinkerbell stack installation so that Smee has all the required flags
set to be able to serve ISO and handles toggling between the bootstrap
IP and the actual TinkerbelIP.

Signed-off-by: Rahul Ganesh <[email protected]>
Co-authored-by: Rahul Ganesh <[email protected]>
  • Loading branch information
rahulbabu95 and Rahul Ganesh authored Feb 5, 2025
1 parent 647fa4a commit 21b4f95
Show file tree
Hide file tree
Showing 47 changed files with 1,812 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,15 @@ spec:
description: HookImagesURLPath can be used to override the default
Hook images path to pull from a local server.
type: string
isoBoot:
description: IsoBoot can be used to indicate that the hardware must
boot using an ISO.
type: boolean
hookIsoURL:
description: HookIsoURL is the URL of ISO image that will one time boot.
It can be used to override the default Hook OS ISO image to pull
from a local server.
type: string
loadBalancerInterface:
description: LoadBalancerInterface can be used to configure a load
balancer interface for the Tinkerbell stack.
Expand Down Expand Up @@ -83,4 +92,4 @@ status:
kind: ""
plural: ""
conditions: []
storedVersions: []
storedVersions: []
9 changes: 9 additions & 0 deletions config/manifest/eksa-components.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6910,6 +6910,15 @@ spec:
description: HookImagesURLPath can be used to override the default
Hook images path to pull from a local server.
type: string
isoBoot:
description: IsoBoot can be used to indicate that the hardware must
boot using an ISO.
type: boolean
hookIsoURL:
description: HookIsoURL is the URL of ISO image that will one time boot.
It can be used to override the default Hook OS ISO image to pull
from a local server.
type: string
loadBalancerInterface:
description: LoadBalancerInterface can be used to configure a load
balancer interface for the Tinkerbell stack.
Expand Down
31 changes: 21 additions & 10 deletions pkg/api/v1alpha1/thirdparty/tinkerbell/rufio/machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

// These types are the Rufio v1alpha1 APIs/types copied from https://github.com/tinkerbell/rufio/tree/main/api/v1alpha1

import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -47,8 +45,6 @@ const (
Off PowerState = "off"
// Unknown represents that a Machine's power state is unknown.
Unknown PowerState = "unknown"
// PXE is the boot device name for PXE booting a machine.
PXE string = "pxe"
)

// MachineConditionType represents the condition of the Machine.
Expand All @@ -75,23 +71,37 @@ type MachineSpec struct {
Connection Connection `json:"connection"`
}

// ProviderOptions contains all the provider specific options.
// ProviderName is the bmclib specific provider name. Names are case insensitive.
// +kubebuilder:validation:Pattern=(?i)^(ipmitool|asrockrack|gofish|IntelAMT|dell|supermicro|openbmc)$
type ProviderName string

func (p ProviderName) String() string {
return string(p)
}

// ProviderOptions hold provider specific configurable options.
type ProviderOptions struct {
// PreferredOrder allows customizing the order that BMC providers are called.
// Providers added to this list will be moved to the front of the default order.
// Provider names are case insensitive.
// The default order is: ipmitool, asrockrack, gofish, intelamt, dell, supermicro, openbmc.
// +optional
PreferredOrder []ProviderName `json:"preferredOrder,omitempty"`
// IntelAMT contains the options to customize the IntelAMT provider.
// +optional
IntelAMT *IntelAMTOptions `json:"intelAMT"`
IntelAMT *IntelAMTOptions `json:"intelAMT,omitempty"`

// IPMITOOL contains the options to customize the Ipmitool provider.
// +optional
IPMITOOL *IPMITOOLOptions `json:"ipmitool"`
IPMITOOL *IPMITOOLOptions `json:"ipmitool,omitempty"`

// Redfish contains the options to customize the Redfish provider.
// +optional
Redfish *RedfishOptions `json:"redfish"`
Redfish *RedfishOptions `json:"redfish,omitempty"`

// RPC contains the options to customize the RPC provider.
// +optional
RPC *RPCOptions `json:"rpc"`
RPC *RPCOptions `json:"rpc,omitempty"`
}

// Connection contains connection data for a Baseboard Management Controller.
Expand Down Expand Up @@ -147,7 +157,6 @@ type MachineCondition struct {
Message string `json:"message,omitempty"`
}

// MachineSetConditionOption is a function that manipulates a MachineCondition.
// +kubebuilder:object:generate=false
type MachineSetConditionOption func(*MachineCondition)

Expand Down Expand Up @@ -192,6 +201,8 @@ func WithMachineConditionMessage(m string) MachineSetConditionOption {
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
//+kubebuilder:resource:path=machines,scope=Namespaced,categories=tinkerbell,singular=machine
// +kubebuilder:metadata:labels=clusterctl.cluster.x-k8s.io=
// +kubebuilder:metadata:labels=clusterctl.cluster.x-k8s.io/move=

// Machine is the Schema for the machines API.
type Machine struct {
Expand Down
57 changes: 36 additions & 21 deletions pkg/api/v1alpha1/thirdparty/tinkerbell/rufio/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,38 @@ import (
// RedfishOptions contains the redfish provider specific options.
type RedfishOptions struct {
// Port that redfish will use for calls.
Port int `json:"port"`
// +optional
Port int `json:"port,omitempty"`
// UseBasicAuth for redfish calls. The default is false which means token based auth is used.
// +optional
UseBasicAuth bool `json:"useBasicAuth,omitempty"`
// SystemName is the name of the system to use for redfish calls.
// With redfish implementations that manage multiple systems via a single endpoint, this allows for specifying the system to manage.
// +optional
SystemName string `json:"systemName,omitempty"`
}

// IPMITOOLOptions contains the ipmitool provider specific options.
type IPMITOOLOptions struct {
// Port that ipmitool will use for calls.
// +optional
Port int `json:"port"`
Port int `json:"port,omitempty"`
// CipherSuite that ipmitool will use for calls.
// +optional
CipherSuite string `json:"cipherSuite"`
CipherSuite string `json:"cipherSuite,omitempty"`
}

// IntelAMTOptions contains the intelAMT provider specific options.
type IntelAMTOptions struct {
// Port that intelAMT will use for calls.
Port int `json:"port"`
// +optional
Port int `json:"port,omitempty"`

// HostScheme determines whether to use http or https for intelAMT calls.
// +optional
// +kubebuilder:validation:Enum=http;https
// +kubebuilder:default:=http
HostScheme string `json:"hostScheme,omitempty"`
}

// HMACAlgorithm is a type for HMAC algorithms.
Expand All @@ -42,72 +57,72 @@ type RPCOptions struct {
ConsumerURL string `json:"consumerURL"`
// LogNotificationsDisabled determines whether responses from rpc consumer/listeners will be logged or not.
// +optional
LogNotificationsDisabled bool `json:"logNotificationsDisabled"`
LogNotificationsDisabled bool `json:"logNotificationsDisabled,omitempty"`
// Request is the options used to create the rpc HTTP request.
// +optional
Request *RequestOpts `json:"request"`
Request *RequestOpts `json:"request,omitempty"`
// Signature is the options used for adding an HMAC signature to an HTTP request.
// +optional
Signature *SignatureOpts `json:"signature"`
Signature *SignatureOpts `json:"signature,omitempty"`
// HMAC is the options used to create a HMAC signature.
// +optional
HMAC *HMACOpts `json:"hmac"`
HMAC *HMACOpts `json:"hmac,omitempty"`
// Experimental options.
// +optional
Experimental *ExperimentalOpts `json:"experimental"`
Experimental *ExperimentalOpts `json:"experimental,omitempty"`
}

// RequestOpts are the options used when creating an HTTP request.
type RequestOpts struct {
// HTTPContentType is the content type to use for the rpc request notification.
// +optional
HTTPContentType string `json:"httpContentType"`
HTTPContentType string `json:"httpContentType,omitempty"`
// HTTPMethod is the HTTP method to use for the rpc request notification.
// +optional
HTTPMethod string `json:"httpMethod"`
HTTPMethod string `json:"httpMethod,omitempty"`
// StaticHeaders are predefined headers that will be added to every request.
// +optional
StaticHeaders http.Header `json:"staticHeaders"`
StaticHeaders http.Header `json:"staticHeaders,omitempty"`
// TimestampFormat is the time format for the timestamp header.
// +optional
TimestampFormat string `json:"timestampFormat"`
TimestampFormat string `json:"timestampFormat,omitempty"`
// TimestampHeader is the header name that should contain the timestamp. Example: X-BMCLIB-Timestamp
// +optional
TimestampHeader string `json:"timestampHeader"`
TimestampHeader string `json:"timestampHeader,omitempty"`
}

// SignatureOpts are the options used for adding an HMAC signature to an HTTP request.
type SignatureOpts struct {
// HeaderName is the header name that should contain the signature(s). Example: X-BMCLIB-Signature
// +optional
HeaderName string `json:"headerName"`
HeaderName string `json:"headerName,omitempty"`
// AppendAlgoToHeaderDisabled decides whether to append the algorithm to the signature header or not.
// Example: X-BMCLIB-Signature becomes X-BMCLIB-Signature-256
// When set to true, a header will be added for each algorithm. Example: X-BMCLIB-Signature-256 and X-BMCLIB-Signature-512
// +optional
AppendAlgoToHeaderDisabled bool `json:"appendAlgoToHeaderDisabled"`
AppendAlgoToHeaderDisabled bool `json:"appendAlgoToHeaderDisabled,omitempty"`
// IncludedPayloadHeaders are headers whose values will be included in the signature payload. Example: X-BMCLIB-My-Custom-Header
// All headers will be deduplicated.
// +optional
IncludedPayloadHeaders []string `json:"includedPayloadHeaders"`
IncludedPayloadHeaders []string `json:"includedPayloadHeaders,omitempty"`
}

// HMACOpts are the options used to create an HMAC signature.
type HMACOpts struct {
// PrefixSigDisabled determines whether the algorithm will be prefixed to the signature. Example: sha256=abc123
// +optional
PrefixSigDisabled bool `json:"prefixSigDisabled"`
PrefixSigDisabled bool `json:"prefixSigDisabled,omitempty"`
// Secrets are a map of algorithms to secrets used for signing.
// +optional
Secrets HMACSecrets `json:"secrets"`
Secrets HMACSecrets `json:"secrets,omitempty"`
}

// ExperimentalOpts are options we're still learning about and should be used carefully.
type ExperimentalOpts struct {
// CustomRequestPayload must be in json.
// +optional
CustomRequestPayload string `json:"customRequestPayload"`
CustomRequestPayload string `json:"customRequestPayload,omitempty"`
// DotPath is the path to the json object where the bmclib RequestPayload{} struct will be embedded. For example: object.data.body
// +optional
DotPath string `json:"dotPath"`
DotPath string `json:"dotPath,omitempty"`
}

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

12 changes: 12 additions & 0 deletions pkg/api/v1alpha1/tinkerbelldatacenterconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ func validateDatacenterConfig(config *TinkerbellDatacenterConfig) error {
return fmt.Errorf("TinkerbellDatacenterConfig: invalid tinkerbell ip: %v", err)
}

if config.Spec.IsoBoot {
if config.Spec.HookIsoURL != "" {
if _, err := url.ParseRequestURI(config.Spec.HookIsoURL); err != nil {
return fmt.Errorf("parsing hookIsoURL: %v, please provide a valid URL", err)
}
}
} else {
if config.Spec.HookIsoURL != "" {
return fmt.Errorf("isoURL can be set, only when isoBoot is set to true")
}
}

return nil
}

Expand Down
8 changes: 8 additions & 0 deletions pkg/api/v1alpha1/tinkerbelldatacenterconfig_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ type TinkerbellDatacenterConfigSpec struct {
SkipLoadBalancerDeployment bool `json:"skipLoadBalancerDeployment,omitempty"`
// LoadBalancerInterface can be used to configure a load balancer interface for the Tinkerbell stack.
LoadBalancerInterface string `json:"loadBalancerInterface,omitempty"`
// IsoBoot can be used to indicate that the hardware should boot using an ISO.
//+optional
IsoBoot bool `json:"isoBoot,omitempty"`
// HookIsoURL is the URL of ISO image that will be used to provision the hardware
// during one time boot process.
// It can be used to override the default Hook OS ISO image to pull from a local server.
//+optional
HookIsoURL string `json:"hookIsoURL,omitempty"`
}

// TinkerbellDatacenterConfigStatus defines the observed state of TinkerbellDatacenterConfig
Expand Down
32 changes: 32 additions & 0 deletions pkg/api/v1alpha1/tinkerbelldatacenterconfig_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,21 @@ func TestTinkerbellDatacenterConfigValidateFail(t *testing.T) {
}),
wantErr: "parsing osImageOverride: parse \"test\": invalid URI for request",
},
{
name: "ISO URL set, isoBoot not enabled",
tinkDC: newTinkerbellDatacenterConfig(func(dc *v1alpha1.TinkerbellDatacenterConfig) {
dc.Spec.HookIsoURL = "127.0.0.1:8080/hook.iso"
}),
wantErr: "isoURL can be set, only when isoBoot is set to true",
},
{
name: "Invalid ISO URL",
tinkDC: newTinkerbellDatacenterConfig(func(dc *v1alpha1.TinkerbellDatacenterConfig) {
dc.Spec.IsoBoot = true
dc.Spec.HookIsoURL = "test"
}),
wantErr: "parsing hookIsoURL: parse \"test\": invalid URI for request",
},
{
name: "Invalid hook Image URL",
tinkDC: newTinkerbellDatacenterConfig(func(dc *v1alpha1.TinkerbellDatacenterConfig) {
Expand Down Expand Up @@ -66,6 +81,23 @@ func TestTinkerbellDatacenterConfigValidateSuccess(t *testing.T) {
g.Expect(tinkDC.Validate()).To(Succeed())
}

func TestTinkerbellDatacenterConfigIsoBootValidateSuccess(t *testing.T) {
tinkDC := createTinkerbellDatacenterConfig()
tinkDC.Spec.IsoBoot = true

g := NewWithT(t)
g.Expect(tinkDC.Validate()).To(Succeed())
}

func TestTinkerbellDatacenterConfigIsoURLValidateSuccess(t *testing.T) {
tinkDC := createTinkerbellDatacenterConfig()
tinkDC.Spec.IsoBoot = true
tinkDC.Spec.HookIsoURL = "http://127.0.0.1:8080/hook.iso"

g := NewWithT(t)
g.Expect(tinkDC.Validate()).To(Succeed())
}

func newTinkerbellDatacenterConfig(opts ...func(*v1alpha1.TinkerbellDatacenterConfig)) *v1alpha1.TinkerbellDatacenterConfig {
c := createTinkerbellDatacenterConfig()
for _, o := range opts {
Expand Down
Loading

0 comments on commit 21b4f95

Please sign in to comment.