Skip to content
13 changes: 6 additions & 7 deletions api/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,12 @@ const (
)

type ImageSpec struct {
Size uint64 `json:"size"`
WWN string `json:"wwn"`
Limits Limits `json:"limits"`
Image string `json:"image"`
ImageArchitecture *string `json:"imageArchitecture"`
SnapshotRef *string `json:"snapshotRef"`
Encryption *EncryptionSpec `json:"encryption"`
Size uint64 `json:"size"`
WWN string `json:"wwn"`
Limits Limits `json:"limits"`
Encryption EncryptionSpec `json:"encryption"`

SnapshotSource *string `json:"snapshotSource"`
}

type EncryptionType string
Expand Down
20 changes: 13 additions & 7 deletions api/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,22 @@ import (
type Snapshot struct {
apiutils.Metadata `json:"metadata,omitempty"`

Source SnapshotSource `json:"source"`

Spec SnapshotSpec `json:"spec"`
Status SnapshotStatus `json:"status"`
}

type SnapshotProtection string

const (
SnapshotProtectionNone SnapshotProtection = "none"
SnapshotProtectionProtected SnapshotProtection = "protected"
)

type SnapshotSpec struct {
ImageRef string `json:"imageRef"`
Protection SnapshotProtection `json:"protection"`
}

type SnapshotState string

const (
Expand All @@ -29,8 +40,3 @@ type SnapshotStatus struct {
Digest string `json:"digest"`
Size int64 `json:"size"`
}

type SnapshotSource struct {
IronCoreImage string `json:"ironcoreImage"`
VolumeImageID string `json:"volumeImageId"`
}
75 changes: 75 additions & 0 deletions api/volume.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package api

import (
apiutils "github.com/ironcore-dev/provider-utils/apiutils/api"
)

type Volume struct {
apiutils.Metadata `json:"metadata,omitempty"`

Spec VolumeSpec `json:"spec"`
Status VolumeStatus `json:"status"`
}

type VolumeState string

const (
VolumeStatePending VolumeState = "Pending"
VolumeStateAvailable VolumeState = "Available"
)

type VolumeEncryptionState string

const (
VolumeEncryptionStateHeaderSet VolumeEncryptionState = "VolumeEncryptionHeaderSet"
)

type VolumeSpec struct {
Size uint64 `json:"size"`
WWN string `json:"wwn"`
Limits Limits `json:"limits"`
VolumeEncryption VolumeEncryptionSpec `json:"encryption"`

Source VolumeSource `json:"source"`
}

type VolumeSource struct {
OSVolume *OSVolumeSource `json:"osVolume"`
SnapshotSource *string `json:"snapshotSource"`
}

type OSVolumeSource struct {
Name string `json:"name"`
Architecture *string `json:"architecture"`
}

type VolumeEncryptionType string

const (
VolumeEncryptionTypeEncrypted VolumeEncryptionType = "Encrypted"
VolumeEncryptionTypeUnencrypted VolumeEncryptionType = "Unencrypted"
)

type VolumeEncryptionSpec struct {
Type VolumeEncryptionType `json:"type"`
EncryptedPassphrase []byte `json:"encryptedPassphrase"`
}

type VolumeStatus struct {
State VolumeState `json:"state"`
VolumeEncryption VolumeEncryptionState `json:"encryption"`
Access *VolumeAccess `json:"access"`
Size uint64 `json:"size"`
ImageRef string `json:"imageRef"`
}

type VolumeAccess struct {
Monitors string `json:"monitors"`
Handle string `json:"handle"`

User string `json:"user"`
UserKey string `json:"userKey"`
}
1 change: 1 addition & 0 deletions internal/controllers/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
ImageSnapshotVersion = "v1"
)

// TODO remove/check if needed
func ImageIDToRBDID(imageID string) string {
return ImageRBDIDPrefix + imageID
}
Expand Down
36 changes: 21 additions & 15 deletions internal/controllers/image_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ type ImageReconcilerOptions struct {
func NewImageReconciler(
log logr.Logger,
conn *rados.Conn,
registry image.Source,
images store.Store[*providerapi.Image],
snapshots store.Store[*providerapi.Snapshot],
eventRecorder eventrecorder.EventRecorder,
Expand All @@ -58,6 +59,10 @@ func NewImageReconciler(
return nil, fmt.Errorf("must specify conn")
}

if registry == nil {
return nil, fmt.Errorf("must specify registry")
}

if images == nil {
return nil, fmt.Errorf("must specify image store")
}
Expand Down Expand Up @@ -97,6 +102,7 @@ func NewImageReconciler(
return &ImageReconciler{
log: log,
conn: conn,
registry: registry,
queue: workqueue.NewTypedRateLimitingQueue[string](workqueue.DefaultTypedControllerRateLimiter[string]()),
images: images,
snapshots: snapshots,
Expand All @@ -115,7 +121,8 @@ type ImageReconciler struct {
log logr.Logger
conn *rados.Conn

queue workqueue.TypedRateLimitingInterface[string]
registry image.Source
queue workqueue.TypedRateLimitingInterface[string]

images store.Store[*providerapi.Image]
snapshots store.Store[*providerapi.Snapshot]
Expand Down Expand Up @@ -492,7 +499,7 @@ func (r *ImageReconciler) isImageExisting(ioCtx *rados.IOContext, imageID string
}

for _, img := range images {
if ImageIDToRBDID(imageID) == img {
if ImageIDToRBDID(image.ID) == img {
return true, nil
}
}
Expand All @@ -502,9 +509,9 @@ func (r *ImageReconciler) isImageExisting(ioCtx *rados.IOContext, imageID string

func (r *ImageReconciler) updateImage(ctx context.Context, log logr.Logger, ioCtx *rados.IOContext, image *providerapi.Image) (err error) {
log.V(2).Info("Updating image")
img, err := openImage(ioCtx, ImageIDToRBDID(image.ID))
img, err := librbd.OpenImage(ioCtx, ImageIDToRBDID(image.ID), librbd.NoSnapshot)
if err != nil {
return err
return fmt.Errorf("failed to open image: %w", err)
}
defer closeImage(log, img)

Expand Down Expand Up @@ -596,8 +603,8 @@ func (r *ImageReconciler) reconcileImage(ctx context.Context, id string) error {
log.V(2).Info("Configured pool", "pool", r.pool)

switch {
case img.Spec.SnapshotRef != nil:
snapshotRef := img.Spec.SnapshotRef
case img.Spec.SnapshotSource != nil:
snapshotRef := img.Spec.SnapshotSource
log.V(2).Info("Creating image from snapshot", "snapshotRef", snapshotRef)
ok, err := r.createImageFromSnapshot(ctx, log, ioCtx, img, *snapshotRef, options)
if err != nil {
Expand Down Expand Up @@ -676,9 +683,9 @@ func (r *ImageReconciler) setImageLimits(log logr.Logger, ioCtx *rados.IOContext

func (r *ImageReconciler) setWWN(log logr.Logger, ioCtx *rados.IOContext, image *providerapi.Image) error {
log.V(1).Info("Setting WWN")
img, err := openImage(ioCtx, ImageIDToRBDID(image.ID))
img, err := librbd.OpenImage(ioCtx, ImageIDToRBDID(image.ID), librbd.NoSnapshot)
if err != nil {
return err
return fmt.Errorf("failed to open rbd image: %w", err)
}
defer closeImage(log, img)

Expand All @@ -691,7 +698,7 @@ func (r *ImageReconciler) setWWN(log logr.Logger, ioCtx *rados.IOContext, image
}

func (r *ImageReconciler) setEncryptionHeader(ctx context.Context, log logr.Logger, ioCtx *rados.IOContext, image *providerapi.Image) error {
if image.Spec.Encryption == nil || image.Spec.Encryption.Type == "" || image.Spec.Encryption.Type == providerapi.EncryptionTypeUnencrypted || image.Status.Encryption == providerapi.EncryptionStateHeaderSet {
if image.Spec.Encryption.Type == "" || image.Spec.Encryption.Type == providerapi.EncryptionTypeUnencrypted || image.Status.Encryption == providerapi.EncryptionStateHeaderSet {
return nil
}

Expand All @@ -701,9 +708,9 @@ func (r *ImageReconciler) setEncryptionHeader(ctx context.Context, log logr.Logg
return fmt.Errorf("failed to decrypt passphrase: %w", err)
}

img, err := openImage(ioCtx, ImageIDToRBDID(image.ID))
img, err := librbd.OpenImage(ioCtx, ImageIDToRBDID(image.ID), librbd.NoSnapshot)
if err != nil {
return err
return fmt.Errorf("failed to open rbd image: %w", err)
}
defer closeImage(log, img)

Expand Down Expand Up @@ -746,7 +753,7 @@ func (r *ImageReconciler) createImageFromSnapshot(ctx context.Context, log logr.
return false, nil
}

if snapshot.Status.State != providerapi.SnapshotStateReady && snapshot.Status.State != providerapi.SnapshotStatePopulated {
if snapshot.Status.State != providerapi.SnapshotStateReady {
log.V(1).Info("snapshot is not populated", "state", snapshot.Status.State)
return false, nil
}
Expand All @@ -762,16 +769,15 @@ func (r *ImageReconciler) createImageFromSnapshot(ctx context.Context, log logr.
}
defer ioCtx2.Destroy()

log.V(1).Info("Cloning Image", "ParentName", parentName, "SnapName", snapName, "ImageID", image.ID)
if err = librbd.CloneImage(ioCtx2, parentName, snapName, ioCtx, ImageIDToRBDID(image.ID), options); err != nil {
r.Eventf(image.Metadata, corev1.EventTypeWarning, "CreateImageFromSnapshotFailed", "Failed to clone rbd image: %s", err)
return false, fmt.Errorf("failed to clone rbd image: %w", err)
}
log.V(2).Info("Cloned image")

img, err := openImage(ioCtx, ImageIDToRBDID(image.ID))
img, err := librbd.OpenImage(ioCtx, ImageIDToRBDID(image.ID), librbd.NoSnapshot)
if err != nil {
return false, err
return false, fmt.Errorf("failed to open rbd image: %w", err)
}
defer closeImage(log, img)

Expand Down
56 changes: 51 additions & 5 deletions internal/controllers/snapshot_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
func NewSnapshotReconciler(
log logr.Logger,
conn *rados.Conn,
registry image.Source,
store store.Store[*providerapi.Snapshot],
images store.Store[*providerapi.Image],
events event.Source[*providerapi.Snapshot],
Expand All @@ -44,6 +45,10 @@
return nil, fmt.Errorf("must specify conn")
}

if registry == nil {
return nil, fmt.Errorf("must specify registry")
}

if store == nil {
return nil, fmt.Errorf("must specify store")
}
Expand Down Expand Up @@ -71,6 +76,7 @@
return &SnapshotReconciler{
log: log,
conn: conn,
registry: registry,
queue: workqueue.NewTypedRateLimitingQueue[string](workqueue.DefaultTypedControllerRateLimiter[string]()),
store: store,
images: images,
Expand All @@ -82,9 +88,11 @@
}

type SnapshotReconciler struct {
log logr.Logger
conn *rados.Conn
queue workqueue.TypedRateLimitingInterface[string]
log logr.Logger
conn *rados.Conn

registry image.Source
queue workqueue.TypedRateLimitingInterface[string]

store store.Store[*providerapi.Snapshot]
images store.Store[*providerapi.Image]
Expand Down Expand Up @@ -152,6 +160,39 @@
SnapshotFinalizer = "snapshot"
)

func (r *SnapshotReconciler) removeSnapshot(log logr.Logger, snapshotID string, img *librbd.Image) error {
log.V(2).Info("Remove snapshot")

pools, imgs, err := img.ListChildren()
if err != nil {
return fmt.Errorf("unable to list children: %w", err)
}
log.V(2).Info("Snapshot references", "pools", len(pools), "rbd-images", len(imgs))

if len(pools) != 0 || len(imgs) != 0 {
return fmt.Errorf("unable to delete snapshot: still in use")
}

snapshot := img.GetSnapshot(snapshotID)
isProtected, err := snapshot.IsProtected()
if err != nil {
return fmt.Errorf("unable to check if snapshot is protected: %w", err)
}

if isProtected {
if err := snapshot.Unprotect(); err != nil {
return fmt.Errorf("unable to unprotect snapshot: %w", err)
}
}

if err := snapshot.Remove(); err != nil {
return fmt.Errorf("unable to remove snapshot: %w", err)
}
log.V(2).Info("Snapshot Removed")

return nil
}

func (r *SnapshotReconciler) deleteSnapshot(ctx context.Context, log logr.Logger, ioCtx *rados.IOContext, snapshot *providerapi.Snapshot) error {
if !slices.Contains(snapshot.Finalizers, SnapshotFinalizer) {
log.V(1).Info("snapshot has no finalizer: done")
Expand Down Expand Up @@ -367,10 +408,12 @@
func (r *SnapshotReconciler) openIroncoreImageSource(ctx context.Context, imageReference string, platform *ocispec.Platform) (io.ReadCloser, uint64, string, error) {
osImgSrc, err := createOsImageSource(platform)
if err != nil {
return nil, 0, "", fmt.Errorf("failed to create os image source: %w", err)
return fmt.Errorf("failed to open rbd image: %w", err)
}
defer closeImage(log, rbdImg)

img, err := osImgSrc.Resolve(ctx, imageReference)
snapshotName := snapshot.ID
imgSnap, err := rbdImg.CreateSnapshot(snapshotName)
if err != nil {
return nil, 0, "", fmt.Errorf("failed to resolve image ref in os image source: %w", err)
}
Expand Down Expand Up @@ -402,19 +445,22 @@

if err := r.populateImage(log, rbdImg, rc); err != nil {
return fmt.Errorf("failed to populate os image: %w", err)
snapshot.Status.State = providerapi.SnapshotStateReady
if _, err = r.store.Update(ctx, snapshot); err != nil {
return fmt.Errorf("failed to update snapshot: %w", err)
}
log.V(2).Info("Populated os image on rbd image")

return nil
}

func (r *SnapshotReconciler) populateImage(log logr.Logger, dst io.WriteCloser, src io.Reader) error {

Check failure on line 457 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

expected operand, found '.'

Check failure on line 457 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

missing ',' in argument list

Check failure on line 457 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / lint

missing ',' in argument list (typecheck)

Check failure on line 457 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / lint

syntax error: unexpected name logr in argument list; possibly missing comma or ) (typecheck)

Check failure on line 457 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

expected operand, found '.'

Check failure on line 457 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

missing ',' in argument list
throughputReader := rater.NewRater(src)
ticker := time.NewTicker(5 * time.Second)
defer ticker.Stop()

Check failure on line 460 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

missing ',' before newline in argument list

Check failure on line 460 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

missing ',' in argument list

Check failure on line 460 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / lint

missing ',' in argument list (typecheck)

Check failure on line 460 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

missing ',' before newline in argument list

Check failure on line 460 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

missing ',' in argument list
done := make(chan struct{})

Check failure on line 461 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

missing ',' before newline in argument list

Check failure on line 461 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

missing ',' in argument list

Check failure on line 461 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / lint

missing ',' in argument list (typecheck)

Check failure on line 461 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

missing ',' before newline in argument list

Check failure on line 461 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

missing ',' in argument list

go func() {

Check failure on line 463 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

missing ',' in argument list

Check failure on line 463 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

expected operand, found 'go'

Check failure on line 463 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / lint

expected operand, found 'go' (typecheck)

Check failure on line 463 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

missing ',' in argument list

Check failure on line 463 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

expected operand, found 'go'
for {
select {
case <-ticker.C:
Expand All @@ -423,14 +469,14 @@
return
}
}
}()

Check failure on line 472 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

missing ',' before newline in argument list

Check failure on line 472 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / lint

missing ',' before newline in argument list (typecheck)

Check failure on line 472 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

missing ',' before newline in argument list
defer func() { close(done) }()

Check failure on line 473 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

expected operand, found 'defer'

Check failure on line 473 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / lint

expected operand, found 'defer' (typecheck)

Check failure on line 473 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / run

expected operand, found 'defer'

buffer := make([]byte, r.populatorBufferSize)
_, err := io.CopyBuffer(dst, throughputReader, buffer)
if err != nil {

Check failure on line 477 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / lint

expected operand, found 'if' (typecheck)
return fmt.Errorf("failed to populate image: %w", err)

Check failure on line 478 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / lint

expected operand, found 'return' (typecheck)
}

Check failure on line 479 in internal/controllers/snapshot_controller.go

View workflow job for this annotation

GitHub Actions / lint

missing ',' before newline in argument list (typecheck)
log.Info("Successfully populated image")

return nil
Expand Down
Loading
Loading