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
12 changes: 12 additions & 0 deletions api/v1alpha1/volume_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ type VolumeResourceSpec struct {
// +listType=atomic
// +optional
Metadata []VolumeMetadata `json:"metadata,omitempty"`

// imageRef is a reference to an ORC Image. If specified, creates a
// bootable volume from this image. The volume size must be >= the
// image's min_disk requirement.
// +optional
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="imageRef is immutable"
ImageRef *KubernetesNameRef `json:"imageRef,omitempty"`
}

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

// imageID is the ID of the image this volume was created from, if any.
// +kubebuilder:validation:MaxLength=1024
// +optional
ImageID string `json:"imageID,omitempty"`

// encrypted denotes if the volume is encrypted.
// +optional
Encrypted *bool `json:"encrypted,omitempty"`
Expand Down
5 changes: 5 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.

14 changes: 14 additions & 0 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.

16 changes: 16 additions & 0 deletions config/crd/bases/openstack.k-orc.cloud_volumes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,17 @@ spec:
maxLength: 255
minLength: 1
type: string
imageRef:
description: |-
imageRef is a reference to an ORC Image. If specified, creates a
bootable volume from this image. The volume size must be >= the
image's min_disk requirement.
maxLength: 253
minLength: 1
type: string
x-kubernetes-validations:
- message: imageRef is immutable
rule: self == oldSelf
metadata:
description: |-
metadata key and value pairs to be associated with the volume.
Expand Down Expand Up @@ -389,6 +400,11 @@ spec:
description: host is the identifier of the host holding the volume.
maxLength: 1024
type: string
imageID:
description: imageID is the ID of the image this volume was created
from, if any.
maxLength: 1024
type: string
metadata:
description: metadata key and value pairs to be associated with
the volume.
Expand Down
29 changes: 29 additions & 0 deletions config/samples/openstack_v1alpha1_volume_bootable.yaml
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file will not be in the release artifacts because it's not referenced from the kustomization.yaml. If you want to document a bootable volume, you should add this example to openstack_v1alpha1_volume.yaml.

Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
# Example of creating a bootable volume from an image.
# The volume can then be used as a boot device for a server.
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Image
metadata:
name: ubuntu-2404
spec:
cloudCredentialsRef:
cloudName: openstack
secretName: openstack-clouds
import:
filter:
name: ubuntu-24.04-server
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Volume
metadata:
name: bootable-volume-sample
spec:
cloudCredentialsRef:
cloudName: openstack
secretName: openstack-clouds
managementPolicy: managed
resource:
description: Bootable volume created from Ubuntu image
size: 50
imageRef: ubuntu-2404
volumeTypeRef: fast-ssd
15 changes: 15 additions & 0 deletions internal/controllers/volume/actuator.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,20 @@ func (actuator volumeActuator) CreateResource(ctx context.Context, obj orcObject
}
}

// Resolve image dependency for bootable volumes
var imageID string
if resource.ImageRef != nil {
image, imageDepRS := imageDependency.GetDependency(
ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Image) bool {
return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
},
)
reconcileStatus = reconcileStatus.WithReconcileStatus(imageDepRS)
if image != nil {
imageID = ptr.Deref(image.Status.ID, "")
}
}

if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
return nil, reconcileStatus
}
Expand All @@ -181,6 +195,7 @@ func (actuator volumeActuator) CreateResource(ctx context.Context, obj orcObject
Metadata: metadata,
VolumeType: volumetypeID,
AvailabilityZone: resource.AvailabilityZone,
ImageID: imageID,
}

osResource, err := actuator.osClient.CreateVolume(ctx, createOpts)
Expand Down
21 changes: 21 additions & 0 deletions internal/controllers/volume/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ var volumetypeDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.Vo
finalizer, externalObjectFieldOwner,
)

var imageDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.VolumeList, *orcv1alpha1.Image](
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most likely we don't want it to be a deletion guard because I expect that OpenStack allows us to delete an image after a volume was created from it (to be verified). See how we did for the server's dependency on flavor.

"spec.resource.imageRef",
func(volume *orcv1alpha1.Volume) []string {
resource := volume.Spec.Resource
if resource == nil || resource.ImageRef == nil {
return nil
}
return []string{string(*resource.ImageRef)}
},
finalizer, externalObjectFieldOwner,
)

// serverToVolumeMapFunc creates a mapping function that reconciles volumes when:
// - a volume ID appears in server status but the volume doesn't have attachment info for that server
// - a volume has attachment info for a server, but the server no longer lists that volume
Expand Down Expand Up @@ -209,18 +221,27 @@ func (c volumeReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c
return err
}

imageWatchEventHandler, err := imageDependency.WatchEventHandler(log, k8sClient)
if err != nil {
return err
}

