Skip to content

Commit 3050d71

Browse files
authored
Merge pull request #634 from eshulman2/bootfrom
Add boot-from-volume support to server controller
2 parents 815934d + 432e7e7 commit 3050d71

20 files changed

Lines changed: 527 additions & 29 deletions

File tree

api/v1alpha1/server_types.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,20 @@ type ServerPortSpec struct {
6060
PortRef *KubernetesNameRef `json:"portRef,omitempty"`
6161
}
6262

63+
// ServerBootVolumeSpec defines the boot volume for boot-from-volume server creation.
64+
// When specified, the server boots from this volume instead of an image.
65+
type ServerBootVolumeSpec struct {
66+
// volumeRef is a reference to a Volume object. The volume must be
67+
// bootable (created from an image) and available before server creation.
68+
// +required
69+
VolumeRef KubernetesNameRef `json:"volumeRef,omitempty"`
70+
71+
// tag is the device tag applied to the volume.
72+
// +kubebuilder:validation:MaxLength:=255
73+
// +optional
74+
Tag *string `json:"tag,omitempty"`
75+
}
76+
6377
// +kubebuilder:validation:MinProperties:=1
6478
type ServerVolumeSpec struct {
6579
// volumeRef is a reference to a Volume object. Server creation will wait for
@@ -122,23 +136,32 @@ type ServerInterfaceStatus struct {
122136
}
123137

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

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

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

158+
// bootVolume specifies a volume to boot from instead of an image.
159+
// When specified, imageRef must be omitted. The volume must be
160+
// bootable (created from an image using imageRef in the Volume spec).
161+
// +optional
162+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="bootVolume is immutable"
163+
BootVolume *ServerBootVolumeSpec `json:"bootVolume,omitempty"`
164+
142165
// userData specifies data which will be made available to the server at
143166
// boot time, either via the metadata service or a config drive. It is
144167
// typically read by a configuration service such as cloud-init or ignition.

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 30 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/models-schema/zz_generated.openapi.go

Lines changed: 38 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/openstack.k-orc.cloud_servers.yaml

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,29 @@ spec:
203203
x-kubernetes-validations:
204204
- message: availabilityZone is immutable
205205
rule: self == oldSelf
206+
bootVolume:
207+
description: |-
208+
bootVolume specifies a volume to boot from instead of an image.
209+
When specified, imageRef must be omitted. The volume must be
210+
bootable (created from an image using imageRef in the Volume spec).
211+
properties:
212+
tag:
213+
description: tag is the device tag applied to the volume.
214+
maxLength: 255
215+
type: string
216+
volumeRef:
217+
description: |-
218+
volumeRef is a reference to a Volume object. The volume must be
219+
bootable (created from an image) and available before server creation.
220+
maxLength: 253
221+
minLength: 1
222+
type: string
223+
required:
224+
- volumeRef
225+
type: object
226+
x-kubernetes-validations:
227+
- message: bootVolume is immutable
228+
rule: self == oldSelf
206229
configDrive:
207230
description: |-
208231
configDrive specifies whether to attach a config drive to the server.
@@ -224,7 +247,7 @@ spec:
224247
imageRef:
225248
description: |-
226249
imageRef references the image to use for the server instance.
227-
NOTE: This is not required in case of boot from volume.
250+
This field is required unless bootVolume is specified for boot-from-volume.
228251
maxLength: 253
229252
minLength: 1
230253
type: string
@@ -355,9 +378,13 @@ spec:
355378
x-kubernetes-list-type: atomic
356379
required:
357380
- flavorRef
358-
- imageRef
359381
- ports
360382
type: object
383+
x-kubernetes-validations:
384+
- message: either imageRef or bootVolume must be specified
385+
rule: has(self.imageRef) || has(self.bootVolume)
386+
- message: imageRef and bootVolume are mutually exclusive
387+
rule: '!(has(self.imageRef) && has(self.bootVolume))'
361388
required:
362389
- cloudCredentialsRef
363390
type: object
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Example of creating a server that boots from a Cinder volume instead of an image.
2+
# This is the boot-from-volume (BFV) pattern.
3+
#
4+
# Prerequisites:
5+
# - A bootable volume created from an image (see openstack_v1alpha1_volume_bootable.yaml)
6+
# - Network, subnet, and port resources
7+
# - A flavor
8+
---
9+
apiVersion: openstack.k-orc.cloud/v1alpha1
10+
kind: Server
11+
metadata:
12+
name: server-boot-from-volume-sample
13+
spec:
14+
cloudCredentialsRef:
15+
cloudName: openstack
16+
secretName: openstack-clouds
17+
managementPolicy: managed
18+
resource:
19+
# Note: No imageRef - booting from volume instead
20+
bootVolume:
21+
volumeRef: bootable-volume-sample
22+
flavorRef: server-sample
23+
ports:
24+
- portRef: server-sample
25+
availabilityZone: nova
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
apiVersion: kustomize.config.k8s.io/v1beta1
3+
kind: Kustomization
4+
resources:
5+
- volume.yaml
6+
- server.yaml
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
# Server that boots from a volume instead of an image
3+
apiVersion: openstack.k-orc.cloud/v1alpha1
4+
kind: Server
5+
metadata:
6+
name: server
7+
spec:
8+
cloudCredentialsRef:
9+
cloudName: openstack
10+
secretName: cloud-config
11+
managementPolicy: managed
12+
resource:
13+
# No imageRef - booting from volume
14+
bootVolume:
15+
volumeRef: boot-volume
16+
flavorRef: flavor
17+
ports:
18+
- portRef: port
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
# Bootable volume created from the cirros image
3+
apiVersion: openstack.k-orc.cloud/v1alpha1
4+
kind: Volume
5+
metadata:
6+
name: boot-volume
7+
spec:
8+
cloudCredentialsRef:
9+
cloudName: openstack
10+
secretName: cloud-config
11+
managementPolicy: managed
12+
resource:
13+
size: 1
14+
imageRef: cirros

internal/controllers/server/actuator.go

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,13 +160,43 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp
160160

161161
reconcileStatus := progress.NewReconcileStatus()
162162

163-
var image *orcv1alpha1.Image
164-
{
163+
// Determine if we're booting from volume or image
164+
bootFromVolume := resource.BootVolume != nil
165+
166+
var imageID string
167+
if !bootFromVolume {
168+
// Traditional boot from image
165169
dep, imageReconcileStatus := imageDependency.GetDependency(
166170
ctx, actuator.k8sClient, obj, orcv1alpha1.IsAvailable,
167171
)
168172
reconcileStatus = reconcileStatus.WithReconcileStatus(imageReconcileStatus)
169-
image = dep
173+
if dep != nil && dep.Status.ID != nil {
174+
imageID = *dep.Status.ID
175+
}
176+
}
177+
178+
// Resolve boot volume for boot-from-volume
179+
var blockDevices []servers.BlockDevice
180+
if bootFromVolume {
181+
bootVolume, bvReconcileStatus := bootVolumeDependency.GetDependency(
182+
ctx, actuator.k8sClient, obj, func(volume *orcv1alpha1.Volume) bool {
183+
return orcv1alpha1.IsAvailable(volume) && volume.Status.ID != nil
184+
},
185+
)
186+
reconcileStatus = reconcileStatus.WithReconcileStatus(bvReconcileStatus)
187+
188+
if bootVolume != nil && bootVolume.Status.ID != nil {
189+
bd := servers.BlockDevice{
190+
SourceType: servers.SourceVolume,
191+
DestinationType: servers.DestinationVolume,
192+
UUID: *bootVolume.Status.ID,
193+
BootIndex: 0, // Always 0 for boot volume
194+
}
195+
if resource.BootVolume.Tag != nil {
196+
bd.Tag = *resource.BootVolume.Tag
197+
}
198+
blockDevices = append(blockDevices, bd)
199+
}
170200
}
171201

172202
flavor, flavorReconcileStatus := dependency.FetchDependency[*orcv1alpha1.Flavor](
@@ -252,14 +282,15 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp
252282

253283
serverCreateOpts := servers.CreateOpts{
254284
Name: getResourceName(obj),
255-
ImageRef: *image.Status.ID,
285+
ImageRef: imageID, // Empty string if boot-from-volume
256286
FlavorRef: *flavor.Status.ID,
257287
Networks: portList,
258288
UserData: userData,
259289
Tags: tags,
260290
Metadata: metadata,
261291
AvailabilityZone: resource.AvailabilityZone,
262292
ConfigDrive: resource.ConfigDrive,
293+
BlockDevice: blockDevices, // Boot volume for BFV
263294
}
264295

265296
/* keypairs.CreateOptsExt was merged into servers.CreateOpts in gopher cloud V3

0 commit comments

Comments
 (0)