Skip to content

Commit 0801402

Browse files
committed
Add imageRef to volume controller
Enables creating bootable volumes from images by adding an imageRef field to the Volume spec. When specified, the volume is created with the image baked in, making it suitable for boot-from-volume scenarios. Changes: - Add imageRef field to VolumeResourceSpec - Add bootable and imageID fields to VolumeResourceStatus - Add image dependency with deletion guard - Add kuttl tests for bootable volume creation assisted-by: claude
1 parent 7601b7c commit 0801402

20 files changed

Lines changed: 189 additions & 2 deletions

api/v1alpha1/volume_types.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,13 @@ type VolumeResourceSpec struct {
5656
// +listType=atomic
5757
// +optional
5858
Metadata []VolumeMetadata `json:"metadata,omitempty"`
59+
60+
// imageRef is a reference to an ORC Image. If specified, creates a
61+
// bootable volume from this image. The volume size must be >= the
62+
// image's min_disk requirement.
63+
// +optional
64+
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="imageRef is immutable"
65+
ImageRef *KubernetesNameRef `json:"imageRef,omitempty"`
5966
}
6067

6168
// VolumeFilter defines an existing resource by its properties
@@ -176,6 +183,11 @@ type VolumeResourceStatus struct {
176183
// +optional
177184
Bootable *bool `json:"bootable,omitempty"`
178185

186+
// imageID is the ID of the image this volume was created from, if any.
187+
// +kubebuilder:validation:MaxLength=1024
188+
// +optional
189+
ImageID string `json:"imageID,omitempty"`
190+
179191
// encrypted denotes if the volume is encrypted.
180192
// +optional
181193
Encrypted *bool `json:"encrypted,omitempty"`

api/v1alpha1/zz_generated.deepcopy.go

Lines changed: 5 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: 14 additions & 0 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_volumes.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,17 @@ spec:
173173
maxLength: 255
174174
minLength: 1
175175
type: string
176+
imageRef:
177+
description: |-
178+
imageRef is a reference to an ORC Image. If specified, creates a
179+
bootable volume from this image. The volume size must be >= the
180+
image's min_disk requirement.
181+
maxLength: 253
182+
minLength: 1
183+
type: string
184+
x-kubernetes-validations:
185+
- message: imageRef is immutable
186+
rule: self == oldSelf
176187
metadata:
177188
description: |-
178189
metadata key and value pairs to be associated with the volume.
@@ -389,6 +400,11 @@ spec:
389400
description: host is the identifier of the host holding the volume.
390401
maxLength: 1024
391402
type: string
403+
imageID:
404+
description: imageID is the ID of the image this volume was created
405+
from, if any.
406+
maxLength: 1024
407+
type: string
392408
metadata:
393409
description: metadata key and value pairs to be associated with
394410
the volume.

config/samples/openstack_v1alpha1_volume.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ spec:
1212
description: Sample Volume
1313
size: 100
1414
volumeTypeRef: my-volume-type
15+
imageRef: ubuntu-2404
1516
metadata:
1617
key1: value1
1718
key2: value2

internal/controllers/volume/actuator.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/k-orc/openstack-resource-controller/v2/internal/controllers/generic/progress"
3333
"github.com/k-orc/openstack-resource-controller/v2/internal/logging"
3434
"github.com/k-orc/openstack-resource-controller/v2/internal/osclients"
35+
"github.com/k-orc/openstack-resource-controller/v2/internal/util/dependency"
3536
orcerrors "github.com/k-orc/openstack-resource-controller/v2/internal/util/errors"
3637
)
3738

@@ -165,6 +166,17 @@ func (actuator volumeActuator) CreateResource(ctx context.Context, obj orcObject
165166
}
166167
}
167168

169+
// Resolve image dependency for bootable volumes
170+
image, imageDepRS := dependency.FetchDependency(
171+
ctx, actuator.k8sClient, obj.Namespace,
172+
resource.ImageRef, "Image",
173+
func(dep *orcv1alpha1.Image) bool {
174+
return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
175+
},
176+
)
177+
reconcileStatus = reconcileStatus.WithReconcileStatus(imageDepRS)
178+
imageID := ptr.Deref(image.Status.ID, "")
179+
168180
if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
169181
return nil, reconcileStatus
170182
}
@@ -181,6 +193,7 @@ func (actuator volumeActuator) CreateResource(ctx context.Context, obj orcObject
181193
Metadata: metadata,
182194
VolumeType: volumetypeID,
183195
AvailabilityZone: resource.AvailabilityZone,
196+
ImageID: imageID,
184197
}
185198

