diff --git a/api/machine.go b/api/machine.go index e6f9520e..06260c50 100644 --- a/api/machine.go +++ b/api/machine.go @@ -7,6 +7,7 @@ import ( "time" apiutils "github.com/ironcore-dev/provider-utils/apiutils/api" + "k8s.io/utils/ptr" ) type Machine struct { @@ -22,8 +23,7 @@ type MachineSpec struct { Cpu int64 `json:"cpu"` MemoryBytes int64 `json:"memoryBytes"` - Image *string `json:"image"` - Ignition []byte `json:"ignition"` + Ignition []byte `json:"ignition"` Volumes []*VolumeSpec `json:"volumes"` NetworkInterfaces []*NetworkInterfaceSpec `json:"networkInterfaces"` @@ -68,7 +68,7 @@ const ( type VolumeSpec struct { Name string `json:"name"` Device string `json:"device"` - EmptyDisk *EmptyDiskSpec `json:"emptyDisk,omitempty"` + LocalDisk *LocalDiskSpec `json:"localDisk,omitempty"` Connection *VolumeConnection `json:"cephDisk,omitempty"` } @@ -79,8 +79,9 @@ type VolumeStatus struct { Size int64 `json:"size,omitempty"` } -type EmptyDiskSpec struct { - Size int64 `json:"size"` +type LocalDiskSpec struct { + Size int64 `json:"size"` + Image *string `json:"image"` } type VolumeConnection struct { @@ -122,3 +123,25 @@ const ( type GuestAgentStatus struct { Addr string `json:"addr,omitempty"` } + +func HasBootImage(machine *Machine) *string { + for _, volume := range machine.Spec.Volumes { + if volume.LocalDisk == nil { + continue + } + + if volume.LocalDisk.Image != nil { + return volume.LocalDisk.Image + } + } + return nil +} + +func IsImageReferenced(machine *Machine, image string) bool { + bootImage := HasBootImage(machine) + if bootImage == nil { + return false + } + + return ptr.Deref(bootImage, "") == image +} diff --git a/cmd/libvirt-provider/app/app.go b/cmd/libvirt-provider/app/app.go index a6721194..684d43f3 100644 --- a/cmd/libvirt-provider/app/app.go +++ b/cmd/libvirt-provider/app/app.go @@ -33,7 +33,7 @@ import ( "github.com/ironcore-dev/libvirt-provider/internal/networkinterfaceplugin" volumeplugin "github.com/ironcore-dev/libvirt-provider/internal/plugins/volume" "github.com/ironcore-dev/libvirt-provider/internal/plugins/volume/ceph" - "github.com/ironcore-dev/libvirt-provider/internal/plugins/volume/emptydisk" + "github.com/ironcore-dev/libvirt-provider/internal/plugins/volume/localdisk" "github.com/ironcore-dev/libvirt-provider/internal/raw" "github.com/ironcore-dev/libvirt-provider/internal/server" "github.com/ironcore-dev/libvirt-provider/internal/strategy" @@ -265,7 +265,7 @@ func Run(ctx context.Context, opts Options) error { volumePlugins := volumeplugin.NewPluginManager() if err := volumePlugins.InitPlugins(providerHost, []volumeplugin.Plugin{ ceph.NewPlugin(), - emptydisk.NewPlugin(rawInst), + localdisk.NewPlugin(rawInst, imgCache), }); err != nil { setupLog.Error(err, "failed to initialize volume plugin manager") return err diff --git a/go.mod b/go.mod index 5a01643f..376253b0 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/ironcore-dev/libvirt-provider -go 1.24.0 - -toolchain go1.24.1 +go 1.24.1 require ( github.com/blang/semver/v4 v4.0.0 @@ -11,7 +9,7 @@ require ( github.com/go-logr/logr v1.4.3 github.com/google/uuid v1.6.0 github.com/ironcore-dev/controller-utils v0.10.0 - github.com/ironcore-dev/ironcore v0.2.4 + github.com/ironcore-dev/ironcore v0.2.5-0.20251024123813-eecafba97af9 github.com/ironcore-dev/ironcore-image v0.2.5 github.com/ironcore-dev/ironcore-net v0.2.4 github.com/ironcore-dev/provider-utils v0.0.0-20251010114402-394db8015df4 @@ -24,13 +22,13 @@ require ( github.com/spf13/pflag v1.0.10 golang.org/x/sync v0.17.0 google.golang.org/grpc v1.76.0 - k8s.io/api v0.33.4 - k8s.io/apimachinery v0.33.4 - k8s.io/client-go v0.33.4 - k8s.io/kubectl v0.33.3 + k8s.io/api v0.34.1 + k8s.io/apimachinery v0.34.1 + k8s.io/client-go v0.34.1 + k8s.io/kubectl v0.34.1 k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 libvirt.org/go/libvirtxml v1.11008.0 - sigs.k8s.io/controller-runtime v0.21.0 + sigs.k8s.io/controller-runtime v0.22.3 ) require ( @@ -57,7 +55,7 @@ require ( github.com/evanphx/json-patch/v5 v5.9.11 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.8.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect @@ -67,7 +65,7 @@ require ( github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/google/btree v1.1.3 // indirect - github.com/google/gnostic-models v0.6.9 // indirect + github.com/google/gnostic-models v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect github.com/google/pprof v0.0.0-20250820193118-f64d9cf942d6 // indirect github.com/gorilla/mux v1.8.1 // indirect @@ -83,12 +81,13 @@ require ( github.com/moby/locker v1.0.1 // indirect github.com/moby/spdystream v0.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.1 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/common v0.66.1 // indirect @@ -113,25 +112,25 @@ require ( golang.org/x/mod v0.27.0 // indirect golang.org/x/net v0.43.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect - golang.org/x/sys v0.35.0 // indirect + golang.org/x/sys v0.37.0 // indirect golang.org/x/term v0.34.0 // indirect golang.org/x/text v0.28.0 // indirect golang.org/x/time v0.12.0 // indirect golang.org/x/tools v0.36.0 // indirect gomodules.xyz/jsonpatch/v2 v2.5.0 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect - google.golang.org/protobuf v1.36.8 // indirect + google.golang.org/protobuf v1.36.10 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/apiextensions-apiserver v0.33.3 // indirect - k8s.io/apiserver v0.33.3 // indirect - k8s.io/cli-runtime v0.33.3 // indirect + k8s.io/apiextensions-apiserver v0.34.1 // indirect + k8s.io/apiserver v0.34.1 // indirect + k8s.io/cli-runtime v0.34.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250610211856-8b98d1ed966a // indirect + k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect oras.land/oras-go v1.2.6 // indirect sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index 4291aafe..dcc21fe2 100644 --- a/go.sum +++ b/go.sum @@ -87,8 +87,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= -github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= github.com/gkampitakis/ciinfo v0.3.2/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= @@ -133,10 +133,9 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -152,7 +151,6 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= @@ -163,8 +161,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/ironcore-dev/controller-utils v0.10.0 h1:nG+TsVMtxt6PLJrlLr2LePYd8cOxud+z76rhuy5gCOs= github.com/ironcore-dev/controller-utils v0.10.0/go.mod h1:ZGv1A78M0X2G20ex/7F5BbyHoG0+7U2IWfqvzHh8T+4= -github.com/ironcore-dev/ironcore v0.2.4 h1:i/RqiMIdzaptuDR6EKSX9hbeolj7AfTuT+4v1ZC4Jeg= -github.com/ironcore-dev/ironcore v0.2.4/go.mod h1:idYH/uyjxsIx8k3PFfXNtwfEpF9bBzBNe0aJFpIZpxs= +github.com/ironcore-dev/ironcore v0.2.5-0.20251024123813-eecafba97af9 h1:QQfXQO7+/RuWsMRCfO5GsODpe0WyW0SQBIEp8ri16G8= +github.com/ironcore-dev/ironcore v0.2.5-0.20251024123813-eecafba97af9/go.mod h1:9ATw2BEKZSdtUtNK9sAd+GLrcoxF2iF4Q6S67KqfKow= github.com/ironcore-dev/ironcore-image v0.2.5 h1:ImJD3B1p1/JVs5vx7YB3c8dwH/EHyWOcgWUqhTUmsR0= github.com/ironcore-dev/ironcore-image v0.2.5/go.mod h1:pQLc+7dzgjYORTpEM9ORM757Vm14EQRju9hsvR8VT/U= github.com/ironcore-dev/ironcore-net v0.2.4 h1:r2SqxwStws86KBvxJcK/myRxb946qAZVpPKd1rJzfsQ= @@ -220,8 +218,9 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -403,8 +402,8 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= -golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= +golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -427,15 +426,15 @@ gomodules.xyz/jsonpatch/v2 v2.5.0 h1:JELs8RLM12qJGXU4u/TO3V25KW8GreMKl9pdkk14RM0 gomodules.xyz/jsonpatch/v2 v2.5.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= +google.golang.org/genproto v0.0.0-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg= google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8= google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.76.0 h1:UnVkv1+uMLYXoIz6o7chp59WfQUYA2ex/BXQ9rHZu7A= google.golang.org/grpc v1.76.0/go.mod h1:Ju12QI8M6iQJtbcsV+awF5a4hfJMLi4X0JLo94ULZ6c= -google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= -google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= @@ -452,39 +451,39 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU= gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= -k8s.io/api v0.33.4 h1:oTzrFVNPXBjMu0IlpA2eDDIU49jsuEorGHB4cvKupkk= -k8s.io/api v0.33.4/go.mod h1:VHQZ4cuxQ9sCUMESJV5+Fe8bGnqAARZ08tSTdHWfeAc= -k8s.io/apiextensions-apiserver v0.33.3 h1:qmOcAHN6DjfD0v9kxL5udB27SRP6SG/MTopmge3MwEs= -k8s.io/apiextensions-apiserver v0.33.3/go.mod h1:oROuctgo27mUsyp9+Obahos6CWcMISSAPzQ77CAQGz8= -k8s.io/apimachinery v0.33.4 h1:SOf/JW33TP0eppJMkIgQ+L6atlDiP/090oaX0y9pd9s= -k8s.io/apimachinery v0.33.4/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/apiserver v0.33.3 h1:Wv0hGc+QFdMJB4ZSiHrCgN3zL3QRatu56+rpccKC3J4= -k8s.io/apiserver v0.33.3/go.mod h1:05632ifFEe6TxwjdAIrwINHWE2hLwyADFk5mBsQa15E= -k8s.io/cli-runtime v0.33.3 h1:Dgy4vPjNIu8LMJBSvs8W0LcdV0PX/8aGG1DA1W8lklA= -k8s.io/cli-runtime v0.33.3/go.mod h1:yklhLklD4vLS8HNGgC9wGiuHWze4g7x6XQZ+8edsKEo= -k8s.io/client-go v0.33.4 h1:TNH+CSu8EmXfitntjUPwaKVPN0AYMbc9F1bBS8/ABpw= -k8s.io/client-go v0.33.4/go.mod h1:LsA0+hBG2DPwovjd931L/AoaezMPX9CmBgyVyBZmbCY= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apiextensions-apiserver v0.34.1 h1:NNPBva8FNAPt1iSVwIE0FsdrVriRXMsaWFMqJbII2CI= +k8s.io/apiextensions-apiserver v0.34.1/go.mod h1:hP9Rld3zF5Ay2Of3BeEpLAToP+l4s5UlxiHfqRaRcMc= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/apiserver v0.34.1 h1:U3JBGdgANK3dfFcyknWde1G6X1F4bg7PXuvlqt8lITA= +k8s.io/apiserver v0.34.1/go.mod h1:eOOc9nrVqlBI1AFCvVzsob0OxtPZUCPiUJL45JOTBG0= +k8s.io/cli-runtime v0.34.1 h1:btlgAgTrYd4sk8vJTRG6zVtqBKt9ZMDeQZo2PIzbL7M= +k8s.io/cli-runtime v0.34.1/go.mod h1:aVA65c+f0MZiMUPbseU/M9l1Wo2byeaGwUuQEQVVveE= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= +k8s.io/component-base v0.34.1 h1:v7xFgG+ONhytZNFpIz5/kecwD+sUhVE6HU7qQUiRM4A= +k8s.io/component-base v0.34.1/go.mod h1:mknCpLlTSKHzAQJJnnHVKqjxR7gBeHRv0rPXA7gdtQ0= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250610211856-8b98d1ed966a h1:ZV3Zr+/7s7aVbjNGICQt+ppKWsF1tehxggNfbM7XnG8= -k8s.io/kube-openapi v0.0.0-20250610211856-8b98d1ed966a/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/kubectl v0.33.3 h1:r/phHvH1iU7gO/l7tTjQk2K01ER7/OAJi8uFHHyWSac= -k8s.io/kubectl v0.33.3/go.mod h1:euj2bG56L6kUGOE/ckZbCoudPwuj4Kud7BR0GzyNiT0= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b h1:MloQ9/bdJyIu9lb1PzujOPolHyvO06MXG5TUIj2mNAA= +k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b/go.mod h1:UZ2yyWbFTpuhSbFhv24aGNOdoRdJZgsIObGBUaYVsts= +k8s.io/kubectl v0.34.1 h1:1qP1oqT5Xc93K+H8J7ecpBjaz511gan89KO9Vbsh/OI= +k8s.io/kubectl v0.34.1/go.mod h1:JRYlhJpGPyk3dEmJ+BuBiOB9/dAvnrALJEiY/C5qa6A= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y= k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= libvirt.org/go/libvirtxml v1.11008.0 h1:R5nffNDkOn3bndLiOXvmvld38J5QLZ2D6ZJdLOYOYBg= libvirt.org/go/libvirtxml v1.11008.0/go.mod h1:7Oq2BLDstLr/XtoQD8Fr3mfDNrzlI3utYKySXF2xkng= oras.land/oras-go v1.2.6 h1:z8cmxQXBU8yZ4mkytWqXfo6tZcamPwjsuxYU81xJ8Lk= oras.land/oras-go v1.2.6/go.mod h1:OVPc1PegSEe/K8YiLfosrlqlqTN9PUyFvOw5Y9gwrT8= -sigs.k8s.io/controller-runtime v0.21.0 h1:CYfjpEuicjUecRk+KAeyYh+ouUBn4llGyDYytIGcJS8= -sigs.k8s.io/controller-runtime v0.21.0/go.mod h1:OSg14+F65eWqIu4DceX7k/+QRAbTTvxeQSNSOQpukWM= +sigs.k8s.io/controller-runtime v0.22.3 h1:I7mfqz/a/WdmDCEnXmSPm8/b/yRTy6JsKKENTijTq8Y= +sigs.k8s.io/controller-runtime v0.22.3/go.mod h1:+QX1XUpTXN4mLoblf4tqr5CQcyHPAki2HLXqQMY6vh8= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= -sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/internal/controllers/machine_controller.go b/internal/controllers/machine_controller.go index ac628e47..059915db 100644 --- a/internal/controllers/machine_controller.go +++ b/internal/controllers/machine_controller.go @@ -23,7 +23,6 @@ import ( "github.com/ironcore-dev/libvirt-provider/internal/libvirt/guest" libvirtmeta "github.com/ironcore-dev/libvirt-provider/internal/libvirt/meta" libvirtutils "github.com/ironcore-dev/libvirt-provider/internal/libvirt/utils" - "github.com/ironcore-dev/libvirt-provider/internal/osutils" providernetworkinterface "github.com/ironcore-dev/libvirt-provider/internal/plugins/networkinterface" providervolume "github.com/ironcore-dev/libvirt-provider/internal/plugins/volume" "github.com/ironcore-dev/libvirt-provider/internal/raw" @@ -35,14 +34,12 @@ import ( corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/util/workqueue" - "k8s.io/utils/ptr" "libvirt.org/go/libvirtxml" ) const ( MachineFinalizer = "machine" filePerm = 0666 - rootFSAlias = "ua-rootfs" libvirtDomainXMLIgnitionKeyName = "opt/com.coreos/config" networkInterfaceAliasPrefix = "ua-networkinterface-" @@ -159,8 +156,8 @@ func (r *MachineReconciler) Start(ctx context.Context) error { } for _, machine := range machines { - if ptr.Deref(machine.Spec.Image, "") == evt.Ref { - r.eventRecorder.Eventf(machine.Metadata, corev1.EventTypeNormal, "PulledImage", "Pulled image %s", *machine.Spec.Image) + if api.IsImageReferenced(machine, evt.Ref) { + r.eventRecorder.Eventf(machine.Metadata, corev1.EventTypeNormal, "PulledImage", "Pulled image %s", evt.Ref) log.V(1).Info("Image pulled: Requeue machines", "Image", evt.Ref, "Machine", machine.ID) r.queue.Add(machine.ID) } @@ -418,6 +415,21 @@ func (r *MachineReconciler) reconcileMachine(ctx context.Context, id string) err } log.V(1).Info("Successfully made machine directories") + if bootImage := api.HasBootImage(machine); bootImage != nil { + log.V(1).Info("Boot image referenced", "image", bootImage) + + _, err := r.imageCache.Get(ctx, *bootImage) + if err != nil { + if errors.Is(err, ociutils.ErrImagePulling) { + log.V(1).Info("Image is pulling, reconcile later") + r.eventRecorder.Eventf(machine.Metadata, corev1.EventTypeNormal, "PullingImage", "Pulling image in progress") + return nil + } + return err + } + log.V(2).Info("Image is present") + } + log.V(1).Info("Reconciling domain") state, volumeStates, nicStates, err := r.reconcileDomain(ctx, log, machine) if err != nil { @@ -690,12 +702,6 @@ func (r *MachineReconciler) domainFor( r.setGuestAgent(machine, domainDesc) } - if machineImgRef := machine.Spec.Image; machineImgRef != nil && ptr.Deref(machineImgRef, "") != "" { - if err := r.setDomainImage(ctx, log, machine, domainDesc, ptr.Deref(machineImgRef, "")); err != nil { - return nil, nil, nil, err - } - } - if ignitionSpec := machine.Spec.Ignition; ignitionSpec != nil { if err := r.setDomainIgnition(machine, domainDesc); err != nil { return nil, nil, nil, err @@ -840,61 +846,6 @@ func (r *MachineReconciler) setGuestAgent(machine *api.Machine, domainDesc *libv machine.Status.GuestAgentStatus = &api.GuestAgentStatus{Addr: "unix://" + socketPath} } -func (r *MachineReconciler) setDomainImage( - ctx context.Context, - log logr.Logger, - machine *api.Machine, - domain *libvirtxml.Domain, - machineImgRef string, -) error { - img, err := r.imageCache.Get(ctx, machineImgRef) - if err != nil { - if !errors.Is(err, ociutils.ErrImagePulling) { - return err - } - - r.eventRecorder.Eventf(machine.Metadata, corev1.EventTypeNormal, "PullingImage", "Pulling image %s", machineImgRef) - return err - } - - rootFSFile := r.host.MachineRootFSFile(machine.ID) - ok, err := osutils.RegularFileExists(rootFSFile) - if err != nil { - return err - } - if !ok { - if err := r.raw.Create(rootFSFile, raw.WithSourceFile(img.RootFS.Path)); err != nil { - return fmt.Errorf("error creating root fs disk: %w", err) - } - if err := os.Chmod(rootFSFile, filePerm); err != nil { - return fmt.Errorf("error changing root fs disk mode: %w", err) - } - } - - domain.Devices.Disks = append(domain.Devices.Disks, libvirtxml.DomainDisk{ - Alias: &libvirtxml.DomainAlias{ - Name: rootFSAlias, - }, - Device: "disk", - Driver: &libvirtxml.DomainDiskDriver{ - Name: "qemu", - Type: "raw", - }, - Source: &libvirtxml.DomainDiskSource{ - File: &libvirtxml.DomainDiskSourceFile{ - File: rootFSFile, - }, - }, - Target: &libvirtxml.DomainDiskTarget{ - Dev: "vdaaa", // TODO: Reserving vdaaa for ramdisk, so that it doesnt conflict with other volumes, investigate better solution. - Bus: "virtio", - }, - Serial: "machineboot", - ReadOnly: &libvirtxml.DomainDiskReadOnly{}, - }) - return nil -} - func (r *MachineReconciler) setDomainIgnition(machine *api.Machine, domain *libvirtxml.Domain) error { ignitionData := machine.Spec.Ignition diff --git a/internal/controllers/machine_controller_volumes.go b/internal/controllers/machine_controller_volumes.go index e4ea1566..e002c918 100644 --- a/internal/controllers/machine_controller_volumes.go +++ b/internal/controllers/machine_controller_volumes.go @@ -675,9 +675,8 @@ func (r *MachineReconciler) applyVolume( }); err != nil && !errors.Is(err, ErrAttachedVolumeAlreadyExists) { return "", 0, fmt.Errorf("error ensuring volume is attached: %w", err) } - - //TODO do epsilon comparison - if lastVolumeSize != 0 && volumeSize != lastVolumeSize { + isBootDisk := desiredVolume.LocalDisk != nil && desiredVolume.LocalDisk.Image != nil + if lastVolumeSize != 0 && volumeSize != lastVolumeSize && !isBootDisk { log.V(1).Info("Resize volume", "volumeID", volumeID, "lastSize", lastVolumeSize, "volumeSize", volumeSize) if err := attacher.ResizeVolume(&AttachVolume{ Name: desiredVolume.Name, @@ -772,6 +771,7 @@ func (a *libvirtVolumeAttacher) providerVolumeToLibvirt(computeVolumeName string File: vol.RawFile, }, } + return disk, nil, nil, nil, nil, nil case vol.CephDisk != nil: var ( diff --git a/internal/plugins/volume/emptydisk/emptydisk.go b/internal/plugins/volume/localdisk/emptydisk.go similarity index 50% rename from internal/plugins/volume/emptydisk/emptydisk.go rename to internal/plugins/volume/localdisk/emptydisk.go index f202bc0b..13688fa5 100644 --- a/internal/plugins/volume/emptydisk/emptydisk.go +++ b/internal/plugins/volume/localdisk/emptydisk.go @@ -1,25 +1,28 @@ // SPDX-FileCopyrightText: 2023 SAP SE or an SAP affiliate company and IronCore contributors // SPDX-License-Identifier: Apache-2.0 -package emptydisk +package localdisk import ( "context" - "crypto/rand" + "crypto/sha1" "encoding/hex" - "errors" "fmt" "os" "path/filepath" + "strings" + "github.com/go-logr/logr" "github.com/ironcore-dev/libvirt-provider/api" + "github.com/ironcore-dev/libvirt-provider/internal/osutils" "github.com/ironcore-dev/libvirt-provider/internal/plugins/volume" "github.com/ironcore-dev/libvirt-provider/internal/raw" + ociutils "github.com/ironcore-dev/provider-utils/ociutils/oci" utilstrings "k8s.io/utils/strings" ) const ( - pluginName = "libvirt-provider.ironcore.dev/empty-disk" + pluginName = "libvirt-provider.ironcore.dev/local-disk" defaultSize = 500 * 1024 * 1024 // 500Mi by default @@ -30,11 +33,14 @@ const ( type plugin struct { host volume.Host raw raw.Raw + + imageCache ociutils.Cache } -func NewPlugin(raw raw.Raw) volume.Plugin { +func NewPlugin(raw raw.Raw, osImages ociutils.Cache) volume.Plugin { return &plugin{ - raw: raw, + raw: raw, + imageCache: osImages, } } @@ -48,14 +54,14 @@ func (p *plugin) Name() string { } func (p *plugin) GetBackingVolumeID(volume *api.VolumeSpec) (string, error) { - if volume.EmptyDisk == nil { - return "", fmt.Errorf("volume does not specify an EmptyDisk") + if volume.LocalDisk == nil { + return "", fmt.Errorf("volume does not specify an LocalDisk") } return volume.Name, nil } func (p *plugin) CanSupport(volume *api.VolumeSpec) bool { - return volume.EmptyDisk != nil + return volume.LocalDisk != nil } func (p *plugin) diskFilename(computeVolumeName string, machineID string) string { @@ -63,46 +69,73 @@ func (p *plugin) diskFilename(computeVolumeName string, machineID string) string } func (p *plugin) Apply(ctx context.Context, spec *api.VolumeSpec, machine *api.Machine) (*volume.Volume, error) { + log := logr.FromContextOrDiscard(ctx) + volumeDir := p.host.MachineVolumeDir(machine.ID, utilstrings.EscapeQualifiedName(pluginName), spec.Name) + + log.V(2).Info("Creating volume directory", "directory", volumeDir) if err := os.MkdirAll(volumeDir, perm); err != nil { return nil, err } - handle, err := randomHex(8) - if err != nil { - return nil, fmt.Errorf("failed to generate WWN/handle for the disk: %w", err) - } + diskFilename := p.diskFilename(spec.Name, machine.ID) - size := spec.EmptyDisk.Size - if size == 0 { - size = defaultSize + ok, err := osutils.RegularFileExists(diskFilename) + if err != nil { + log.V(2).Info("Disk already exists", "file", diskFilename) + return nil, err } - diskFilename := p.diskFilename(spec.Name, machine.ID) - if _, err := os.Stat(diskFilename); err != nil { - if !errors.Is(err, os.ErrNotExist) { - return nil, fmt.Errorf("error stat-ing disk: %w", err) + if !ok { + var createOption raw.CreateOption + if imgRef := spec.LocalDisk.Image; imgRef != nil { + img, err := p.imageCache.Get(ctx, *imgRef) + if err != nil { + return nil, err + } + + log.V(2).Info("Create disk with rootfs from img", "file", img.RootFS.Path) + createOption = raw.WithSourceFile(img.RootFS.Path) + } else { + size := spec.LocalDisk.Size + if size == 0 { + size = defaultSize + } + + log.V(2).Info("Create disk", "size", size) + createOption = raw.WithSize(size) } - if err := p.raw.Create(diskFilename, raw.WithSize(size)); err != nil { + if err := p.raw.Create(diskFilename, createOption); err != nil { return nil, fmt.Errorf("error creating disk %w", err) } if err := os.Chmod(diskFilename, filePerm); err != nil { return nil, fmt.Errorf("error changing disk file mode: %w", err) } } - return &volume.Volume{RawFile: diskFilename, Handle: handle}, nil + + stat, err := os.Stat(diskFilename) + if err != nil { + return nil, fmt.Errorf("error checking disk file size: %w", err) + } + + return &volume.Volume{ + RawFile: diskFilename, + Handle: generateWWN(machine.ID, spec.Name), + EffectiveStorageBytesSize: stat.Size(), + }, nil } func (p *plugin) Delete(ctx context.Context, computeVolumeName string, machineID string) error { return os.RemoveAll(p.host.MachineVolumeDir(machineID, utilstrings.EscapeQualifiedName(pluginName), computeVolumeName)) } -// randomHex generates random hexadecimal digits of the length n*2. -func randomHex(n int) (string, error) { - bytes := make([]byte, n) - if _, err := rand.Read(bytes); err != nil { - return "", err - } - return hex.EncodeToString(bytes), nil +func generateWWN(machineID, diskName string) string { + input := fmt.Sprintf("%s:%s", machineID, diskName) + hash := sha1.Sum([]byte(input)) + wwnBytes := hash[:8] + + wwnBytes[0] |= 0x80 + + return strings.ToUpper(hex.EncodeToString(wwnBytes)) } diff --git a/internal/server/machine.go b/internal/server/machine.go index a8458b30..71690fd2 100644 --- a/internal/server/machine.go +++ b/internal/server/machine.go @@ -10,6 +10,7 @@ import ( "github.com/go-logr/logr" iri "github.com/ironcore-dev/ironcore/iri/apis/machine/v1alpha1" "github.com/ironcore-dev/libvirt-provider/api" + "k8s.io/utils/ptr" ) func (s *Server) convertMachineToIRIMachine(ctx context.Context, log logr.Logger, machine *api.Machine) (*iri.Machine, error) { @@ -46,16 +47,8 @@ func (s *Server) getIRIMachineSpec(machine *api.Machine) (*iri.MachineSpec, erro return nil, fmt.Errorf("failed to get power state: %w", err) } - var imageSpec *iri.ImageSpec - if image := machine.Spec.Image; image != nil { - imageSpec = &iri.ImageSpec{ - Image: *image, - } - } - spec := &iri.MachineSpec{ Power: power, - Image: imageSpec, Class: class, IgnitionData: machine.Spec.Ignition, Volumes: s.getIRIVolumeSpec(machine), @@ -68,10 +61,16 @@ func (s *Server) getIRIMachineSpec(machine *api.Machine) (*iri.MachineSpec, erro func (s *Server) getIRIVolumeSpec(machine *api.Machine) []*iri.Volume { var volumes []*iri.Volume for _, volume := range machine.Spec.Volumes { - var emptyDisk *iri.EmptyDisk - if volume.EmptyDisk != nil { - emptyDisk = &iri.EmptyDisk{ - SizeBytes: volume.EmptyDisk.Size, + var localDisk *iri.LocalDisk + if volume.LocalDisk != nil { + localDisk = &iri.LocalDisk{ + SizeBytes: volume.LocalDisk.Size, + } + + if img := volume.LocalDisk.Image; img != nil { + localDisk.Image = &iri.ImageSpec{ + Image: *img, + } } } @@ -90,7 +89,7 @@ func (s *Server) getIRIVolumeSpec(machine *api.Machine) []*iri.Volume { volumes = append(volumes, &iri.Volume{ Name: volume.Name, Device: volume.Device, - EmptyDisk: emptyDisk, + LocalDisk: localDisk, Connection: connection, }) } @@ -239,10 +238,14 @@ func (s *Server) getVolumeFromIRIVolume(iriVolume *iri.Volume) (*api.VolumeSpec, return nil, fmt.Errorf("original volume is nil") } - var emptyDiskSpec *api.EmptyDiskSpec - if emptyDisk := iriVolume.EmptyDisk; emptyDisk != nil { - emptyDiskSpec = &api.EmptyDiskSpec{ - Size: emptyDisk.SizeBytes, + var localDiskSpec *api.LocalDiskSpec + if localDisk := iriVolume.LocalDisk; localDisk != nil { + localDiskSpec = &api.LocalDiskSpec{ + Size: localDisk.SizeBytes, + } + + if img := localDisk.Image; img != nil && img.Image != "" { + localDiskSpec.Image = ptr.To(img.Image) } } @@ -261,7 +264,7 @@ func (s *Server) getVolumeFromIRIVolume(iriVolume *iri.Volume) (*api.VolumeSpec, volumeSpec := &api.VolumeSpec{ Name: iriVolume.Name, Device: iriVolume.Device, - EmptyDisk: emptyDiskSpec, + LocalDisk: localDiskSpec, Connection: connectionSpec, } diff --git a/internal/server/machine_create.go b/internal/server/machine_create.go index 7cbd01ef..b34a03ea 100644 --- a/internal/server/machine_create.go +++ b/internal/server/machine_create.go @@ -86,10 +86,6 @@ func (s *Server) createMachineFromIRIMachine(ctx context.Context, log logr.Logge api.SetClassLabel(machine, iriMachine.Spec.Class) api.SetManagerLabel(machine, api.MachineManager) - if iriMachine.Spec.Image != nil { - machine.Spec.Image = &iriMachine.Spec.Image.Image - } - apiMachine, err := s.machineStore.Create(ctx, machine) if err != nil { return nil, fmt.Errorf("failed to create machine: %w", err) diff --git a/internal/server/machine_create_test.go b/internal/server/machine_create_test.go index 9771e695..1ef25b88 100644 --- a/internal/server/machine_create_test.go +++ b/internal/server/machine_create_test.go @@ -37,7 +37,6 @@ var _ = Describe("CreateMachine", func() { Expect(createResp).Should(SatisfyAll( HaveField("Machine.Metadata.Id", Not(BeEmpty())), HaveField("Machine.Spec.Power", iri.Power_POWER_ON), - HaveField("Machine.Spec.Image", BeNil()), HaveField("Machine.Spec.Class", machineClassx3xlarge), HaveField("Machine.Spec.IgnitionData", BeNil()), HaveField("Machine.Spec.Volumes", BeNil()), @@ -113,7 +112,7 @@ var _ = Describe("CreateMachine", func() { Volumes: []*iri.Volume{ { Name: "disk-1", - EmptyDisk: &iri.EmptyDisk{ + LocalDisk: &iri.LocalDisk{ SizeBytes: emptyDiskSize, }, Device: "oda", @@ -134,12 +133,11 @@ var _ = Describe("CreateMachine", func() { Expect(createResp).Should(SatisfyAll( HaveField("Machine.Metadata.Id", Not(BeEmpty())), HaveField("Machine.Spec.Power", iri.Power_POWER_ON), - HaveField("Machine.Spec.Image", BeNil()), HaveField("Machine.Spec.Class", machineClassx3xlarge), HaveField("Machine.Spec.IgnitionData", BeNil()), HaveField("Machine.Spec.Volumes", ContainElement(&iri.Volume{ Name: "disk-1", - EmptyDisk: &iri.EmptyDisk{ + LocalDisk: &iri.LocalDisk{ SizeBytes: emptyDiskSize, }, Device: "oda", @@ -199,7 +197,7 @@ var _ = Describe("CreateMachine", func() { HaveField("ImageRef", BeEmpty()), HaveField("Volumes", ContainElement(&iri.VolumeStatus{ Name: "disk-1", - Handle: "libvirt-provider.ironcore.dev/empty-disk/disk-1", + Handle: "libvirt-provider.ironcore.dev/local-disk/disk-1", State: iri.VolumeState_VOLUME_ATTACHED, })), HaveField("NetworkInterfaces", ContainElement(&iri.NetworkInterfaceStatus{ @@ -221,19 +219,25 @@ var _ = Describe("CreateMachine", func() { }, }, Spec: &iri.MachineSpec{ - Power: iri.Power_POWER_ON, - Image: &iri.ImageSpec{ - Image: osImage, - }, + Power: iri.Power_POWER_ON, Class: machineClassx3xlarge, IgnitionData: ignitionData, Volumes: []*iri.Volume{ + { + Name: "rootdisk", + Device: "oda", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: osImage, + }, + }, + }, { Name: "disk-1", - EmptyDisk: &iri.EmptyDisk{ + LocalDisk: &iri.LocalDisk{ SizeBytes: emptyDiskSize, }, - Device: "oda", + Device: "odb", }, }, NetworkInterfaces: []*iri.NetworkInterface{ @@ -251,16 +255,26 @@ var _ = Describe("CreateMachine", func() { Expect(createResp).Should(SatisfyAll( HaveField("Machine.Metadata.Id", Not(BeEmpty())), HaveField("Machine.Spec.Power", iri.Power_POWER_ON), - HaveField("Machine.Spec.Image.Image", Equal(osImage)), HaveField("Machine.Spec.Class", machineClassx3xlarge), HaveField("Machine.Spec.IgnitionData", Equal(ignitionData)), - HaveField("Machine.Spec.Volumes", ContainElement(&iri.Volume{ - Name: "disk-1", - Device: "oda", - EmptyDisk: &iri.EmptyDisk{ - SizeBytes: emptyDiskSize, + HaveField("Machine.Spec.Volumes", ContainElements( + &iri.Volume{ + Name: "rootdisk", + Device: "oda", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: osImage, + }, + }, }, - })), + &iri.Volume{ + Name: "disk-1", + Device: "odb", + LocalDisk: &iri.LocalDisk{ + SizeBytes: emptyDiskSize, + }, + }, + )), HaveField("Machine.Spec.NetworkInterfaces", ContainElement(&iri.NetworkInterface{ Name: "nic-1", })), @@ -316,7 +330,7 @@ var _ = Describe("CreateMachine", func() { HaveField("ImageRef", BeEmpty()), HaveField("Volumes", ContainElement(&iri.VolumeStatus{ Name: "disk-1", - Handle: "libvirt-provider.ironcore.dev/empty-disk/disk-1", + Handle: "libvirt-provider.ironcore.dev/local-disk/disk-1", State: iri.VolumeState_VOLUME_ATTACHED, })), HaveField("NetworkInterfaces", ContainElement(&iri.NetworkInterfaceStatus{ @@ -337,26 +351,32 @@ var _ = Describe("CreateMachine", func() { }, }, Spec: &iri.MachineSpec{ - Power: iri.Power_POWER_ON, - Image: &iri.ImageSpec{ - Image: osImage, - }, + Power: iri.Power_POWER_ON, Class: machineClassx3xlarge, IgnitionData: ignitionData, Volumes: []*iri.Volume{ + { + Name: "rootdisk", + Device: "oda", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: osImage, + }, + }, + }, { Name: "disk-1", - EmptyDisk: &iri.EmptyDisk{ + LocalDisk: &iri.LocalDisk{ SizeBytes: emptyDiskSize, }, - Device: "oda", + Device: "odb", }, { Name: "disk-2", - EmptyDisk: &iri.EmptyDisk{ + LocalDisk: &iri.LocalDisk{ SizeBytes: emptyDiskSize, }, - Device: "odb", + Device: "odc", }, }, NetworkInterfaces: []*iri.NetworkInterface{ @@ -374,21 +394,29 @@ var _ = Describe("CreateMachine", func() { Expect(createResp).Should(SatisfyAll( HaveField("Machine.Metadata.Id", Not(BeEmpty())), HaveField("Machine.Spec.Power", iri.Power_POWER_ON), - HaveField("Machine.Spec.Image.Image", Equal(osImage)), HaveField("Machine.Spec.Class", machineClassx3xlarge), HaveField("Machine.Spec.IgnitionData", Equal(ignitionData)), HaveField("Machine.Spec.Volumes", ContainElements( &iri.Volume{ - Name: "disk-1", + Name: "rootdisk", Device: "oda", - EmptyDisk: &iri.EmptyDisk{ + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: osImage, + }, + }, + }, + &iri.Volume{ + Name: "disk-1", + Device: "odb", + LocalDisk: &iri.LocalDisk{ SizeBytes: emptyDiskSize, }, }, &iri.Volume{ Name: "disk-2", - Device: "odb", - EmptyDisk: &iri.EmptyDisk{ + Device: "odc", + LocalDisk: &iri.LocalDisk{ SizeBytes: emptyDiskSize, }, })), @@ -448,12 +476,12 @@ var _ = Describe("CreateMachine", func() { HaveField("Volumes", ContainElements( &iri.VolumeStatus{ Name: "disk-1", - Handle: "libvirt-provider.ironcore.dev/empty-disk/disk-1", + Handle: "libvirt-provider.ironcore.dev/local-disk/disk-1", State: iri.VolumeState_VOLUME_ATTACHED, }, &iri.VolumeStatus{ Name: "disk-2", - Handle: "libvirt-provider.ironcore.dev/empty-disk/disk-2", + Handle: "libvirt-provider.ironcore.dev/local-disk/disk-2", State: iri.VolumeState_VOLUME_ATTACHED, })), HaveField("NetworkInterfaces", ContainElement(&iri.NetworkInterfaceStatus{ diff --git a/internal/server/machine_delete_test.go b/internal/server/machine_delete_test.go index 05df1445..0ec01319 100644 --- a/internal/server/machine_delete_test.go +++ b/internal/server/machine_delete_test.go @@ -28,17 +28,23 @@ var _ = Describe("DeleteMachine", func() { }, Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, - Image: &iri.ImageSpec{ - Image: osImage, - }, Class: machineClassx3xlarge, Volumes: []*iri.Volume{ + { + Name: "rootdisk", + Device: "oda", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: osImage, + }, + }, + }, { Name: "disk-1", - EmptyDisk: &iri.EmptyDisk{ + LocalDisk: &iri.LocalDisk{ SizeBytes: emptyDiskSize, }, - Device: "oda", + Device: "odb", }, }, }, @@ -134,7 +140,7 @@ var _ = Describe("DeleteMachine", func() { Volumes: []*iri.Volume{ { Name: "disk-1", - EmptyDisk: &iri.EmptyDisk{ + LocalDisk: &iri.LocalDisk{ SizeBytes: emptyDiskSize, }, Device: "oda", diff --git a/internal/server/machine_list_test.go b/internal/server/machine_list_test.go index 646d536f..22e3cc4b 100644 --- a/internal/server/machine_list_test.go +++ b/internal/server/machine_list_test.go @@ -28,7 +28,7 @@ var _ = Describe("ListMachine", func() { Volumes: []*iri.Volume{ { Name: "disk-1", - EmptyDisk: &iri.EmptyDisk{ + LocalDisk: &iri.LocalDisk{ SizeBytes: 5368709120, }, Device: "oda", diff --git a/internal/server/machine_networkinterface_attach_test.go b/internal/server/machine_networkinterface_attach_test.go index 8872cf7d..906936ef 100644 --- a/internal/server/machine_networkinterface_attach_test.go +++ b/internal/server/machine_networkinterface_attach_test.go @@ -28,8 +28,16 @@ var _ = Describe("AttachNetworkInterface", func() { Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, Class: machineClassx2medium, - Image: &iri.ImageSpec{ - Image: osImage, + Volumes: []*iri.Volume{ + { + Name: "rootdisk", + Device: "oda", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: osImage, + }, + }, + }, }, }, }, diff --git a/internal/server/machine_networkinterface_detach_test.go b/internal/server/machine_networkinterface_detach_test.go index 4952ee13..b6a74438 100644 --- a/internal/server/machine_networkinterface_detach_test.go +++ b/internal/server/machine_networkinterface_detach_test.go @@ -28,8 +28,16 @@ var _ = Describe("DetachNetworkInterface", func() { Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, Class: machineClassx2medium, - Image: &iri.ImageSpec{ - Image: osImage, + Volumes: []*iri.Volume{ + { + Name: "rootdisk", + Device: "oda", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: osImage, + }, + }, + }, }, NetworkInterfaces: []*iri.NetworkInterface{ { diff --git a/internal/server/machine_volume_attach_test.go b/internal/server/machine_volume_attach_test.go index a4d65c5a..40b265d6 100644 --- a/internal/server/machine_volume_attach_test.go +++ b/internal/server/machine_volume_attach_test.go @@ -29,6 +29,17 @@ var _ = Describe("AttachVolume", func() { Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, Class: machineClassx3xlarge, + Volumes: []*iri.Volume{ + { + Name: "rootdisk", + Device: "oda", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: osImage, + }, + }, + }, + }, }, }, }) @@ -65,18 +76,18 @@ var _ = Describe("AttachVolume", func() { }).Should(Equal(libvirt.DomainRunning)) By("attaching empty disk to a machine") - attachEmptyDiskResp, err := machineClient.AttachVolume(ctx, &iri.AttachVolumeRequest{ + attachLocalDiskResp, err := machineClient.AttachVolume(ctx, &iri.AttachVolumeRequest{ MachineId: createResp.Machine.Metadata.Id, Volume: &iri.Volume{ Name: "disk-1", - EmptyDisk: &iri.EmptyDisk{ + LocalDisk: &iri.LocalDisk{ SizeBytes: 5368709120, }, - Device: "oda", + Device: "odb", }, }) Expect(err).NotTo(HaveOccurred()) - Expect(attachEmptyDiskResp).NotTo(BeNil()) + Expect(attachLocalDiskResp).NotTo(BeNil()) By("ensuring attached empty disk have been updated in machine status field") Eventually(func(g Gomega) *iri.MachineStatus { @@ -93,7 +104,7 @@ var _ = Describe("AttachVolume", func() { HaveField("Volumes", ContainElements( &iri.VolumeStatus{ Name: "disk-1", - Handle: "libvirt-provider.ironcore.dev/empty-disk/disk-1", + Handle: "libvirt-provider.ironcore.dev/local-disk/disk-1", State: iri.VolumeState_VOLUME_ATTACHED, })), HaveField("State", Equal(iri.MachineState_MACHINE_RUNNING)), @@ -104,7 +115,7 @@ var _ = Describe("AttachVolume", func() { MachineId: createResp.Machine.Metadata.Id, Volume: &iri.Volume{ Name: "volume-1", - Device: "odb", + Device: "odc", Connection: &iri.VolumeConnection{ Driver: "ceph", Handle: "dummy", @@ -132,9 +143,10 @@ var _ = Describe("AttachVolume", func() { g.Expect(domainXML.Unmarshal(domainXMLData)).Should(Succeed()) disks = domainXML.Devices.Disks return len(disks) - }).WithTimeout(2 * time.Minute).WithPolling(2 * time.Second).Should(Equal(2)) + }).WithTimeout(2 * time.Minute).WithPolling(2 * time.Second).Should(Equal(3)) Expect(disks[0].Serial).To(HavePrefix("oda")) Expect(disks[1].Serial).To(HavePrefix("odb")) + Expect(disks[2].Serial).To(HavePrefix("odc")) By("ensuring attached volume have been updated in machine status field") Eventually(func(g Gomega) *iri.MachineStatus { @@ -151,7 +163,7 @@ var _ = Describe("AttachVolume", func() { HaveField("Volumes", ContainElements( &iri.VolumeStatus{ Name: "disk-1", - Handle: "libvirt-provider.ironcore.dev/empty-disk/disk-1", + Handle: "libvirt-provider.ironcore.dev/local-disk/disk-1", State: iri.VolumeState_VOLUME_ATTACHED, }, &iri.VolumeStatus{ diff --git a/internal/server/machine_volume_detach_test.go b/internal/server/machine_volume_detach_test.go index b2385640..8942a513 100644 --- a/internal/server/machine_volume_detach_test.go +++ b/internal/server/machine_volume_detach_test.go @@ -28,28 +28,34 @@ var _ = Describe("DetachVolume", func() { }, Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, - Image: &iri.ImageSpec{ - Image: osImage, - }, Class: machineClassx3xlarge, Volumes: []*iri.Volume{ + { + Name: "rootdisk", + Device: "oda", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: osImage, + }, + }, + }, { Name: "disk-1", - EmptyDisk: &iri.EmptyDisk{ + LocalDisk: &iri.LocalDisk{ SizeBytes: emptyDiskSize, }, - Device: "oda", + Device: "odb", }, { Name: "disk-2", - EmptyDisk: &iri.EmptyDisk{ + LocalDisk: &iri.LocalDisk{ SizeBytes: emptyDiskSize, }, - Device: "odb", + Device: "odc", }, { Name: "volume-1", - Device: "odc", + Device: "odd", Connection: &iri.VolumeConnection{ Driver: "ceph", Handle: "dummy", @@ -113,14 +119,19 @@ var _ = Describe("DetachVolume", func() { return listResp.Machines[0].Status }).Should(SatisfyAll( HaveField("Volumes", ContainElements( + &iri.VolumeStatus{ + Name: "rootdisk", + Handle: "libvirt-provider.ironcore.dev/local-disk/rootdisk", + State: iri.VolumeState_VOLUME_ATTACHED, + }, &iri.VolumeStatus{ Name: "disk-1", - Handle: "libvirt-provider.ironcore.dev/empty-disk/disk-1", + Handle: "libvirt-provider.ironcore.dev/local-disk/disk-1", State: iri.VolumeState_VOLUME_ATTACHED, }, &iri.VolumeStatus{ Name: "disk-2", - Handle: "libvirt-provider.ironcore.dev/empty-disk/disk-2", + Handle: "libvirt-provider.ironcore.dev/local-disk/disk-2", State: iri.VolumeState_VOLUME_ATTACHED, }, &iri.VolumeStatus{ @@ -168,7 +179,7 @@ var _ = Describe("DetachVolume", func() { HaveField("Volumes", ContainElements( &iri.VolumeStatus{ Name: "disk-2", - Handle: "libvirt-provider.ironcore.dev/empty-disk/disk-2", + Handle: "libvirt-provider.ironcore.dev/local-disk/disk-2", State: iri.VolumeState_VOLUME_ATTACHED, })), HaveField("State", Equal(iri.MachineState_MACHINE_RUNNING)), diff --git a/internal/server/machine_volume_update_test.go b/internal/server/machine_volume_update_test.go index 0c39c647..a2f063b8 100644 --- a/internal/server/machine_volume_update_test.go +++ b/internal/server/machine_volume_update_test.go @@ -28,14 +28,20 @@ var _ = Describe("UpdateVolume", func() { }, Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, - Image: &iri.ImageSpec{ - Image: osImage, - }, Class: machineClassx3xlarge, Volumes: []*iri.Volume{ { - Name: "volume-1", + Name: "rootdisk", Device: "oda", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: osImage, + }, + }, + }, + { + Name: "volume-1", + Device: "odb", Connection: &iri.VolumeConnection{ Driver: "ceph", Handle: "dummy", @@ -117,14 +123,14 @@ var _ = Describe("UpdateVolume", func() { disks = domainXML.Devices.Disks return len(disks) }).Should(Equal(2)) - Expect(disks[0].Serial).To(HavePrefix("oda")) + Expect(disks[1].Serial).To(HavePrefix("odb")) By("updating machine volume") updateVolumeResp, err := machineClient.UpdateVolume(ctx, &iri.UpdateVolumeRequest{ MachineId: createResp.Machine.Metadata.Id, Volume: &iri.Volume{ Name: "volume-1", - Device: "oda", + Device: "odb", Connection: &iri.VolumeConnection{ Driver: "ceph", Handle: "dummy", @@ -153,10 +159,11 @@ var _ = Describe("UpdateVolume", func() { g.Expect(err).NotTo(HaveOccurred()) g.Expect(listResp.Machines).NotTo(BeEmpty()) g.Expect(listResp.Machines).Should(HaveLen(1)) - return listResp.Machines[0].Spec.Volumes[0] + g.Expect(listResp.Machines[0].Spec.Volumes).Should(HaveLen(2)) + return listResp.Machines[0].Spec.Volumes[1] }).Should(SatisfyAll( HaveField("Name", Equal("volume-1")), - HaveField("Device", Equal("oda")), + HaveField("Device", Equal("odb")), HaveField("Connection.EffectiveStorageBytes", Equal(resource.NewQuantity(2*1024*1024*1024, resource.BinarySI).Value())), )) })