diff --git a/api/compute/v1alpha1/machine_types.go b/api/compute/v1alpha1/machine_types.go index 23a1b05b4..1cf681a0e 100644 --- a/api/compute/v1alpha1/machine_types.go +++ b/api/compute/v1alpha1/machine_types.go @@ -23,6 +23,7 @@ type MachineSpec struct { // Power is the desired machine power state. // Defaults to PowerOn. Power Power `json:"power,omitempty"` + // Deprecated: Use LocalDisk to provide a bootable disk // Image is the optional URL providing the operating system image of the machine. // +optional Image string `json:"image,omitempty"` @@ -106,8 +107,11 @@ type Volume struct { type VolumeSource struct { // VolumeRef instructs to use the specified Volume as source for the attachment. VolumeRef *corev1.LocalObjectReference `json:"volumeRef,omitempty"` + // Deprecated: Use LocalDisk instead // EmptyDisk instructs to use a Volume offered by the machine pool provider. EmptyDisk *EmptyDiskVolumeSource `json:"emptyDisk,omitempty"` + // LocalDisk instructs to use a Volume offered by the machine pool provider. + LocalDisk *LocalDiskVolumeSource `json:"localDisk,omitempty"` // Ephemeral instructs to create an ephemeral (i.e. coupled to the lifetime of the surrounding object) // Volume to use. Ephemeral *EphemeralVolumeSource `json:"ephemeral,omitempty"` @@ -122,6 +126,18 @@ type EmptyDiskVolumeSource struct { SizeLimit *resource.Quantity `json:"sizeLimit,omitempty"` } +// LocalDiskVolumeSource is a volume that's offered by the machine pool provider. +// Usually ephemeral (i.e. deleted when the surrounding entity is deleted), with +// varying performance characteristics. Potentially not recoverable. +type LocalDiskVolumeSource struct { + // SizeLimit is the total amount of local storage required for this LocalDisk volume. + // The default is nil which means that the limit is undefined. + SizeLimit *resource.Quantity `json:"sizeLimit,omitempty"` + // Image is the optional URL providing the operating system image of the machine. + // +optional + Image string `json:"image,omitempty"` +} + // NetworkInterfaceStatus reports the status of a NetworkInterfaceSource. type NetworkInterfaceStatus struct { // Name is the name of the NetworkInterface to whom the status belongs to. diff --git a/api/compute/v1alpha1/zz_generated.deepcopy.go b/api/compute/v1alpha1/zz_generated.deepcopy.go index 7910e66dd..c62bdb9f6 100644 --- a/api/compute/v1alpha1/zz_generated.deepcopy.go +++ b/api/compute/v1alpha1/zz_generated.deepcopy.go @@ -112,6 +112,27 @@ func (in *EphemeralVolumeSource) DeepCopy() *EphemeralVolumeSource { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalDiskVolumeSource) DeepCopyInto(out *LocalDiskVolumeSource) { + *out = *in + if in.SizeLimit != nil { + in, out := &in.SizeLimit, &out.SizeLimit + x := (*in).DeepCopy() + *out = &x + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalDiskVolumeSource. +func (in *LocalDiskVolumeSource) DeepCopy() *LocalDiskVolumeSource { + if in == nil { + return nil + } + out := new(LocalDiskVolumeSource) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Machine) DeepCopyInto(out *Machine) { *out = *in @@ -636,6 +657,11 @@ func (in *VolumeSource) DeepCopyInto(out *VolumeSource) { *out = new(EmptyDiskVolumeSource) (*in).DeepCopyInto(*out) } + if in.LocalDisk != nil { + in, out := &in.LocalDisk, &out.LocalDisk + *out = new(LocalDiskVolumeSource) + (*in).DeepCopyInto(*out) + } if in.Ephemeral != nil { in, out := &in.Ephemeral, &out.Ephemeral *out = new(EphemeralVolumeSource) diff --git a/broker/machinebroker/server/event_list_test.go b/broker/machinebroker/server/event_list_test.go index 101e6bb8c..2eef19ee8 100644 --- a/broker/machinebroker/server/event_list_test.go +++ b/broker/machinebroker/server/event_list_test.go @@ -44,10 +44,15 @@ var _ = Describe("ListEvents", func() { }, Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, - Image: &iri.ImageSpec{ - Image: "example.org/foo:latest", - }, Class: machineClass.Name, + Volumes: []*iri.Volume{{ + Name: "root", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: "example.org/foo:latest", + }, + }, + }}, NetworkInterfaces: []*iri.NetworkInterface{ { Name: "primary-nic", diff --git a/broker/machinebroker/server/machine.go b/broker/machinebroker/server/machine.go index 9149c4d3d..b280566db 100644 --- a/broker/machinebroker/server/machine.go +++ b/broker/machinebroker/server/machine.go @@ -115,7 +115,7 @@ func (s *Server) convertIronCoreVolume( ) (*iri.Volume, error) { var ( connection *iri.VolumeConnection - emptyDisk *iri.EmptyDisk + localDisk *iri.LocalDisk ) switch { case ironcoreMachineVolume.VolumeRef != nil: @@ -137,22 +137,31 @@ func (s *Server) convertIronCoreVolume( EffectiveStorageBytes: effectiveStorageBytes, } } - case ironcoreMachineVolume.EmptyDisk != nil: + case ironcoreMachineVolume.LocalDisk != nil: var sizeBytes int64 - if sizeLimit := ironcoreMachineVolume.EmptyDisk.SizeLimit; sizeLimit != nil { + if sizeLimit := ironcoreMachineVolume.LocalDisk.SizeLimit; sizeLimit != nil { sizeBytes = sizeLimit.Value() } - emptyDisk = &iri.EmptyDisk{ + + var imageSpec *iri.ImageSpec + if image := ironcoreMachineVolume.LocalDisk.Image; image != "" { + imageSpec = &iri.ImageSpec{ + Image: ironcoreMachineVolume.LocalDisk.Image, + } + } + + localDisk = &iri.LocalDisk{ SizeBytes: sizeBytes, + Image: imageSpec, } default: - return nil, fmt.Errorf("machine volume %#v does neither specify volume ref nor empty disk", ironcoreMachineVolume) + return nil, fmt.Errorf("machine volume %#v does neither specify volume ref nor local disk", ironcoreMachineVolume) } return &iri.Volume{ Name: ironcoreMachineVolume.Name, Device: *ironcoreMachineVolume.Device, - EmptyDisk: emptyDisk, + LocalDisk: localDisk, Connection: connection, }, nil } @@ -195,13 +204,6 @@ func (s *Server) convertAggregateIronCoreMachine(aggIronCoreMachine *AggregateIr return nil, fmt.Errorf("error converting power state: %w", err) } - var imageSpec *iri.ImageSpec - if image := aggIronCoreMachine.Machine.Spec.Image; image != "" { - imageSpec = &iri.ImageSpec{ - Image: image, - } - } - volumes := make([]*iri.Volume, len(aggIronCoreMachine.Machine.Spec.Volumes)) for i, ironcoreMachineVolume := range aggIronCoreMachine.Machine.Spec.Volumes { ironcoreVolume := aggIronCoreMachine.Volumes[ironcoreMachineVolume.Name] @@ -253,7 +255,6 @@ func (s *Server) convertAggregateIronCoreMachine(aggIronCoreMachine *AggregateIr Metadata: metadata, Spec: &iri.MachineSpec{ Power: power, - Image: imageSpec, Class: aggIronCoreMachine.Machine.Spec.MachineClassRef.Name, IgnitionData: ignitionData, Volumes: volumes, diff --git a/broker/machinebroker/server/machine_create.go b/broker/machinebroker/server/machine_create.go index 084db6205..3632c9077 100644 --- a/broker/machinebroker/server/machine_create.go +++ b/broker/machinebroker/server/machine_create.go @@ -28,7 +28,6 @@ type IronCoreMachineConfig struct { Annotations map[string]string Power computev1alpha1.Power MachineClassName string - Image string IgnitionData []byte NetworkInterfaceConfigs []*IronCoreNetworkInterfaceConfig VolumeConfigs []*IronCoreVolumeConfig @@ -75,11 +74,6 @@ func (s *Server) getIronCoreMachineConfig(machine *iri.Machine) (*IronCoreMachin return nil, err } - var ironcoreImage string - if image := machine.Spec.Image; image != nil { - ironcoreImage = image.Image - } - ironcoreNicCfgs := make([]*IronCoreNetworkInterfaceConfig, len(machine.Spec.NetworkInterfaces)) for i, nic := range machine.Spec.NetworkInterfaces { ironcoreNicCfg, err := s.getIronCoreNetworkInterfaceConfig(nic) @@ -116,7 +110,6 @@ func (s *Server) getIronCoreMachineConfig(machine *iri.Machine) (*IronCoreMachin Annotations: annotations, Power: ironcorePower, MachineClassName: machine.Spec.Class, - Image: ironcoreImage, IgnitionData: machine.Spec.IgnitionData, NetworkInterfaceConfigs: ironcoreNicCfgs, VolumeConfigs: ironcoreVolumeCfgs, @@ -199,7 +192,6 @@ func (s *Server) createIronCoreMachine( MachinePoolSelector: s.cluster.MachinePoolSelector(), MachinePoolRef: s.ironcoreMachinePoolRef(), Power: cfg.Power, - Image: cfg.Image, ImagePullSecretRef: nil, // TODO: Specify as soon as available. NetworkInterfaces: ironcoreMachineNics, Volumes: ironcoreMachineVolumes, diff --git a/broker/machinebroker/server/machine_create_test.go b/broker/machinebroker/server/machine_create_test.go index f4f3e0498..eaabc8721 100644 --- a/broker/machinebroker/server/machine_create_test.go +++ b/broker/machinebroker/server/machine_create_test.go @@ -31,10 +31,15 @@ var _ = Describe("CreateMachine", func() { }, Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, - Image: &iri.ImageSpec{ - Image: "example.org/foo:latest", - }, Class: machineClass.Name, + Volumes: []*iri.Volume{{ + Name: "root", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: "example.org/foo:latest", + }, + }, + }}, }, }, }) @@ -64,7 +69,6 @@ var _ = Describe("CreateMachine", func() { machinebrokerv1alpha1.LabelsAnnotation: encodedIRILabels, })) Expect(ironcoreMachine.Spec.Power).To(Equal(computev1alpha1.PowerOn)) - Expect(ironcoreMachine.Spec.Image).To(Equal("example.org/foo:latest")) Expect(ironcoreMachine.Spec.MachineClassRef.Name).To(Equal(machineClass.Name)) }) }) diff --git a/broker/machinebroker/server/machine_delete_test.go b/broker/machinebroker/server/machine_delete_test.go index 0928a45dc..6c8d5f3a1 100644 --- a/broker/machinebroker/server/machine_delete_test.go +++ b/broker/machinebroker/server/machine_delete_test.go @@ -31,10 +31,8 @@ var _ = Describe("DeleteMachine", func() { }, Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, - Image: &iri.ImageSpec{ - Image: "example.org/foo:latest", - }, Class: machineClass.Name, + NetworkInterfaces: []*iri.NetworkInterface{ { Name: "primary-nic", @@ -43,6 +41,14 @@ var _ = Describe("DeleteMachine", func() { }, }, Volumes: []*iri.Volume{ + { + Name: "root", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: "example.org/foo:latest", + }, + }, + }, { Name: "primary-volume", Device: "oda", diff --git a/broker/machinebroker/server/machine_list_test.go b/broker/machinebroker/server/machine_list_test.go index 435ba238d..e180b608a 100644 --- a/broker/machinebroker/server/machine_list_test.go +++ b/broker/machinebroker/server/machine_list_test.go @@ -30,9 +30,6 @@ var _ = Describe("ListMachines", func() { }, Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, - Image: &iri.ImageSpec{ - Image: "example.org/foo:latest", - }, Class: machineClass.Name, NetworkInterfaces: []*iri.NetworkInterface{ { @@ -41,7 +38,15 @@ var _ = Describe("ListMachines", func() { Ips: []string{"10.0.0.1"}, }, }, - Volumes: []*iri.Volume{ + + Volumes: []*iri.Volume{{ + Name: "root", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: "example.org/foo:latest", + }, + }, + }, { Name: "primary-volume", Device: "oda", diff --git a/broker/machinebroker/server/machine_networkinterface_attach_test.go b/broker/machinebroker/server/machine_networkinterface_attach_test.go index 0aa5606fb..5cd09d7ba 100644 --- a/broker/machinebroker/server/machine_networkinterface_attach_test.go +++ b/broker/machinebroker/server/machine_networkinterface_attach_test.go @@ -31,10 +31,15 @@ var _ = Describe("AttachNetworkInterface", func() { Machine: &iri.Machine{ Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, - Image: &iri.ImageSpec{ - Image: "example.org/foo:latest", - }, Class: machineClass.Name, + Volumes: []*iri.Volume{{ + Name: "root", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: "example.org/foo:latest", + }, + }, + }}, }, }, }) @@ -116,9 +121,14 @@ var _ = Describe("AttachNetworkInterface", func() { Machine: &iri.Machine{ Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, - Image: &iri.ImageSpec{ - Image: "example.org/foo:latest", - }, + Volumes: []*iri.Volume{{ + Name: "root", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: "example.org/foo:latest", + }, + }, + }}, Class: machineClass.Name, }, }, diff --git a/broker/machinebroker/server/machine_networkinterface_detach_test.go b/broker/machinebroker/server/machine_networkinterface_detach_test.go index 27825429a..daba0000a 100644 --- a/broker/machinebroker/server/machine_networkinterface_detach_test.go +++ b/broker/machinebroker/server/machine_networkinterface_detach_test.go @@ -23,9 +23,14 @@ var _ = Describe("DetachNetworkInterface", func() { Machine: &iri.Machine{ Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, - Image: &iri.ImageSpec{ - Image: "example.org/foo:latest", - }, + Volumes: []*iri.Volume{{ + Name: "root", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: "example.org/foo:latest", + }, + }, + }}, Class: machineClass.Name, NetworkInterfaces: []*iri.NetworkInterface{ { diff --git a/broker/machinebroker/server/machine_volume_attach.go b/broker/machinebroker/server/machine_volume_attach.go index bad2ceedd..a0834e111 100644 --- a/broker/machinebroker/server/machine_volume_attach.go +++ b/broker/machinebroker/server/machine_volume_attach.go @@ -30,13 +30,14 @@ import ( type IronCoreVolumeConfig struct { Name string Device string - EmptyDisk *IronCoreVolumeEmptyDiskConfig + LocalDisk *IronCoreVolumeLocalDiskConfig Remote *IronCoreVolumeRemoteConfig Labels map[string]string } -type IronCoreVolumeEmptyDiskConfig struct { +type IronCoreVolumeLocalDiskConfig struct { SizeLimit *resource.Quantity + Image string } type IronCoreVolumeRemoteConfig struct { @@ -50,17 +51,18 @@ type IronCoreVolumeRemoteConfig struct { func (s *Server) getIronCoreVolumeConfig(volume *iri.Volume) (*IronCoreVolumeConfig, error) { var ( - emptyDisk *IronCoreVolumeEmptyDiskConfig + localDisk *IronCoreVolumeLocalDiskConfig remote *IronCoreVolumeRemoteConfig ) switch { - case volume.EmptyDisk != nil: + case volume.LocalDisk != nil: var sizeLimit *resource.Quantity - if sizeBytes := volume.EmptyDisk.SizeBytes; sizeBytes > 0 { + if sizeBytes := volume.LocalDisk.SizeBytes; sizeBytes > 0 { sizeLimit = resource.NewQuantity(sizeBytes, resource.DecimalSI) } - emptyDisk = &IronCoreVolumeEmptyDiskConfig{ + localDisk = &IronCoreVolumeLocalDiskConfig{ SizeLimit: sizeLimit, + Image: volume.LocalDisk.Image.Image, } case volume.Connection != nil: remote = &IronCoreVolumeRemoteConfig{ @@ -81,7 +83,7 @@ func (s *Server) getIronCoreVolumeConfig(volume *iri.Volume) (*IronCoreVolumeCon return &IronCoreVolumeConfig{ Name: volume.Name, Device: volume.Device, - EmptyDisk: emptyDisk, + LocalDisk: localDisk, Remote: remote, Labels: labels, }, nil @@ -236,9 +238,10 @@ func (s *Server) createIronCoreVolume( AccessSecret: accessSecret, } ironcoreVolumeSrc.VolumeRef = &corev1.LocalObjectReference{Name: ironcoreVolume.Name} - case cfg.EmptyDisk != nil: - ironcoreVolumeSrc.EmptyDisk = &computev1alpha1.EmptyDiskVolumeSource{ - SizeLimit: cfg.EmptyDisk.SizeLimit, + case cfg.LocalDisk != nil: + ironcoreVolumeSrc.LocalDisk = &computev1alpha1.LocalDiskVolumeSource{ + SizeLimit: cfg.LocalDisk.SizeLimit, + Image: cfg.LocalDisk.Image, } } return &computev1alpha1.Volume{ diff --git a/broker/machinebroker/server/machine_volume_attach_test.go b/broker/machinebroker/server/machine_volume_attach_test.go index 81c06abb0..42c9cb95a 100644 --- a/broker/machinebroker/server/machine_volume_attach_test.go +++ b/broker/machinebroker/server/machine_volume_attach_test.go @@ -29,9 +29,15 @@ var _ = Describe("AttachVolume", func() { Machine: &iri.Machine{ Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, - Image: &iri.ImageSpec{ - Image: "example.org/foo:latest", - }, + Volumes: []*iri.Volume{{ + Name: "root", + Device: "oda", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: "example.org/foo:latest", + }, + }, + }}, Class: machineClass.Name, }, }, @@ -47,7 +53,7 @@ var _ = Describe("AttachVolume", func() { MachineId: machineID, Volume: &iri.Volume{ Name: "my-volume", - Device: "oda", + Device: "odb", Connection: &iri.VolumeConnection{ Driver: "ceph", Handle: "mycephvolume", @@ -69,9 +75,9 @@ var _ = Describe("AttachVolume", func() { Expect(k8sClient.Get(ctx, ironcoreMachineKey, ironcoreMachine)).To(Succeed()) By("inspecting the ironcore machine's volumes") - Expect(ironcoreMachine.Spec.Volumes).To(ConsistOf(MatchAllFields(Fields{ + Expect(ironcoreMachine.Spec.Volumes).To(ContainElement(MatchAllFields(Fields{ "Name": Equal("my-volume"), - "Device": PointTo(Equal("oda")), + "Device": PointTo(Equal("odb")), "VolumeSource": MatchFields(IgnoreExtras, Fields{ "VolumeRef": PointTo(MatchAllFields(Fields{ "Name": Not(BeEmpty()), @@ -81,7 +87,7 @@ var _ = Describe("AttachVolume", func() { By("getting the corresponding ironcore volume") volume := &storagev1alpha1.Volume{} - volumeName := ironcoreMachine.Spec.Volumes[0].VolumeRef.Name + volumeName := ironcoreMachine.Spec.Volumes[1].VolumeRef.Name volumeKey := client.ObjectKey{Namespace: ns.Name, Name: volumeName} Expect(k8sClient.Get(ctx, volumeKey, volume)).To(Succeed()) @@ -124,9 +130,15 @@ var _ = Describe("AttachVolume", func() { Machine: &iri.Machine{ Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, - Image: &iri.ImageSpec{ - Image: "example.org/foo:latest", - }, + Volumes: []*iri.Volume{{ + Name: "root", + Device: "oda", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: "example.org/foo:latest", + }, + }, + }}, Class: machineClass.Name, }, }, @@ -139,7 +151,7 @@ var _ = Describe("AttachVolume", func() { MachineId: machineID, Volume: &iri.Volume{ Name: "my-volume", - Device: "oda", + Device: "odb", Connection: &iri.VolumeConnection{ Driver: "ceph", Handle: "mycephvolume", @@ -162,9 +174,9 @@ var _ = Describe("AttachVolume", func() { Expect(k8sClient.Get(ctx, ironcoreMachineKey, ironcoreMachine)).To(Succeed()) By("inspecting the ironcore machine's volumes") - Expect(ironcoreMachine.Spec.Volumes).To(ConsistOf(MatchAllFields(Fields{ + Expect(ironcoreMachine.Spec.Volumes).To(ContainElement(MatchAllFields(Fields{ "Name": Equal("my-volume"), - "Device": PointTo(Equal("oda")), + "Device": PointTo(Equal("odb")), "VolumeSource": MatchFields(IgnoreExtras, Fields{ "VolumeRef": PointTo(MatchAllFields(Fields{ "Name": Not(BeEmpty()), @@ -174,7 +186,7 @@ var _ = Describe("AttachVolume", func() { By("getting the corresponding ironcore volume") volume := &storagev1alpha1.Volume{} - volumeName := ironcoreMachine.Spec.Volumes[0].VolumeRef.Name + volumeName := ironcoreMachine.Spec.Volumes[1].VolumeRef.Name volumeKey := client.ObjectKey{Namespace: ns.Name, Name: volumeName} Expect(k8sClient.Get(ctx, volumeKey, volume)).To(Succeed()) diff --git a/broker/machinebroker/server/machine_volume_detach.go b/broker/machinebroker/server/machine_volume_detach.go index 81972f39e..706bd6dc3 100644 --- a/broker/machinebroker/server/machine_volume_detach.go +++ b/broker/machinebroker/server/machine_volume_detach.go @@ -55,8 +55,8 @@ func (s *Server) DetachVolume(ctx context.Context, req *iri.DetachVolumeRequest) if err := s.cluster.Client().Delete(ctx, ironcoreVolume); client.IgnoreNotFound(err) != nil { return nil, fmt.Errorf("error deleting ironcore volume %s: %w", ironcoreVolumeName, err) } - case ironcoreMachineVolume.EmptyDisk != nil: - log.V(1).Info("No need to clean up empty disk") + case ironcoreMachineVolume.LocalDisk != nil: + log.V(1).Info("No need to clean up local disk") default: return nil, fmt.Errorf("unrecognized ironcore machine volume %#v", ironcoreMachineVolume) } diff --git a/broker/machinebroker/server/machine_volume_detach_test.go b/broker/machinebroker/server/machine_volume_detach_test.go index 15edb54d4..9d970bd83 100644 --- a/broker/machinebroker/server/machine_volume_detach_test.go +++ b/broker/machinebroker/server/machine_volume_detach_test.go @@ -11,6 +11,7 @@ import ( . "github.com/onsi/gomega" . "github.com/onsi/gomega/gstruct" corev1 "k8s.io/api/core/v1" + "k8s.io/utils/ptr" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -24,14 +25,19 @@ var _ = Describe("DetachVolume", func() { Machine: &iri.Machine{ Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, - Image: &iri.ImageSpec{ - Image: "example.org/foo:latest", - }, Class: machineClass.Name, - Volumes: []*iri.Volume{ + Volumes: []*iri.Volume{{ + Name: "root", + Device: "oda", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: "example.org/foo:latest", + }, + }, + }, { Name: "my-volume", - Device: "oda", + Device: "odb", Connection: &iri.VolumeConnection{ Driver: "ceph", Handle: "mycephvolume", @@ -62,7 +68,15 @@ var _ = Describe("DetachVolume", func() { Expect(k8sClient.Get(ctx, ironcoreMachineKey, ironcoreMachine)).To(Succeed()) By("inspecting the ironcore machine's volumes") - Expect(ironcoreMachine.Spec.Volumes).To(BeEmpty()) + Expect(ironcoreMachine.Spec.Volumes).To(ContainElement(computev1alpha1.Volume{ + Name: "root", + Device: ptr.To("oda"), + VolumeSource: computev1alpha1.VolumeSource{ + LocalDisk: &computev1alpha1.LocalDiskVolumeSource{ + Image: "example.org/foo:latest", + }, + }, + })) By("listing for any ironcore volume in the namespace") volumeList := &storagev1alpha1.VolumeList{} diff --git a/broker/machinebroker/server/machine_volume_update_test.go b/broker/machinebroker/server/machine_volume_update_test.go index 284bde254..eb42d174c 100644 --- a/broker/machinebroker/server/machine_volume_update_test.go +++ b/broker/machinebroker/server/machine_volume_update_test.go @@ -30,14 +30,20 @@ var _ = Describe("UpdateVolume", func() { Machine: &iri.Machine{ Spec: &iri.MachineSpec{ Power: iri.Power_POWER_ON, - Image: &iri.ImageSpec{ - Image: "example.org/foo:latest", - }, Class: machineClass.Name, - Volumes: []*iri.Volume{ + + Volumes: []*iri.Volume{{ + Name: "root", + Device: "oda", + LocalDisk: &iri.LocalDisk{ + Image: &iri.ImageSpec{ + Image: "example.org/foo:latest", + }, + }, + }, { Name: "primary", - Device: "oda", + Device: "odb", Connection: &iri.VolumeConnection{ Driver: "test", Handle: "testhandle", @@ -58,7 +64,7 @@ var _ = Describe("UpdateVolume", func() { By("getting the corresponding ironcore volume") volume := &storagev1alpha1.Volume{} - volumeName := ironcoreMachine.Spec.Volumes[0].VolumeRef.Name + volumeName := ironcoreMachine.Spec.Volumes[1].VolumeRef.Name volumeKey := client.ObjectKey{Namespace: ns.Name, Name: volumeName} Expect(k8sClient.Get(ctx, volumeKey, volume)).To(Succeed()) @@ -67,7 +73,7 @@ var _ = Describe("UpdateVolume", func() { MachineId: machineID, Volume: &iri.Volume{ Name: "primary", - Device: "oda", + Device: "odb", Connection: &iri.VolumeConnection{ Driver: "test", Handle: "testhandle", @@ -90,9 +96,9 @@ var _ = Describe("UpdateVolume", func() { )) By("verifying machine volume is updated") - Eventually(Object(ironcoreMachine)).Should(HaveField("Spec.Volumes", ConsistOf(MatchFields(IgnoreExtras, Fields{ + Eventually(Object(ironcoreMachine)).Should(HaveField("Spec.Volumes", ContainElement(MatchFields(IgnoreExtras, Fields{ "Name": Equal("primary"), - "Device": Equal(ptr.To("oda")), + "Device": Equal(ptr.To("odb")), "VolumeSource": Equal(computev1alpha1.VolumeSource{ VolumeRef: &corev1.LocalObjectReference{Name: volume.Name}, }), diff --git a/client-go/applyconfigurations/compute/v1alpha1/localdiskvolumesource.go b/client-go/applyconfigurations/compute/v1alpha1/localdiskvolumesource.go new file mode 100644 index 000000000..698ac88f7 --- /dev/null +++ b/client-go/applyconfigurations/compute/v1alpha1/localdiskvolumesource.go @@ -0,0 +1,39 @@ +// SPDX-FileCopyrightText: 2025 SAP SE or an SAP affiliate company and IronCore contributors +// SPDX-License-Identifier: Apache-2.0 + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1alpha1 + +import ( + resource "k8s.io/apimachinery/pkg/api/resource" +) + +// LocalDiskVolumeSourceApplyConfiguration represents a declarative configuration of the LocalDiskVolumeSource type for use +// with apply. +type LocalDiskVolumeSourceApplyConfiguration struct { + SizeLimit *resource.Quantity `json:"sizeLimit,omitempty"` + Image *string `json:"image,omitempty"` +} + +// LocalDiskVolumeSourceApplyConfiguration constructs a declarative configuration of the LocalDiskVolumeSource type for use with +// apply. +func LocalDiskVolumeSource() *LocalDiskVolumeSourceApplyConfiguration { + return &LocalDiskVolumeSourceApplyConfiguration{} +} + +// WithSizeLimit sets the SizeLimit field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the SizeLimit field is set to the value of the last call. +func (b *LocalDiskVolumeSourceApplyConfiguration) WithSizeLimit(value resource.Quantity) *LocalDiskVolumeSourceApplyConfiguration { + b.SizeLimit = &value + return b +} + +// WithImage sets the Image field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Image field is set to the value of the last call. +func (b *LocalDiskVolumeSourceApplyConfiguration) WithImage(value string) *LocalDiskVolumeSourceApplyConfiguration { + b.Image = &value + return b +} diff --git a/client-go/applyconfigurations/compute/v1alpha1/volume.go b/client-go/applyconfigurations/compute/v1alpha1/volume.go index 01a6aa691..ba13772e4 100644 --- a/client-go/applyconfigurations/compute/v1alpha1/volume.go +++ b/client-go/applyconfigurations/compute/v1alpha1/volume.go @@ -55,6 +55,14 @@ func (b *VolumeApplyConfiguration) WithEmptyDisk(value *EmptyDiskVolumeSourceApp return b } +// WithLocalDisk sets the LocalDisk field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LocalDisk field is set to the value of the last call. +func (b *VolumeApplyConfiguration) WithLocalDisk(value *LocalDiskVolumeSourceApplyConfiguration) *VolumeApplyConfiguration { + b.VolumeSourceApplyConfiguration.LocalDisk = value + return b +} + // WithEphemeral sets the Ephemeral field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Ephemeral field is set to the value of the last call. diff --git a/client-go/applyconfigurations/compute/v1alpha1/volumesource.go b/client-go/applyconfigurations/compute/v1alpha1/volumesource.go index bc4e6a58e..46ee0aee2 100644 --- a/client-go/applyconfigurations/compute/v1alpha1/volumesource.go +++ b/client-go/applyconfigurations/compute/v1alpha1/volumesource.go @@ -14,6 +14,7 @@ import ( type VolumeSourceApplyConfiguration struct { VolumeRef *v1.LocalObjectReference `json:"volumeRef,omitempty"` EmptyDisk *EmptyDiskVolumeSourceApplyConfiguration `json:"emptyDisk,omitempty"` + LocalDisk *LocalDiskVolumeSourceApplyConfiguration `json:"localDisk,omitempty"` Ephemeral *EphemeralVolumeSourceApplyConfiguration `json:"ephemeral,omitempty"` } @@ -39,6 +40,14 @@ func (b *VolumeSourceApplyConfiguration) WithEmptyDisk(value *EmptyDiskVolumeSou return b } +// WithLocalDisk sets the LocalDisk field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the LocalDisk field is set to the value of the last call. +func (b *VolumeSourceApplyConfiguration) WithLocalDisk(value *LocalDiskVolumeSourceApplyConfiguration) *VolumeSourceApplyConfiguration { + b.LocalDisk = value + return b +} + // WithEphemeral sets the Ephemeral field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Ephemeral field is set to the value of the last call. diff --git a/client-go/applyconfigurations/internal/internal.go b/client-go/applyconfigurations/internal/internal.go index 4633c2af5..caceb5113 100644 --- a/client-go/applyconfigurations/internal/internal.go +++ b/client-go/applyconfigurations/internal/internal.go @@ -121,6 +121,15 @@ var schemaYAML = typed.YAMLObject(`types: - name: volumeTemplate type: namedType: com.github.ironcore-dev.ironcore.api.storage.v1alpha1.VolumeTemplateSpec +- name: com.github.ironcore-dev.ironcore.api.compute.v1alpha1.LocalDiskVolumeSource + map: + fields: + - name: image + type: + scalar: string + - name: sizeLimit + type: + namedType: io.k8s.apimachinery.pkg.api.resource.Quantity - name: com.github.ironcore-dev.ironcore.api.compute.v1alpha1.Machine map: fields: @@ -401,6 +410,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: ephemeral type: namedType: com.github.ironcore-dev.ironcore.api.compute.v1alpha1.EphemeralVolumeSource + - name: localDisk + type: + namedType: com.github.ironcore-dev.ironcore.api.compute.v1alpha1.LocalDiskVolumeSource - name: name type: scalar: string diff --git a/client-go/applyconfigurations/utils.go b/client-go/applyconfigurations/utils.go index 6e1ecc577..6683f67a6 100644 --- a/client-go/applyconfigurations/utils.go +++ b/client-go/applyconfigurations/utils.go @@ -37,6 +37,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &computev1alpha1.EphemeralNetworkInterfaceSourceApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("EphemeralVolumeSource"): return &computev1alpha1.EphemeralVolumeSourceApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("LocalDiskVolumeSource"): + return &computev1alpha1.LocalDiskVolumeSourceApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Machine"): return &computev1alpha1.MachineApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("MachineClass"): diff --git a/client-go/openapi/zz_generated.openapi.go b/client-go/openapi/zz_generated.openapi.go index eeff3bb00..aad1b066f 100644 --- a/client-go/openapi/zz_generated.openapi.go +++ b/client-go/openapi/zz_generated.openapi.go @@ -33,6 +33,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.EmptyDiskVolumeSource": schema_ironcore_api_compute_v1alpha1_EmptyDiskVolumeSource(ref), "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.EphemeralNetworkInterfaceSource": schema_ironcore_api_compute_v1alpha1_EphemeralNetworkInterfaceSource(ref), "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.EphemeralVolumeSource": schema_ironcore_api_compute_v1alpha1_EphemeralVolumeSource(ref), + "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.LocalDiskVolumeSource": schema_ironcore_api_compute_v1alpha1_LocalDiskVolumeSource(ref), "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.Machine": schema_ironcore_api_compute_v1alpha1_Machine(ref), "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.MachineClass": schema_ironcore_api_compute_v1alpha1_MachineClass(ref), "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.MachineClassList": schema_ironcore_api_compute_v1alpha1_MachineClassList(ref), @@ -835,6 +836,34 @@ func schema_ironcore_api_compute_v1alpha1_EphemeralVolumeSource(ref common.Refer } } +func schema_ironcore_api_compute_v1alpha1_LocalDiskVolumeSource(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "LocalDiskVolumeSource is a volume that's offered by the machine pool provider. Usually ephemeral (i.e. deleted when the surrounding entity is deleted), with varying performance characteristics. Potentially not recoverable.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "sizeLimit": { + SchemaProps: spec.SchemaProps{ + Description: "SizeLimit is the total amount of local storage required for this LocalDisk volume. The default is nil which means that the limit is undefined.", + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + "image": { + SchemaProps: spec.SchemaProps{ + Description: "Image is the optional URL providing the operating system image of the machine.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + } +} + func schema_ironcore_api_compute_v1alpha1_Machine(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -1446,7 +1475,7 @@ func schema_ironcore_api_compute_v1alpha1_MachineSpec(ref common.ReferenceCallba }, "image": { SchemaProps: spec.SchemaProps{ - Description: "Image is the optional URL providing the operating system image of the machine.", + Description: "Deprecated: Use LocalDisk to provide a bootable disk Image is the optional URL providing the operating system image of the machine.", Type: []string{"string"}, Format: "", }, @@ -1754,10 +1783,16 @@ func schema_ironcore_api_compute_v1alpha1_Volume(ref common.ReferenceCallback) c }, "emptyDisk": { SchemaProps: spec.SchemaProps{ - Description: "EmptyDisk instructs to use a Volume offered by the machine pool provider.", + Description: "Deprecated: Use LocalDisk instead EmptyDisk instructs to use a Volume offered by the machine pool provider.", Ref: ref("github.com/ironcore-dev/ironcore/api/compute/v1alpha1.EmptyDiskVolumeSource"), }, }, + "localDisk": { + SchemaProps: spec.SchemaProps{ + Description: "LocalDisk instructs to use a Volume offered by the machine pool provider.", + Ref: ref("github.com/ironcore-dev/ironcore/api/compute/v1alpha1.LocalDiskVolumeSource"), + }, + }, "ephemeral": { SchemaProps: spec.SchemaProps{ Description: "Ephemeral instructs to create an ephemeral (i.e. coupled to the lifetime of the surrounding object) Volume to use.", @@ -1769,7 +1804,7 @@ func schema_ironcore_api_compute_v1alpha1_Volume(ref common.ReferenceCallback) c }, }, Dependencies: []string{ - "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.EmptyDiskVolumeSource", "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.EphemeralVolumeSource", "k8s.io/api/core/v1.LocalObjectReference"}, + "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.EmptyDiskVolumeSource", "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.EphemeralVolumeSource", "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.LocalDiskVolumeSource", "k8s.io/api/core/v1.LocalObjectReference"}, } } @@ -1788,10 +1823,16 @@ func schema_ironcore_api_compute_v1alpha1_VolumeSource(ref common.ReferenceCallb }, "emptyDisk": { SchemaProps: spec.SchemaProps{ - Description: "EmptyDisk instructs to use a Volume offered by the machine pool provider.", + Description: "Deprecated: Use LocalDisk instead EmptyDisk instructs to use a Volume offered by the machine pool provider.", Ref: ref("github.com/ironcore-dev/ironcore/api/compute/v1alpha1.EmptyDiskVolumeSource"), }, }, + "localDisk": { + SchemaProps: spec.SchemaProps{ + Description: "LocalDisk instructs to use a Volume offered by the machine pool provider.", + Ref: ref("github.com/ironcore-dev/ironcore/api/compute/v1alpha1.LocalDiskVolumeSource"), + }, + }, "ephemeral": { SchemaProps: spec.SchemaProps{ Description: "Ephemeral instructs to create an ephemeral (i.e. coupled to the lifetime of the surrounding object) Volume to use.", @@ -1802,7 +1843,7 @@ func schema_ironcore_api_compute_v1alpha1_VolumeSource(ref common.ReferenceCallb }, }, Dependencies: []string{ - "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.EmptyDiskVolumeSource", "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.EphemeralVolumeSource", "k8s.io/api/core/v1.LocalObjectReference"}, + "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.EmptyDiskVolumeSource", "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.EphemeralVolumeSource", "github.com/ironcore-dev/ironcore/api/compute/v1alpha1.LocalDiskVolumeSource", "k8s.io/api/core/v1.LocalObjectReference"}, } } diff --git a/docs/api-reference/compute.md b/docs/api-reference/compute.md index c22466e21..02538ce54 100644 --- a/docs/api-reference/compute.md +++ b/docs/api-reference/compute.md @@ -134,7 +134,8 @@ string (Optional) -

Image is the optional URL providing the operating system image of the machine.

+

Deprecated: Use LocalDisk to provide a bootable disk +Image is the optional URL providing the operating system image of the machine.

@@ -572,6 +573,52 @@ github.com/ironcore-dev/ironcore/api/storage/v1alpha1.VolumeTemplateSpec +

LocalDiskVolumeSource +

+

+(Appears on:VolumeSource) +

+
+

LocalDiskVolumeSource is a volume that’s offered by the machine pool provider. +Usually ephemeral (i.e. deleted when the surrounding entity is deleted), with +varying performance characteristics. Potentially not recoverable.

+
+ + + + + + + + + + + + + + + + + +
FieldDescription
+sizeLimit
+ + +k8s.io/apimachinery/pkg/api/resource.Quantity + + +
+

SizeLimit is the total amount of local storage required for this LocalDisk volume. +The default is nil which means that the limit is undefined.

+
+image
+ +string + +
+(Optional) +

Image is the optional URL providing the operating system image of the machine.

+

MachineExecOptions

@@ -1061,7 +1108,8 @@ string (Optional) -

Image is the optional URL providing the operating system image of the machine.

+

Deprecated: Use LocalDisk to provide a bootable disk +Image is the optional URL providing the operating system image of the machine.

@@ -1573,7 +1621,21 @@ EmptyDiskVolumeSource -

EmptyDisk instructs to use a Volume offered by the machine pool provider.

+

Deprecated: Use LocalDisk instead +EmptyDisk instructs to use a Volume offered by the machine pool provider.

+ + + + +localDisk
+ + +LocalDiskVolumeSource + + + + +

LocalDisk instructs to use a Volume offered by the machine pool provider.

diff --git a/go.mod b/go.mod index 60a04c0bf..ea019704b 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/ironcore-dev/ironcore -go 1.24.0 +go 1.24.1 require ( github.com/afritzler/protoequal v0.1.9 diff --git a/internal/apis/compute/machine_types.go b/internal/apis/compute/machine_types.go index ec0b25943..3f218cc31 100644 --- a/internal/apis/compute/machine_types.go +++ b/internal/apis/compute/machine_types.go @@ -98,6 +98,8 @@ type VolumeSource struct { VolumeRef *corev1.LocalObjectReference // EmptyDisk instructs to use a Volume offered by the machine pool provider. EmptyDisk *EmptyDiskVolumeSource + // LocalDisk instructs to use a Volume offered by the machine pool provider. + LocalDisk *LocalDiskVolumeSource // Ephemeral instructs to create an ephemeral (i.e. coupled to the lifetime of the surrounding object) // Volume to use. Ephemeral *EphemeralVolumeSource @@ -112,6 +114,18 @@ type EmptyDiskVolumeSource struct { SizeLimit *resource.Quantity } +// LocalDiskVolumeSource is a volume that's offered by the machine pool provider. +// Usually ephemeral (i.e. deleted when the surrounding entity is deleted), with +// varying performance characteristics. Potentially not recoverable. +type LocalDiskVolumeSource struct { + // SizeLimit is the total amount of local storage required for this LocalDisk volume. + // The default is nil which means that the limit is undefined. + SizeLimit *resource.Quantity + // Image is the optional URL providing the operating system image of the machine. + // +optional + Image string +} + // NetworkInterfaceStatus reports the status of a NetworkInterfaceSource. type NetworkInterfaceStatus struct { // Name is the name of the NetworkInterface to whom the status belongs to. diff --git a/internal/apis/compute/v1alpha1/zz_generated.conversion.go b/internal/apis/compute/v1alpha1/zz_generated.conversion.go index e4a4409a3..3dbc834d7 100644 --- a/internal/apis/compute/v1alpha1/zz_generated.conversion.go +++ b/internal/apis/compute/v1alpha1/zz_generated.conversion.go @@ -85,6 +85,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*computev1alpha1.LocalDiskVolumeSource)(nil), (*compute.LocalDiskVolumeSource)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_LocalDiskVolumeSource_To_compute_LocalDiskVolumeSource(a.(*computev1alpha1.LocalDiskVolumeSource), b.(*compute.LocalDiskVolumeSource), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*compute.LocalDiskVolumeSource)(nil), (*computev1alpha1.LocalDiskVolumeSource)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_compute_LocalDiskVolumeSource_To_v1alpha1_LocalDiskVolumeSource(a.(*compute.LocalDiskVolumeSource), b.(*computev1alpha1.LocalDiskVolumeSource), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*computev1alpha1.Machine)(nil), (*compute.Machine)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_Machine_To_compute_Machine(a.(*computev1alpha1.Machine), b.(*compute.Machine), scope) }); err != nil { @@ -397,6 +407,28 @@ func Convert_compute_EphemeralVolumeSource_To_v1alpha1_EphemeralVolumeSource(in return autoConvert_compute_EphemeralVolumeSource_To_v1alpha1_EphemeralVolumeSource(in, out, s) } +func autoConvert_v1alpha1_LocalDiskVolumeSource_To_compute_LocalDiskVolumeSource(in *computev1alpha1.LocalDiskVolumeSource, out *compute.LocalDiskVolumeSource, s conversion.Scope) error { + out.SizeLimit = (*resource.Quantity)(unsafe.Pointer(in.SizeLimit)) + out.Image = in.Image + return nil +} + +// Convert_v1alpha1_LocalDiskVolumeSource_To_compute_LocalDiskVolumeSource is an autogenerated conversion function. +func Convert_v1alpha1_LocalDiskVolumeSource_To_compute_LocalDiskVolumeSource(in *computev1alpha1.LocalDiskVolumeSource, out *compute.LocalDiskVolumeSource, s conversion.Scope) error { + return autoConvert_v1alpha1_LocalDiskVolumeSource_To_compute_LocalDiskVolumeSource(in, out, s) +} + +func autoConvert_compute_LocalDiskVolumeSource_To_v1alpha1_LocalDiskVolumeSource(in *compute.LocalDiskVolumeSource, out *computev1alpha1.LocalDiskVolumeSource, s conversion.Scope) error { + out.SizeLimit = (*resource.Quantity)(unsafe.Pointer(in.SizeLimit)) + out.Image = in.Image + return nil +} + +// Convert_compute_LocalDiskVolumeSource_To_v1alpha1_LocalDiskVolumeSource is an autogenerated conversion function. +func Convert_compute_LocalDiskVolumeSource_To_v1alpha1_LocalDiskVolumeSource(in *compute.LocalDiskVolumeSource, out *computev1alpha1.LocalDiskVolumeSource, s conversion.Scope) error { + return autoConvert_compute_LocalDiskVolumeSource_To_v1alpha1_LocalDiskVolumeSource(in, out, s) +} + func autoConvert_v1alpha1_Machine_To_compute_Machine(in *computev1alpha1.Machine, out *compute.Machine, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1alpha1_MachineSpec_To_compute_MachineSpec(&in.Spec, &out.Spec, s); err != nil { @@ -940,6 +972,7 @@ func Convert_compute_Volume_To_v1alpha1_Volume(in *compute.Volume, out *computev func autoConvert_v1alpha1_VolumeSource_To_compute_VolumeSource(in *computev1alpha1.VolumeSource, out *compute.VolumeSource, s conversion.Scope) error { out.VolumeRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.VolumeRef)) out.EmptyDisk = (*compute.EmptyDiskVolumeSource)(unsafe.Pointer(in.EmptyDisk)) + out.LocalDisk = (*compute.LocalDiskVolumeSource)(unsafe.Pointer(in.LocalDisk)) out.Ephemeral = (*compute.EphemeralVolumeSource)(unsafe.Pointer(in.Ephemeral)) return nil } @@ -952,6 +985,7 @@ func Convert_v1alpha1_VolumeSource_To_compute_VolumeSource(in *computev1alpha1.V func autoConvert_compute_VolumeSource_To_v1alpha1_VolumeSource(in *compute.VolumeSource, out *computev1alpha1.VolumeSource, s conversion.Scope) error { out.VolumeRef = (*v1.LocalObjectReference)(unsafe.Pointer(in.VolumeRef)) out.EmptyDisk = (*computev1alpha1.EmptyDiskVolumeSource)(unsafe.Pointer(in.EmptyDisk)) + out.LocalDisk = (*computev1alpha1.LocalDiskVolumeSource)(unsafe.Pointer(in.LocalDisk)) out.Ephemeral = (*computev1alpha1.EphemeralVolumeSource)(unsafe.Pointer(in.Ephemeral)) return nil } diff --git a/internal/apis/compute/validation/machine.go b/internal/apis/compute/validation/machine.go index 9a6e3f6e7..8d689e0c7 100644 --- a/internal/apis/compute/validation/machine.go +++ b/internal/apis/compute/validation/machine.go @@ -222,6 +222,14 @@ func validateVolumeSource(source *compute.VolumeSource, fldPath *field.Path) fie allErrs = append(allErrs, validateEmptyDiskVolumeSource(source.EmptyDisk, fldPath.Child("emptyDisk"))...) } } + if source.LocalDisk != nil { + if numDefs > 0 { + allErrs = append(allErrs, field.Forbidden(fldPath.Child("localDisk"), "must only specify one volume source")) + } else { + numDefs++ + allErrs = append(allErrs, validateLocalDiskVolumeSource(source.LocalDisk, fldPath.Child("localDisk"))...) + } + } if source.Ephemeral != nil { if numDefs > 0 { allErrs = append(allErrs, field.Forbidden(fldPath.Child("ephemeral"), "must only specify one volume source")) @@ -247,6 +255,16 @@ func validateEmptyDiskVolumeSource(source *compute.EmptyDiskVolumeSource, fldPat return allErrs } +func validateLocalDiskVolumeSource(source *compute.LocalDiskVolumeSource, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + + if sizeLimit := source.SizeLimit; sizeLimit != nil { + allErrs = append(allErrs, ironcorevalidation.ValidateNonNegativeQuantity(*sizeLimit, fldPath.Child("sizeLimit"))...) + } + + return allErrs +} + func validateEphemeralVolumeSource(source *compute.EphemeralVolumeSource, fldPath *field.Path) field.ErrorList { var allErrs field.ErrorList @@ -286,5 +304,36 @@ func validateMachineSpecUpdate(new, old *compute.MachineSpec, fldPath *field.Pat allErrs = append(allErrs, ironcorevalidation.ValidateImmutableField(new.MachineClassRef, old.MachineClassRef, fldPath.Child("machineClassRef"))...) allErrs = append(allErrs, ironcorevalidation.ValidateSetOnceField(new.MachinePoolRef, old.MachinePoolRef, fldPath.Child("machinePoolRef"))...) + newVolumesByName := map[string]compute.Volume{} + for _, v := range new.Volumes { + newVolumesByName[v.Name] = v + } + + for _, oldVol := range old.Volumes { + if oldVol.LocalDisk != nil && oldVol.LocalDisk.Image != "" { + newVol, exists := newVolumesByName[oldVol.Name] + volPath := fldPath.Child("volumes").Key(oldVol.Name).Child("localDisk").Child("image") + + if !exists { + allErrs = append(allErrs, field.Invalid(volPath, nil, "volume with an image set must not be removed")) + continue + } + + if newVol.LocalDisk == nil || newVol.LocalDisk.Image == "" { + allErrs = append(allErrs, field.Invalid(volPath, nil, "volume with an image set must not be cleared")) + continue + } + + if newVol.LocalDisk.Image != oldVol.LocalDisk.Image { + allErrs = append(allErrs, + ironcorevalidation.ValidateImmutableField( + newVol.LocalDisk.Image, + oldVol.LocalDisk.Image, + volPath, + )...) + } + } + } + return allErrs } diff --git a/internal/apis/compute/validation/machine_test.go b/internal/apis/compute/validation/machine_test.go index 33282c21b..01778bdf2 100644 --- a/internal/apis/compute/validation/machine_test.go +++ b/internal/apis/compute/validation/machine_test.go @@ -131,6 +131,22 @@ var _ = Describe("Machine", func() { }, ContainElement(InvalidField("spec.volume[0].emptyDisk.sizeLimit")), ), + Entry("invalid empty disk size limit quantity", + &compute.Machine{ + Spec: compute.MachineSpec{ + Volumes: []compute.Volume{ + { + Name: "foo", + Device: "oda", + VolumeSource: compute.VolumeSource{ + LocalDisk: &compute.LocalDiskVolumeSource{SizeLimit: mustParseNewQuantity("-1Gi")}, + }, + }, + }, + }, + }, + ContainElement(InvalidField("spec.volume[0].localDisk.sizeLimit")), + ), Entry("duplicate machine volume device", &compute.Machine{ Spec: compute.MachineSpec{ @@ -474,6 +490,60 @@ var _ = Describe("Machine", func() { &compute.Machine{}, Not(ContainElement(ImmutableField("spec.machinePoolRef"))), ), + Entry("mutate local boot disk image", + &compute.Machine{ + Spec: compute.MachineSpec{ + Volumes: []compute.Volume{ + { + Name: "rootdisk", + Device: "vda", + VolumeSource: compute.VolumeSource{ + LocalDisk: &compute.LocalDiskVolumeSource{ + Image: "newImage", + }, + }, + }, + }, + }, + }, + &compute.Machine{ + Spec: compute.MachineSpec{ + Volumes: []compute.Volume{ + { + Name: "rootdisk", + Device: "vda", + VolumeSource: compute.VolumeSource{ + LocalDisk: &compute.LocalDiskVolumeSource{ + Image: "oldImage", + }, + }, + }, + }, + }, + }, + ContainElement(ImmutableField("spec.volumes[rootdisk].localDisk.image")), + ), + Entry("remove local boot disk", + &compute.Machine{ + Spec: compute.MachineSpec{}, + }, + &compute.Machine{ + Spec: compute.MachineSpec{ + Volumes: []compute.Volume{ + { + Name: "rootdisk", + Device: "vda", + VolumeSource: compute.VolumeSource{ + LocalDisk: &compute.LocalDiskVolumeSource{ + Image: "image", + }, + }, + }, + }, + }, + }, + ContainElement(InvalidField("spec.volumes[rootdisk].localDisk.image")), + ), Entry("duplicate volume name", &compute.Machine{ Spec: compute.MachineSpec{ diff --git a/internal/apis/compute/zz_generated.deepcopy.go b/internal/apis/compute/zz_generated.deepcopy.go index 508355b25..87528abfe 100644 --- a/internal/apis/compute/zz_generated.deepcopy.go +++ b/internal/apis/compute/zz_generated.deepcopy.go @@ -112,6 +112,27 @@ func (in *EphemeralVolumeSource) DeepCopy() *EphemeralVolumeSource { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LocalDiskVolumeSource) DeepCopyInto(out *LocalDiskVolumeSource) { + *out = *in + if in.SizeLimit != nil { + in, out := &in.SizeLimit, &out.SizeLimit + x := (*in).DeepCopy() + *out = &x + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new LocalDiskVolumeSource. +func (in *LocalDiskVolumeSource) DeepCopy() *LocalDiskVolumeSource { + if in == nil { + return nil + } + out := new(LocalDiskVolumeSource) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Machine) DeepCopyInto(out *Machine) { *out = *in @@ -631,6 +652,11 @@ func (in *VolumeSource) DeepCopyInto(out *VolumeSource) { *out = new(EmptyDiskVolumeSource) (*in).DeepCopyInto(*out) } + if in.LocalDisk != nil { + in, out := &in.LocalDisk, &out.LocalDisk + *out = new(LocalDiskVolumeSource) + (*in).DeepCopyInto(*out) + } if in.Ephemeral != nil { in, out := &in.Ephemeral, &out.Ephemeral *out = new(EphemeralVolumeSource) diff --git a/iri/apis/machine/v1alpha1/api.pb.go b/iri/apis/machine/v1alpha1/api.pb.go index e1228182b..6cf543e37 100644 --- a/iri/apis/machine/v1alpha1/api.pb.go +++ b/iri/apis/machine/v1alpha1/api.pb.go @@ -556,27 +556,28 @@ func (x *ImageSpec) GetImage() string { return "" } -type EmptyDisk struct { +type LocalDisk struct { state protoimpl.MessageState `protogen:"open.v1"` SizeBytes int64 `protobuf:"varint,1,opt,name=size_bytes,json=sizeBytes,proto3" json:"size_bytes,omitempty"` + Image *ImageSpec `protobuf:"bytes,2,opt,name=image,proto3" json:"image,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } -func (x *EmptyDisk) Reset() { - *x = EmptyDisk{} +func (x *LocalDisk) Reset() { + *x = LocalDisk{} mi := &file_machine_v1alpha1_api_proto_msgTypes[6] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } -func (x *EmptyDisk) String() string { +func (x *LocalDisk) String() string { return protoimpl.X.MessageStringOf(x) } -func (*EmptyDisk) ProtoMessage() {} +func (*LocalDisk) ProtoMessage() {} -func (x *EmptyDisk) ProtoReflect() protoreflect.Message { +func (x *LocalDisk) ProtoReflect() protoreflect.Message { mi := &file_machine_v1alpha1_api_proto_msgTypes[6] if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -588,18 +589,25 @@ func (x *EmptyDisk) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use EmptyDisk.ProtoReflect.Descriptor instead. -func (*EmptyDisk) Descriptor() ([]byte, []int) { +// Deprecated: Use LocalDisk.ProtoReflect.Descriptor instead. +func (*LocalDisk) Descriptor() ([]byte, []int) { return file_machine_v1alpha1_api_proto_rawDescGZIP(), []int{6} } -func (x *EmptyDisk) GetSizeBytes() int64 { +func (x *LocalDisk) GetSizeBytes() int64 { if x != nil { return x.SizeBytes } return 0 } +func (x *LocalDisk) GetImage() *ImageSpec { + if x != nil { + return x.Image + } + return nil +} + type VolumeConnection struct { state protoimpl.MessageState `protogen:"open.v1"` Driver string `protobuf:"bytes,1,opt,name=driver,proto3" json:"driver,omitempty"` @@ -688,7 +696,7 @@ type Volume struct { state protoimpl.MessageState `protogen:"open.v1"` Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Device string `protobuf:"bytes,2,opt,name=device,proto3" json:"device,omitempty"` - EmptyDisk *EmptyDisk `protobuf:"bytes,4,opt,name=empty_disk,json=emptyDisk,proto3" json:"empty_disk,omitempty"` + LocalDisk *LocalDisk `protobuf:"bytes,4,opt,name=local_disk,json=localDisk,proto3" json:"local_disk,omitempty"` Connection *VolumeConnection `protobuf:"bytes,5,opt,name=connection,proto3" json:"connection,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache @@ -738,9 +746,9 @@ func (x *Volume) GetDevice() string { return "" } -func (x *Volume) GetEmptyDisk() *EmptyDisk { +func (x *Volume) GetLocalDisk() *LocalDisk { if x != nil { - return x.EmptyDisk + return x.LocalDisk } return nil } @@ -823,11 +831,10 @@ func (x *NetworkInterface) GetAttributes() map[string]string { type MachineSpec struct { state protoimpl.MessageState `protogen:"open.v1"` Power Power `protobuf:"varint,1,opt,name=power,proto3,enum=machine.v1alpha1.Power" json:"power,omitempty"` - Image *ImageSpec `protobuf:"bytes,2,opt,name=image,proto3" json:"image,omitempty"` - Class string `protobuf:"bytes,3,opt,name=class,proto3" json:"class,omitempty"` - IgnitionData []byte `protobuf:"bytes,4,opt,name=ignition_data,json=ignitionData,proto3" json:"ignition_data,omitempty"` - Volumes []*Volume `protobuf:"bytes,5,rep,name=volumes,proto3" json:"volumes,omitempty"` - NetworkInterfaces []*NetworkInterface `protobuf:"bytes,6,rep,name=network_interfaces,json=networkInterfaces,proto3" json:"network_interfaces,omitempty"` + Class string `protobuf:"bytes,2,opt,name=class,proto3" json:"class,omitempty"` + IgnitionData []byte `protobuf:"bytes,3,opt,name=ignition_data,json=ignitionData,proto3" json:"ignition_data,omitempty"` + Volumes []*Volume `protobuf:"bytes,4,rep,name=volumes,proto3" json:"volumes,omitempty"` + NetworkInterfaces []*NetworkInterface `protobuf:"bytes,5,rep,name=network_interfaces,json=networkInterfaces,proto3" json:"network_interfaces,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -869,13 +876,6 @@ func (x *MachineSpec) GetPower() Power { return Power_POWER_ON } -func (x *MachineSpec) GetImage() *ImageSpec { - if x != nil { - return x.Image - } - return nil -} - func (x *MachineSpec) GetClass() string { if x != nil { return x.Class @@ -2475,10 +2475,11 @@ const file_machine_v1alpha1_api_proto_rawDesc = "" + "\x04spec\x18\x02 \x01(\v2\x1d.machine.v1alpha1.MachineSpecR\x04spec\x127\n" + "\x06status\x18\x03 \x01(\v2\x1f.machine.v1alpha1.MachineStatusR\x06status\"!\n" + "\tImageSpec\x12\x14\n" + - "\x05image\x18\x01 \x01(\tR\x05image\"*\n" + - "\tEmptyDisk\x12\x1d\n" + + "\x05image\x18\x01 \x01(\tR\x05image\"]\n" + + "\tLocalDisk\x12\x1d\n" + "\n" + - "size_bytes\x18\x01 \x01(\x03R\tsizeBytes\"\xc5\x04\n" + + "size_bytes\x18\x01 \x01(\x03R\tsizeBytes\x121\n" + + "\x05image\x18\x02 \x01(\v2\x1b.machine.v1alpha1.ImageSpecR\x05image\"\xc5\x04\n" + "\x10VolumeConnection\x12\x16\n" + "\x06driver\x18\x01 \x01(\tR\x06driver\x12\x16\n" + "\x06handle\x18\x02 \x01(\tR\x06handle\x12R\n" + @@ -2502,7 +2503,7 @@ const file_machine_v1alpha1_api_proto_rawDesc = "" + "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + "\x06device\x18\x02 \x01(\tR\x06device\x12:\n" + "\n" + - "empty_disk\x18\x04 \x01(\v2\x1b.machine.v1alpha1.EmptyDiskR\temptyDisk\x12B\n" + + "local_disk\x18\x04 \x01(\v2\x1b.machine.v1alpha1.LocalDiskR\tlocalDisk\x12B\n" + "\n" + "connection\x18\x05 \x01(\v2\".machine.v1alpha1.VolumeConnectionR\n" + "connection\"\xea\x01\n" + @@ -2516,14 +2517,13 @@ const file_machine_v1alpha1_api_proto_rawDesc = "" + "attributes\x1a=\n" + "\x0fAttributesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + - "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xb1\x02\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\xfe\x01\n" + "\vMachineSpec\x12-\n" + - "\x05power\x18\x01 \x01(\x0e2\x17.machine.v1alpha1.PowerR\x05power\x121\n" + - "\x05image\x18\x02 \x01(\v2\x1b.machine.v1alpha1.ImageSpecR\x05image\x12\x14\n" + - "\x05class\x18\x03 \x01(\tR\x05class\x12#\n" + - "\rignition_data\x18\x04 \x01(\fR\fignitionData\x122\n" + - "\avolumes\x18\x05 \x03(\v2\x18.machine.v1alpha1.VolumeR\avolumes\x12Q\n" + - "\x12network_interfaces\x18\x06 \x03(\v2\".machine.v1alpha1.NetworkInterfaceR\x11networkInterfaces\"\xa6\x02\n" + + "\x05power\x18\x01 \x01(\x0e2\x17.machine.v1alpha1.PowerR\x05power\x12\x14\n" + + "\x05class\x18\x02 \x01(\tR\x05class\x12#\n" + + "\rignition_data\x18\x03 \x01(\fR\fignitionData\x122\n" + + "\avolumes\x18\x04 \x03(\v2\x18.machine.v1alpha1.VolumeR\avolumes\x12Q\n" + + "\x12network_interfaces\x18\x05 \x03(\v2\".machine.v1alpha1.NetworkInterfaceR\x11networkInterfaces\"\xa6\x02\n" + "\rMachineStatus\x12/\n" + "\x13observed_generation\x18\x01 \x01(\x03R\x12observedGeneration\x124\n" + "\x05state\x18\x02 \x01(\x0e2\x1e.machine.v1alpha1.MachineStateR\x05state\x12\x1b\n" + @@ -2669,7 +2669,7 @@ var file_machine_v1alpha1_api_proto_goTypes = []any{ (*MachineClassCapabilities)(nil), // 7: machine.v1alpha1.MachineClassCapabilities (*Machine)(nil), // 8: machine.v1alpha1.Machine (*ImageSpec)(nil), // 9: machine.v1alpha1.ImageSpec - (*EmptyDisk)(nil), // 10: machine.v1alpha1.EmptyDisk + (*LocalDisk)(nil), // 10: machine.v1alpha1.LocalDisk (*VolumeConnection)(nil), // 11: machine.v1alpha1.VolumeConnection (*Volume)(nil), // 12: machine.v1alpha1.Volume (*NetworkInterface)(nil), // 13: machine.v1alpha1.NetworkInterface @@ -2729,14 +2729,14 @@ var file_machine_v1alpha1_api_proto_depIdxs = []int32{ 58, // 5: machine.v1alpha1.Machine.metadata:type_name -> meta.v1alpha1.ObjectMetadata 14, // 6: machine.v1alpha1.Machine.spec:type_name -> machine.v1alpha1.MachineSpec 15, // 7: machine.v1alpha1.Machine.status:type_name -> machine.v1alpha1.MachineStatus - 53, // 8: machine.v1alpha1.VolumeConnection.attributes:type_name -> machine.v1alpha1.VolumeConnection.AttributesEntry - 54, // 9: machine.v1alpha1.VolumeConnection.secret_data:type_name -> machine.v1alpha1.VolumeConnection.SecretDataEntry - 55, // 10: machine.v1alpha1.VolumeConnection.encryption_data:type_name -> machine.v1alpha1.VolumeConnection.EncryptionDataEntry - 10, // 11: machine.v1alpha1.Volume.empty_disk:type_name -> machine.v1alpha1.EmptyDisk - 11, // 12: machine.v1alpha1.Volume.connection:type_name -> machine.v1alpha1.VolumeConnection - 56, // 13: machine.v1alpha1.NetworkInterface.attributes:type_name -> machine.v1alpha1.NetworkInterface.AttributesEntry - 0, // 14: machine.v1alpha1.MachineSpec.power:type_name -> machine.v1alpha1.Power - 9, // 15: machine.v1alpha1.MachineSpec.image:type_name -> machine.v1alpha1.ImageSpec + 9, // 8: machine.v1alpha1.LocalDisk.image:type_name -> machine.v1alpha1.ImageSpec + 53, // 9: machine.v1alpha1.VolumeConnection.attributes:type_name -> machine.v1alpha1.VolumeConnection.AttributesEntry + 54, // 10: machine.v1alpha1.VolumeConnection.secret_data:type_name -> machine.v1alpha1.VolumeConnection.SecretDataEntry + 55, // 11: machine.v1alpha1.VolumeConnection.encryption_data:type_name -> machine.v1alpha1.VolumeConnection.EncryptionDataEntry + 10, // 12: machine.v1alpha1.Volume.local_disk:type_name -> machine.v1alpha1.LocalDisk + 11, // 13: machine.v1alpha1.Volume.connection:type_name -> machine.v1alpha1.VolumeConnection + 56, // 14: machine.v1alpha1.NetworkInterface.attributes:type_name -> machine.v1alpha1.NetworkInterface.AttributesEntry + 0, // 15: machine.v1alpha1.MachineSpec.power:type_name -> machine.v1alpha1.Power 12, // 16: machine.v1alpha1.MachineSpec.volumes:type_name -> machine.v1alpha1.Volume 13, // 17: machine.v1alpha1.MachineSpec.network_interfaces:type_name -> machine.v1alpha1.NetworkInterface 3, // 18: machine.v1alpha1.MachineStatus.state:type_name -> machine.v1alpha1.MachineState diff --git a/iri/apis/machine/v1alpha1/api.proto b/iri/apis/machine/v1alpha1/api.proto index 4e1fc95e6..5adc6436d 100644 --- a/iri/apis/machine/v1alpha1/api.proto +++ b/iri/apis/machine/v1alpha1/api.proto @@ -59,8 +59,9 @@ message ImageSpec { string image = 1; } -message EmptyDisk { +message LocalDisk { int64 size_bytes = 1; + ImageSpec image = 2; } message VolumeConnection { @@ -75,7 +76,7 @@ message VolumeConnection { message Volume { string name = 1; string device = 2; - EmptyDisk empty_disk = 4; + LocalDisk local_disk = 4; VolumeConnection connection = 5; } @@ -93,11 +94,10 @@ enum Power { message MachineSpec { Power power = 1; - ImageSpec image = 2; - string class = 3; - bytes ignition_data = 4; - repeated Volume volumes = 5; - repeated NetworkInterface network_interfaces = 6; + string class = 2; + bytes ignition_data = 3; + repeated Volume volumes = 4; + repeated NetworkInterface network_interfaces = 5; } message MachineStatus { diff --git a/irictl-machine/tableconverters/machine.go b/irictl-machine/tableconverters/machine.go index f82427ff1..da8ccfb19 100644 --- a/irictl-machine/tableconverters/machine.go +++ b/irictl-machine/tableconverters/machine.go @@ -16,7 +16,6 @@ var ( machineHeaders = []api.Header{ {Name: "ID"}, {Name: "Class"}, - {Name: "Image"}, {Name: "State"}, {Name: "Age"}, } @@ -29,7 +28,6 @@ var ( return api.Row{ machine.Metadata.Id, machine.Spec.Class, - machine.Spec.GetImage().GetImage(), machine.Status.State.String(), duration.HumanDuration(time.Since(time.Unix(0, machine.Metadata.CreatedAt))), }, nil diff --git a/poollet/machinepoollet/controllers/machine_controller.go b/poollet/machinepoollet/controllers/machine_controller.go index 92880022d..2a58a82ca 100644 --- a/poollet/machinepoollet/controllers/machine_controller.go +++ b/poollet/machinepoollet/controllers/machine_controller.go @@ -327,7 +327,7 @@ func (r *MachineReconciler) create( log.V(1).Info("Create") log.V(1).Info("Getting machine config") - iriMachine, ok, err := r.prepareIRIMachine(ctx, machine, nics, volumes) + iriMachine, ok, err := r.prepareIRIMachine(ctx, log, machine, nics, volumes) if err != nil { return ctrl.Result{}, fmt.Errorf("error preparing iri machine: %w", err) } @@ -697,6 +697,7 @@ func (r *MachineReconciler) prepareIRIIgnitionData(ctx context.Context, machine func (r *MachineReconciler) prepareIRIMachine( ctx context.Context, + log logr.Logger, machine *computev1alpha1.Machine, nics []networkingv1alpha1.NetworkInterface, volumes []storagev1alpha1.Volume, @@ -714,13 +715,6 @@ func (r *MachineReconciler) prepareIRIMachine( ok = false } - var imageSpec *iri.ImageSpec - if image := machine.Spec.Image; image != "" { - imageSpec = &iri.ImageSpec{ - Image: image, - } - } - var ignitionData []byte if ignitionRef := machine.Spec.IgnitionRef; ignitionRef != nil { data, ignitionSpecOK, err := r.prepareIRIIgnitionData(ctx, machine, ignitionRef) @@ -742,7 +736,7 @@ func (r *MachineReconciler) prepareIRIMachine( ok = false } - machineVolumes, machineVolumesOK, err := r.prepareIRIVolumes(ctx, machine, volumes) + machineVolumes, machineVolumesOK, err := r.prepareIRIVolumes(ctx, log, machine, volumes) switch { case err != nil: errs = append(errs, fmt.Errorf("error preparing iri machine volumes: %w", err)) @@ -772,7 +766,6 @@ func (r *MachineReconciler) prepareIRIMachine( Annotations: annotations, }, Spec: &iri.MachineSpec{ - Image: imageSpec, Class: class, IgnitionData: ignitionData, Volumes: machineVolumes, diff --git a/poollet/machinepoollet/controllers/machine_controller_test.go b/poollet/machinepoollet/controllers/machine_controller_test.go index a74a86ec7..74e63069f 100644 --- a/poollet/machinepoollet/controllers/machine_controller_test.go +++ b/poollet/machinepoollet/controllers/machine_controller_test.go @@ -598,6 +598,53 @@ var _ = Describe("MachineController", func() { })))) }) + It("should validate IRI volume update for machine", func(ctx SpecContext) { + By("creating a machine") + localDiskSize := resource.MustParse("10Gi") + machine := &computev1alpha1.Machine{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + GenerateName: "machine-", + }, + Spec: computev1alpha1.MachineSpec{ + MachineClassRef: corev1.LocalObjectReference{Name: mc.Name}, + MachinePoolRef: &corev1.LocalObjectReference{Name: mp.Name}, + Volumes: []computev1alpha1.Volume{ + { + Name: "primary", + VolumeSource: computev1alpha1.VolumeSource{ + LocalDisk: &computev1alpha1.LocalDiskVolumeSource{ + SizeLimit: &localDiskSize, + Image: "sample-image", + }, + }, + }, + }, + }, + } + Expect(k8sClient.Create(ctx, machine)).To(Succeed()) + + By("waiting for the runtime to report the machine, volume and network interface") + Eventually(srv).Should(SatisfyAll( + HaveField("Machines", HaveLen(1)), + )) + _, iriMachine := GetSingleMapEntry(srv.Machines) + + By("inspecting the iri machine volumes") + Expect(iriMachine.Spec.Volumes).To(ProtoConsistOf( + &iri.Volume{ + Name: "primary", + Device: "oda", + LocalDisk: &iri.LocalDisk{ + SizeBytes: localDiskSize.Value(), + Image: &iri.ImageSpec{ + Image: "sample-image", + }, + }, + }, + )) + }) + It("should validate IRI volume update for machine", func(ctx SpecContext) { const fooAnnotationValue = "bar" By("creating a network") diff --git a/poollet/machinepoollet/controllers/machine_controller_volume.go b/poollet/machinepoollet/controllers/machine_controller_volume.go index adc38643a..3c7c829e0 100644 --- a/poollet/machinepoollet/controllers/machine_controller_volume.go +++ b/poollet/machinepoollet/controllers/machine_controller_volume.go @@ -228,22 +228,32 @@ func (r *MachineReconciler) prepareRemoteIRIVolume( }, true, nil } -func (r *MachineReconciler) prepareEmptyDiskIRIVolume(machineVolume *computev1alpha1.Volume) *iri.Volume { +func (r *MachineReconciler) prepareLocalDiskIRIVolume(machineVolume *computev1alpha1.Volume) *iri.Volume { var sizeBytes int64 - if sizeLimit := machineVolume.EmptyDisk.SizeLimit; sizeLimit != nil { + if sizeLimit := machineVolume.LocalDisk.SizeLimit; sizeLimit != nil { sizeBytes = sizeLimit.Value() } + + var imageSpec *iri.ImageSpec + if image := machineVolume.LocalDisk.Image; image != "" { + imageSpec = &iri.ImageSpec{ + Image: machineVolume.LocalDisk.Image, + } + } + return &iri.Volume{ Name: machineVolume.Name, Device: *machineVolume.Device, - EmptyDisk: &iri.EmptyDisk{ + LocalDisk: &iri.LocalDisk{ SizeBytes: sizeBytes, + Image: imageSpec, }, } } func (r *MachineReconciler) prepareIRIVolumes( ctx context.Context, + log logr.Logger, machine *computev1alpha1.Machine, volumes []storagev1alpha1.Volume, ) ([]*iri.Volume, bool, error) { @@ -270,12 +280,10 @@ func (r *MachineReconciler) prepareIRIVolumes( } for _, machineVolume := range machine.Spec.Volumes { - if machineVolume.EmptyDisk == nil { - continue + if machineVolume.LocalDisk != nil { + iriVolume := r.prepareLocalDiskIRIVolume(&machineVolume) + iriVolumes = append(iriVolumes, iriVolume) } - - iriVolume := r.prepareEmptyDiskIRIVolume(&machineVolume) - iriVolumes = append(iriVolumes, iriVolume) } if len(iriVolumes) != len(machine.Spec.Volumes) { @@ -402,7 +410,7 @@ func (r *MachineReconciler) updateIRIVolumes( iriMachine *iri.Machine, volumes []storagev1alpha1.Volume, ) error { - desiredIRIVolumes, _, err := r.prepareIRIVolumes(ctx, machine, volumes) + desiredIRIVolumes, _, err := r.prepareIRIVolumes(ctx, log, machine, volumes) if err != nil { return fmt.Errorf("error preparing iri volumes: %w", err) }