186199
osResource, err := actuator.osClient.CreateVolume(ctx, createOpts)

internal/controllers/volume/controller.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ var volumetypeDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.Vo
7474
finalizer, externalObjectFieldOwner,
7575
)
7676

77+
// No deletion guard for image, because images can be safely deleted while
78+
// referenced by a volume
79+
var imageDependency = dependency.NewDependency[*orcv1alpha1.VolumeList, *orcv1alpha1.Image](
80+
"spec.resource.imageRef",
81+
func(volume *orcv1alpha1.Volume) []string {
82+
resource := volume.Spec.Resource
83+
if resource == nil || resource.ImageRef == nil {
84+
return nil
85+
}
86+
return []string{string(*resource.ImageRef)}
87+
},
88+
)
89+
7790
// serverToVolumeMapFunc creates a mapping function that reconciles volumes when:
7891
// - a volume ID appears in server status but the volume doesn't have attachment info for that server
7992
// - a volume has attachment info for a server, but the server no longer lists that volume
@@ -209,18 +222,27 @@ func (c volumeReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c
209222
return err
210223
}
211224

225+
imageWatchEventHandler, err := imageDependency.WatchEventHandler(log, k8sClient)
226+
if err != nil {
227+
return err
228+
}
229+
212230
builder := ctrl.NewControllerManagedBy(mgr).
213231
WithOptions(options).
214232
Watches(&orcv1alpha1.VolumeType{}, volumetypeWatchEventHandler,
215233
builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.VolumeType{})),
216234
).
235+
Watches(&orcv1alpha1.Image{}, imageWatchEventHandler,
236+
builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Image{})),
237+
).
217238
Watches(&orcv1alpha1.Server{}, handler.EnqueueRequestsFromMapFunc(serverToVolumeMapFunc(ctx, k8sClient)),
218239
builder.WithPredicates(predicates.NewServerVolumesChanged(log)),
219240
).
220241
For(&orcv1alpha1.Volume{})
221242

222243
if err := errors.Join(
223244
volumetypeDependency.AddToManager(ctx, mgr),
245+
imageDependency.AddToManager(ctx, mgr),
224246
credentialsDependency.AddToManager(ctx, mgr),
225247
credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency),
226248
); err != nil {

internal/controllers/volume/status.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ func (volumeStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osRes
9292
}
9393
}
9494

95+
// Extract image ID from volume_image_metadata if present.
96+
// When a volume is created from an image, OpenStack stores the source
97+
// image ID in the volume's metadata under "image_id".
98+
if imageID, ok := osResource.VolumeImageMetadata["image_id"]; ok {
99+
resourceStatus.WithImageID(imageID)
100+
}
101+
95102
for k, v := range osResource.Metadata {
96103
resourceStatus.WithMetadata(orcapplyconfigv1alpha1.VolumeMetadataStatus().
97104
WithName(k).

internal/controllers/volume/tests/volume-dependency/00-assert.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,18 @@ status:
2828
message: Waiting for VolumeType/volume-dependency to be created
2929
status: "True"
3030
reason: Progressing
31+
---
32+
apiVersion: openstack.k-orc.cloud/v1alpha1
33+
kind: Volume
34+
metadata:
35+
name: volume-dependency-no-image
36+
status:
37+
conditions:
38+
- type: Available
39+
message: Waiting for Image/volume-dependency-image to be created
40+
status: "False"
41+
reason: Progressing
42+
- type: Progressing
43+
message: Waiting for Image/volume-dependency-image to be created
44+
status: "True"
45+
reason: Progressing

internal/controllers/volume/tests/volume-dependency/00-create-resources-missing-deps.yaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,16 @@ spec:
2323
managementPolicy: managed
2424
resource:
2525
size: 1
26+
---
27+
apiVersion: openstack.k-orc.cloud/v1alpha1
28+
kind: Volume
29+
metadata:
30+
name: volume-dependency-no-image
31+
spec:
32+
cloudCredentialsRef:
33+
cloudName: openstack
34+
secretName: openstack-clouds
35+
managementPolicy: managed
36+
resource:
37+
size: 1
38+
imageRef: volume-dependency-image

0 commit comments

Comments
 (0)