Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions api/v1alpha1/server_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,20 @@ type ServerPortSpec struct {
PortRef *KubernetesNameRef `json:"portRef,omitempty"`
}

// ServerBootVolumeSpec defines the boot volume for boot-from-volume server creation.
// When specified, the server boots from this volume instead of an image.
type ServerBootVolumeSpec struct {
// volumeRef is a reference to a Volume object. The volume must be
// bootable (created from an image) and available before server creation.
// +required
VolumeRef KubernetesNameRef `json:"volumeRef,omitempty"`

// tag is the device tag applied to the volume.
// +kubebuilder:validation:MaxLength:=255
// +optional
Tag *string `json:"tag,omitempty"`
}

// +kubebuilder:validation:MinProperties:=1
type ServerVolumeSpec struct {
// volumeRef is a reference to a Volume object. Server creation will wait for
Expand Down Expand Up @@ -122,23 +136,32 @@ type ServerInterfaceStatus struct {
}

// ServerResourceSpec contains the desired state of a server
// +kubebuilder:validation:XValidation:rule="has(self.imageRef) || has(self.bootVolume)",message="either imageRef or bootVolume must be specified"
// +kubebuilder:validation:XValidation:rule="!(has(self.imageRef) && has(self.bootVolume))",message="imageRef and bootVolume are mutually exclusive"
type ServerResourceSpec struct {
// name will be the name of the created resource. If not specified, the
// name of the ORC object will be used.
// +optional
Name *OpenStackName `json:"name,omitempty"`

// imageRef references the image to use for the server instance.
// NOTE: This is not required in case of boot from volume.
// +required
// This field is required unless bootVolume is specified for boot-from-volume.
// +optional
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="imageRef is immutable"
ImageRef KubernetesNameRef `json:"imageRef,omitempty"`
ImageRef *KubernetesNameRef `json:"imageRef,omitempty"`

// flavorRef references the flavor to use for the server instance.
// +required
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="flavorRef is immutable"
FlavorRef KubernetesNameRef `json:"flavorRef,omitempty"`

// bootVolume specifies a volume to boot from instead of an image.
// When specified, imageRef must be omitted. The volume must be
// bootable (created from an image using imageRef in the Volume spec).
// +optional
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="bootVolume is immutable"
BootVolume *ServerBootVolumeSpec `json:"bootVolume,omitempty"`

// userData specifies data which will be made available to the server at
// boot time, either via the metadata service or a config drive. It is
// typically read by a configuration service such as cloud-init or ignition.
Expand Down
30 changes: 30 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

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

41 changes: 38 additions & 3 deletions cmd/models-schema/zz_generated.openapi.go

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

31 changes: 29 additions & 2 deletions config/crd/bases/openstack.k-orc.cloud_servers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,29 @@ spec:
x-kubernetes-validations:
- message: availabilityZone is immutable
rule: self == oldSelf
bootVolume:
description: |-
bootVolume specifies a volume to boot from instead of an image.
When specified, imageRef must be omitted. The volume must be
bootable (created from an image using imageRef in the Volume spec).
properties:
tag:
description: tag is the device tag applied to the volume.
maxLength: 255
type: string
volumeRef:
description: |-
volumeRef is a reference to a Volume object. The volume must be
bootable (created from an image) and available before server creation.
maxLength: 253
minLength: 1
type: string
required:
- volumeRef
type: object
x-kubernetes-validations:
- message: bootVolume is immutable
rule: self == oldSelf
configDrive:
description: |-
configDrive specifies whether to attach a config drive to the server.
Expand All @@ -223,7 +246,7 @@ spec:
imageRef:
description: |-
imageRef references the image to use for the server instance.
NOTE: This is not required in case of boot from volume.
This field is required unless bootVolume is specified for boot-from-volume.
maxLength: 253
minLength: 1
type: string
Expand Down Expand Up @@ -354,9 +377,13 @@ spec:
x-kubernetes-list-type: atomic
required:
- flavorRef
- imageRef
- ports
type: object
x-kubernetes-validations:
- message: either imageRef or bootVolume must be specified
rule: has(self.imageRef) || has(self.bootVolume)
- message: imageRef and bootVolume are mutually exclusive
rule: '!(has(self.imageRef) && has(self.bootVolume))'
required:
- cloudCredentialsRef
type: object
Expand Down
25 changes: 25 additions & 0 deletions config/samples/openstack_v1alpha1_server_boot_from_volume.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Example of creating a server that boots from a Cinder volume instead of an image.
# This is the boot-from-volume (BFV) pattern.
#
# Prerequisites:
# - A bootable volume created from an image (see openstack_v1alpha1_volume_bootable.yaml)
# - Network, subnet, and port resources
# - A flavor
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Server
metadata:
name: server-boot-from-volume-sample
spec:
cloudCredentialsRef:
cloudName: openstack
secretName: openstack-clouds
managementPolicy: managed
resource:
# Note: No imageRef - booting from volume instead
bootVolume:
volumeRef: bootable-volume-sample
flavorRef: server-sample
ports:
- portRef: server-sample
availabilityZone: nova
6 changes: 6 additions & 0 deletions examples/bases/boot-from-volume/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- volume.yaml
- server.yaml
18 changes: 18 additions & 0 deletions examples/bases/boot-from-volume/server.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
# Server that boots from a volume instead of an image
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Server
metadata:
name: server
spec:
cloudCredentialsRef:
cloudName: openstack
secretName: cloud-config
managementPolicy: managed
resource:
# No imageRef - booting from volume
bootVolume:
volumeRef: boot-volume
flavorRef: flavor
ports:
- portRef: port
14 changes: 14 additions & 0 deletions examples/bases/boot-from-volume/volume.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
# Bootable volume created from the cirros image
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Volume
metadata:
name: boot-volume
spec:
cloudCredentialsRef:
cloudName: openstack
secretName: cloud-config
managementPolicy: managed
resource:
size: 1
imageRef: cirros
39 changes: 35 additions & 4 deletions internal/controllers/server/actuator.go
Original file line number Diff line number Diff line change
Expand Up @@ -160,15 +160,45 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp

reconcileStatus := progress.NewReconcileStatus()

var image *orcv1alpha1.Image
{
// Determine if we're booting from volume or image
bootFromVolume := resource.BootVolume != nil

var imageID string
if !bootFromVolume {
// Traditional boot from image
dep, imageReconcileStatus := imageDependency.GetDependency(
ctx, actuator.k8sClient, obj, func(image *orcv1alpha1.Image) bool {
return orcv1alpha1.IsAvailable(image) && image.Status.ID != nil
},
)
reconcileStatus = reconcileStatus.WithReconcileStatus(imageReconcileStatus)
image = dep
if dep != nil && dep.Status.ID != nil {
imageID = *dep.Status.ID
}
}

// Resolve boot volume for boot-from-volume
var blockDevices []servers.BlockDevice
if bootFromVolume {
bootVolume, bvReconcileStatus := bootVolumeDependency.GetDependency(
ctx, actuator.k8sClient, obj, func(volume *orcv1alpha1.Volume) bool {
return orcv1alpha1.IsAvailable(volume) && volume.Status.ID != nil
},
)
reconcileStatus = reconcileStatus.WithReconcileStatus(bvReconcileStatus)

if bootVolume != nil && bootVolume.Status.ID != nil {
bd := servers.BlockDevice{
SourceType: servers.SourceVolume,
DestinationType: servers.DestinationVolume,
UUID: *bootVolume.Status.ID,
BootIndex: 0, // Always 0 for boot volume
}
if resource.BootVolume.Tag != nil {
bd.Tag = *resource.BootVolume.Tag
}
blockDevices = append(blockDevices, bd)
}
}

flavor, flavorReconcileStatus := dependency.FetchDependency(
Expand Down Expand Up @@ -256,14 +286,15 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp

serverCreateOpts := servers.CreateOpts{
Name: getResourceName(obj),
ImageRef: *image.Status.ID,
ImageRef: imageID, // Empty string if boot-from-volume
FlavorRef: *flavor.Status.ID,
Networks: portList,
UserData: userData,
Tags: tags,
Metadata: metadata,
AvailabilityZone: resource.AvailabilityZone,
ConfigDrive: resource.ConfigDrive,
BlockDevice: blockDevices, // Boot volume for BFV
}

/* keypairs.CreateOptsExt was merged into servers.CreateOpts in gopher cloud V3
Expand Down
Loading
Loading