builder := ctrl.NewControllerManagedBy(mgr).
WithOptions(options).
Watches(&orcv1alpha1.VolumeType{}, volumetypeWatchEventHandler,
builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.VolumeType{})),
).
Watches(&orcv1alpha1.Image{}, imageWatchEventHandler,
builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Image{})),
).
Watches(&orcv1alpha1.Server{}, handler.EnqueueRequestsFromMapFunc(serverToVolumeMapFunc(ctx, k8sClient)),
builder.WithPredicates(predicates.NewServerVolumesChanged(log)),
).
For(&orcv1alpha1.Volume{})

if err := errors.Join(
volumetypeDependency.AddToManager(ctx, mgr),
imageDependency.AddToManager(ctx, mgr),
credentialsDependency.AddToManager(ctx, mgr),
credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency),
); err != nil {
Expand Down
7 changes: 7 additions & 0 deletions internal/controllers/volume/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ func (volumeStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osRes
}
}

// Extract image ID from volume_image_metadata if present.
// When a volume is created from an image, OpenStack stores the source
// image ID in the volume's metadata under "image_id".
if imageID, ok := osResource.VolumeImageMetadata["image_id"]; ok {
resourceStatus.WithImageID(imageID)
}

for k, v := range osResource.Metadata {
resourceStatus.WithMetadata(orcapplyconfigv1alpha1.VolumeMetadataStatus().
WithName(k).
Expand Down
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this test is not necessary because we're already checking the creation of a bootable volume in the volume-dependency test. We will also use bootable volumes when testing for servers booted from volumes soon. What do you say we drop the volume-create-bootable test?

Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Image
metadata:
name: volume-create-bootable-image
status:
resource:
status: active
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Volume
metadata:
name: volume-create-bootable
status:
resource:
name: volume-create-bootable
size: 1
status: available
bootable: true
encrypted: false
multiattach: false
conditions:
- type: Available
status: "True"
reason: Success
- type: Progressing
status: "False"
reason: Success
---
apiVersion: kuttl.dev/v1beta1
kind: TestAssert
resourceRefs:
- apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Volume
name: volume-create-bootable
ref: volume
- apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Image
name: volume-create-bootable-image
ref: image
assertAll:
- celExpr: "volume.status.id != ''"
- celExpr: "volume.status.resource.tenantID != ''"
- celExpr: "volume.status.resource.userID != ''"
- celExpr: "volume.status.resource.volumeType != ''"
- celExpr: "volume.status.resource.createdAt != ''"
- celExpr: "volume.status.resource.updatedAt != ''"
- celExpr: "volume.status.resource.imageID == image.status.id"
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Image
metadata:
name: volume-create-bootable-image
spec:
cloudCredentialsRef:
cloudName: openstack
secretName: openstack-clouds
managementPolicy: managed
resource:
content:
diskFormat: raw
download:
url: https://github.com/k-orc/openstack-resource-controller/raw/690b760f49dfb61b173755e91cb51ed42472c7f3/internal/controllers/image/testdata/raw.img
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Volume
metadata:
name: volume-create-bootable
spec:
cloudCredentialsRef:
cloudName: openstack
secretName: openstack-clouds
managementPolicy: managed
resource:
size: 1
imageRef: volume-create-bootable-image
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
apiVersion: kuttl.dev/v1beta1
kind: TestStep
commands:
- command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
namespaced: true
15 changes: 15 additions & 0 deletions internal/controllers/volume/tests/volume-dependency/00-assert.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,18 @@ status:
message: Waiting for VolumeType/volume-dependency to be created
status: "True"
reason: Progressing
---
apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Volume
metadata:
name: volume-dependency-no-image
status:
conditions:
- type: Available
message: Waiting for Image/volume-dependency-missing-image to be created
status: "False"
reason: Progressing
- type: Progressing
message: Waiting for Image/volume-dependency-missing-image to be created
status: "True"
reason: Progressing
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,16 @@ spec:
managementPolicy: managed
resource:
size: 1
---
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should then proceed to create the missing image, and check that the volume was created successfully.

We should also check that we can delete the image the volume was created from (assuming it works like server and flavors), to ensure we don't add an unnecessary finalizer.

apiVersion: openstack.k-orc.cloud/v1alpha1
kind: Volume
metadata:
name: volume-dependency-no-image
spec:
cloudCredentialsRef:
cloudName: openstack
secretName: openstack-clouds
managementPolicy: managed
resource:
size: 1
imageRef: volume-dependency-missing-image

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

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

6 changes: 6 additions & 0 deletions pkg/clients/applyconfiguration/internal/internal.go

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

Loading