diff --git a/api/storage/v1alpha1/volume_types.go b/api/storage/v1alpha1/volume_types.go index 27775cc97..b6e983224 100644 --- a/api/storage/v1alpha1/volume_types.go +++ b/api/storage/v1alpha1/volume_types.go @@ -123,8 +123,24 @@ type VolumeList struct { Items []Volume `json:"items"` } +// ReclaimPolicy for a Volume tells what to do with the volume after its managing resource has been deleted +type ReclaimPolicy string + +const ( + // The Volume resource is not deleted after the managing resource has been deleted + Retain ReclaimPolicy = "Retain" + // The Volume resource is garbage-collected when the managing resource has been deleted + Delete ReclaimPolicy = "Delete" +) + +type EphemeralVolumeSpec struct { + VolumeSpec `json:",inline"` + // ReclaimPolicy defines the strategy for the corresponding volume resource after its managing resource has been deleted + ReclaimPolicy ReclaimPolicy `json:"reclaimpolicy,omitempty"` +} + // VolumeTemplateSpec is the specification of a Volume template. type VolumeTemplateSpec struct { metav1.ObjectMeta `json:"metadata,omitempty"` - Spec VolumeSpec `json:"spec,omitempty"` + Spec EphemeralVolumeSpec `json:"spec,omitempty"` } diff --git a/api/storage/v1alpha1/zz_generated.deepcopy.go b/api/storage/v1alpha1/zz_generated.deepcopy.go index 27d1cd639..f51db8410 100644 --- a/api/storage/v1alpha1/zz_generated.deepcopy.go +++ b/api/storage/v1alpha1/zz_generated.deepcopy.go @@ -371,6 +371,23 @@ func (in *BucketTemplateSpec) DeepCopy() *BucketTemplateSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EphemeralVolumeSpec) DeepCopyInto(out *EphemeralVolumeSpec) { + *out = *in + in.VolumeSpec.DeepCopyInto(&out.VolumeSpec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EphemeralVolumeSpec. +func (in *EphemeralVolumeSpec) DeepCopy() *EphemeralVolumeSpec { + if in == nil { + return nil + } + out := new(EphemeralVolumeSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Volume) DeepCopyInto(out *Volume) { *out = *in diff --git a/client-go/applyconfigurations/internal/internal.go b/client-go/applyconfigurations/internal/internal.go index ddbc611d6..28e46982c 100644 --- a/client-go/applyconfigurations/internal/internal.go +++ b/client-go/applyconfigurations/internal/internal.go @@ -1438,6 +1438,49 @@ var schemaYAML = typed.YAMLObject(`types: - name: state type: scalar: string +- name: com.github.ironcore-dev.ironcore.api.storage.v1alpha1.EphemeralVolumeSpec + map: + fields: + - name: claimRef + type: + namedType: com.github.ironcore-dev.ironcore.api.common.v1alpha1.LocalUIDReference + - name: encryption + type: + namedType: com.github.ironcore-dev.ironcore.api.storage.v1alpha1.VolumeEncryption + - name: image + type: + scalar: string + - name: imagePullSecretRef + type: + namedType: io.k8s.api.core.v1.LocalObjectReference + - name: reclaimpolicy + type: + scalar: string + - name: resources + type: + map: + elementType: + namedType: io.k8s.apimachinery.pkg.api.resource.Quantity + - name: tolerations + type: + list: + elementType: + namedType: com.github.ironcore-dev.ironcore.api.common.v1alpha1.Toleration + elementRelationship: atomic + - name: unclaimable + type: + scalar: boolean + - name: volumeClassRef + type: + namedType: io.k8s.api.core.v1.LocalObjectReference + - name: volumePoolRef + type: + namedType: io.k8s.api.core.v1.LocalObjectReference + - name: volumePoolSelector + type: + map: + elementType: + scalar: string - name: com.github.ironcore-dev.ironcore.api.storage.v1alpha1.Volume map: fields: @@ -1683,7 +1726,7 @@ var schemaYAML = typed.YAMLObject(`types: default: {} - name: spec type: - namedType: com.github.ironcore-dev.ironcore.api.storage.v1alpha1.VolumeSpec + namedType: com.github.ironcore-dev.ironcore.api.storage.v1alpha1.EphemeralVolumeSpec default: {} - name: io.k8s.api.core.v1.LocalObjectReference map: diff --git a/client-go/applyconfigurations/storage/v1alpha1/ephemeralvolumespec.go b/client-go/applyconfigurations/storage/v1alpha1/ephemeralvolumespec.go new file mode 100644 index 000000000..bb7a2229c --- /dev/null +++ b/client-go/applyconfigurations/storage/v1alpha1/ephemeralvolumespec.go @@ -0,0 +1,122 @@ +// SPDX-FileCopyrightText: 2023 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 ( + commonv1alpha1 "github.com/ironcore-dev/ironcore/api/common/v1alpha1" + corev1alpha1 "github.com/ironcore-dev/ironcore/api/core/v1alpha1" + storagev1alpha1 "github.com/ironcore-dev/ironcore/api/storage/v1alpha1" + v1 "k8s.io/api/core/v1" +) + +// EphemeralVolumeSpecApplyConfiguration represents an declarative configuration of the EphemeralVolumeSpec type for use +// with apply. +type EphemeralVolumeSpecApplyConfiguration struct { + VolumeSpecApplyConfiguration `json:",inline"` + ReclaimPolicy *storagev1alpha1.ReclaimPolicy `json:"reclaimpolicy,omitempty"` +} + +// EphemeralVolumeSpecApplyConfiguration constructs an declarative configuration of the EphemeralVolumeSpec type for use with +// apply. +func EphemeralVolumeSpec() *EphemeralVolumeSpecApplyConfiguration { + return &EphemeralVolumeSpecApplyConfiguration{} +} + +// WithVolumeClassRef sets the VolumeClassRef 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 VolumeClassRef field is set to the value of the last call. +func (b *EphemeralVolumeSpecApplyConfiguration) WithVolumeClassRef(value v1.LocalObjectReference) *EphemeralVolumeSpecApplyConfiguration { + b.VolumeClassRef = &value + return b +} + +// WithVolumePoolSelector puts the entries into the VolumePoolSelector field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, the entries provided by each call will be put on the VolumePoolSelector field, +// overwriting an existing map entries in VolumePoolSelector field with the same key. +func (b *EphemeralVolumeSpecApplyConfiguration) WithVolumePoolSelector(entries map[string]string) *EphemeralVolumeSpecApplyConfiguration { + if b.VolumePoolSelector == nil && len(entries) > 0 { + b.VolumePoolSelector = make(map[string]string, len(entries)) + } + for k, v := range entries { + b.VolumePoolSelector[k] = v + } + return b +} + +// WithVolumePoolRef sets the VolumePoolRef 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 VolumePoolRef field is set to the value of the last call. +func (b *EphemeralVolumeSpecApplyConfiguration) WithVolumePoolRef(value v1.LocalObjectReference) *EphemeralVolumeSpecApplyConfiguration { + b.VolumePoolRef = &value + return b +} + +// WithClaimRef sets the ClaimRef 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 ClaimRef field is set to the value of the last call. +func (b *EphemeralVolumeSpecApplyConfiguration) WithClaimRef(value commonv1alpha1.LocalUIDReference) *EphemeralVolumeSpecApplyConfiguration { + b.ClaimRef = &value + return b +} + +// WithResources sets the Resources 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 Resources field is set to the value of the last call. +func (b *EphemeralVolumeSpecApplyConfiguration) WithResources(value corev1alpha1.ResourceList) *EphemeralVolumeSpecApplyConfiguration { + b.Resources = &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 *EphemeralVolumeSpecApplyConfiguration) WithImage(value string) *EphemeralVolumeSpecApplyConfiguration { + b.Image = &value + return b +} + +// WithImagePullSecretRef sets the ImagePullSecretRef 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 ImagePullSecretRef field is set to the value of the last call. +func (b *EphemeralVolumeSpecApplyConfiguration) WithImagePullSecretRef(value v1.LocalObjectReference) *EphemeralVolumeSpecApplyConfiguration { + b.ImagePullSecretRef = &value + return b +} + +// WithUnclaimable sets the Unclaimable 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 Unclaimable field is set to the value of the last call. +func (b *EphemeralVolumeSpecApplyConfiguration) WithUnclaimable(value bool) *EphemeralVolumeSpecApplyConfiguration { + b.Unclaimable = &value + return b +} + +// WithTolerations adds the given value to the Tolerations field in the declarative configuration +// and returns the receiver, so that objects can be build by chaining "With" function invocations. +// If called multiple times, values provided by each call will be appended to the Tolerations field. +func (b *EphemeralVolumeSpecApplyConfiguration) WithTolerations(values ...commonv1alpha1.Toleration) *EphemeralVolumeSpecApplyConfiguration { + for i := range values { + b.Tolerations = append(b.Tolerations, values[i]) + } + return b +} + +// WithEncryption sets the Encryption 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 Encryption field is set to the value of the last call. +func (b *EphemeralVolumeSpecApplyConfiguration) WithEncryption(value *VolumeEncryptionApplyConfiguration) *EphemeralVolumeSpecApplyConfiguration { + b.Encryption = value + return b +} + +// WithReclaimPolicy sets the ReclaimPolicy 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 ReclaimPolicy field is set to the value of the last call. +func (b *EphemeralVolumeSpecApplyConfiguration) WithReclaimPolicy(value storagev1alpha1.ReclaimPolicy) *EphemeralVolumeSpecApplyConfiguration { + b.ReclaimPolicy = &value + return b +} diff --git a/client-go/applyconfigurations/storage/v1alpha1/volumetemplatespec.go b/client-go/applyconfigurations/storage/v1alpha1/volumetemplatespec.go index 324150023..ba8207a7f 100644 --- a/client-go/applyconfigurations/storage/v1alpha1/volumetemplatespec.go +++ b/client-go/applyconfigurations/storage/v1alpha1/volumetemplatespec.go @@ -15,7 +15,7 @@ import ( // with apply. type VolumeTemplateSpecApplyConfiguration struct { *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` - Spec *VolumeSpecApplyConfiguration `json:"spec,omitempty"` + Spec *EphemeralVolumeSpecApplyConfiguration `json:"spec,omitempty"` } // VolumeTemplateSpecApplyConfiguration constructs an declarative configuration of the VolumeTemplateSpec type for use with @@ -169,7 +169,7 @@ func (b *VolumeTemplateSpecApplyConfiguration) ensureObjectMetaApplyConfiguratio // WithSpec sets the Spec 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 Spec field is set to the value of the last call. -func (b *VolumeTemplateSpecApplyConfiguration) WithSpec(value *VolumeSpecApplyConfiguration) *VolumeTemplateSpecApplyConfiguration { +func (b *VolumeTemplateSpecApplyConfiguration) WithSpec(value *EphemeralVolumeSpecApplyConfiguration) *VolumeTemplateSpecApplyConfiguration { b.Spec = value return b } diff --git a/client-go/applyconfigurations/utils.go b/client-go/applyconfigurations/utils.go index 3384fc3b6..5c985ba8b 100644 --- a/client-go/applyconfigurations/utils.go +++ b/client-go/applyconfigurations/utils.go @@ -200,6 +200,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &applyconfigurationsstoragev1alpha1.BucketSpecApplyConfiguration{} case storagev1alpha1.SchemeGroupVersion.WithKind("BucketStatus"): return &applyconfigurationsstoragev1alpha1.BucketStatusApplyConfiguration{} + case storagev1alpha1.SchemeGroupVersion.WithKind("EphemeralVolumeSpec"): + return &applyconfigurationsstoragev1alpha1.EphemeralVolumeSpecApplyConfiguration{} case storagev1alpha1.SchemeGroupVersion.WithKind("Volume"): return &applyconfigurationsstoragev1alpha1.VolumeApplyConfiguration{} case storagev1alpha1.SchemeGroupVersion.WithKind("VolumeAccess"): diff --git a/client-go/openapi/api_violations.report b/client-go/openapi/api_violations.report index 9bfd08cd3..04bc9c12e 100644 --- a/client-go/openapi/api_violations.report +++ b/client-go/openapi/api_violations.report @@ -54,6 +54,7 @@ API rule violation: names_match,github.com/ironcore-dev/ironcore/api/networking/ API rule violation: names_match,github.com/ironcore-dev/ironcore/api/networking/v1alpha1,NetworkInterfaceSpec,IPs API rule violation: names_match,github.com/ironcore-dev/ironcore/api/networking/v1alpha1,NetworkInterfaceStatus,IPs API rule violation: names_match,github.com/ironcore-dev/ironcore/api/networking/v1alpha1,NetworkSpec,PeeringClaimRefs +API rule violation: names_match,github.com/ironcore-dev/ironcore/api/storage/v1alpha1,EphemeralVolumeSpec,ReclaimPolicy API rule violation: names_match,k8s.io/api/core/v1,AzureDiskVolumeSource,DataDiskURI API rule violation: names_match,k8s.io/api/core/v1,ContainerStatus,LastTerminationState API rule violation: names_match,k8s.io/api/core/v1,DaemonEndpoint,Port diff --git a/client-go/openapi/zz_generated.openapi.go b/client-go/openapi/zz_generated.openapi.go index 8262acdee..06cd3cf72 100644 --- a/client-go/openapi/zz_generated.openapi.go +++ b/client-go/openapi/zz_generated.openapi.go @@ -129,6 +129,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/ironcore-dev/ironcore/api/storage/v1alpha1.BucketSpec": schema_ironcore_api_storage_v1alpha1_BucketSpec(ref), "github.com/ironcore-dev/ironcore/api/storage/v1alpha1.BucketStatus": schema_ironcore_api_storage_v1alpha1_BucketStatus(ref), "github.com/ironcore-dev/ironcore/api/storage/v1alpha1.BucketTemplateSpec": schema_ironcore_api_storage_v1alpha1_BucketTemplateSpec(ref), + "github.com/ironcore-dev/ironcore/api/storage/v1alpha1.EphemeralVolumeSpec": schema_ironcore_api_storage_v1alpha1_EphemeralVolumeSpec(ref), "github.com/ironcore-dev/ironcore/api/storage/v1alpha1.Volume": schema_ironcore_api_storage_v1alpha1_Volume(ref), "github.com/ironcore-dev/ironcore/api/storage/v1alpha1.VolumeAccess": schema_ironcore_api_storage_v1alpha1_VolumeAccess(ref), "github.com/ironcore-dev/ironcore/api/storage/v1alpha1.VolumeClass": schema_ironcore_api_storage_v1alpha1_VolumeClass(ref), @@ -5176,6 +5177,115 @@ func schema_ironcore_api_storage_v1alpha1_BucketTemplateSpec(ref common.Referenc } } +func schema_ironcore_api_storage_v1alpha1_EphemeralVolumeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "volumeClassRef": { + SchemaProps: spec.SchemaProps{ + Description: "VolumeClassRef is the VolumeClass of a volume If empty, an external controller has to provision the volume.", + Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), + }, + }, + "volumePoolSelector": { + SchemaProps: spec.SchemaProps{ + Description: "VolumePoolSelector selects a suitable VolumePoolRef by the given labels.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + "volumePoolRef": { + SchemaProps: spec.SchemaProps{ + Description: "VolumePoolRef indicates which VolumePool to use for a volume. If unset, the scheduler will figure out a suitable VolumePoolRef.", + Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), + }, + }, + "claimRef": { + SchemaProps: spec.SchemaProps{ + Description: "ClaimRef is the reference to the claiming entity of the Volume.", + Ref: ref("github.com/ironcore-dev/ironcore/api/common/v1alpha1.LocalUIDReference"), + }, + }, + "resources": { + SchemaProps: spec.SchemaProps{ + Description: "Resources is a description of the volume's resources and capacity.", + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Ref: ref("k8s.io/apimachinery/pkg/api/resource.Quantity"), + }, + }, + }, + }, + }, + "image": { + SchemaProps: spec.SchemaProps{ + Description: "Image is an optional image to bootstrap the volume with.", + Type: []string{"string"}, + Format: "", + }, + }, + "imagePullSecretRef": { + SchemaProps: spec.SchemaProps{ + Description: "ImagePullSecretRef is an optional secret for pulling the image of a volume.", + Ref: ref("k8s.io/api/core/v1.LocalObjectReference"), + }, + }, + "unclaimable": { + SchemaProps: spec.SchemaProps{ + Description: "Unclaimable marks the volume as unclaimable.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "tolerations": { + SchemaProps: spec.SchemaProps{ + Description: "Tolerations define tolerations the Volume has. Only any VolumePool whose taints covered by Tolerations will be considered to host the Volume.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/ironcore-dev/ironcore/api/common/v1alpha1.Toleration"), + }, + }, + }, + }, + }, + "encryption": { + SchemaProps: spec.SchemaProps{ + Description: "Encryption is an optional field which provides attributes to encrypt Volume.", + Ref: ref("github.com/ironcore-dev/ironcore/api/storage/v1alpha1.VolumeEncryption"), + }, + }, + "reclaimpolicy": { + SchemaProps: spec.SchemaProps{ + Description: "ReclaimPolicy defines the strategy for the corresponding volume resource after its managing resource has been deleted", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/ironcore-dev/ironcore/api/common/v1alpha1.LocalUIDReference", "github.com/ironcore-dev/ironcore/api/common/v1alpha1.Toleration", "github.com/ironcore-dev/ironcore/api/storage/v1alpha1.VolumeEncryption", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + } +} + func schema_ironcore_api_storage_v1alpha1_Volume(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -5951,14 +6061,14 @@ func schema_ironcore_api_storage_v1alpha1_VolumeTemplateSpec(ref common.Referenc "spec": { SchemaProps: spec.SchemaProps{ Default: map[string]interface{}{}, - Ref: ref("github.com/ironcore-dev/ironcore/api/storage/v1alpha1.VolumeSpec"), + Ref: ref("github.com/ironcore-dev/ironcore/api/storage/v1alpha1.EphemeralVolumeSpec"), }, }, }, }, }, Dependencies: []string{ - "github.com/ironcore-dev/ironcore/api/storage/v1alpha1.VolumeSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + "github.com/ironcore-dev/ironcore/api/storage/v1alpha1.EphemeralVolumeSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, } } diff --git a/internal/apis/storage/v1alpha1/zz_generated.conversion.go b/internal/apis/storage/v1alpha1/zz_generated.conversion.go index 953beaeaf..8d303ac25 100644 --- a/internal/apis/storage/v1alpha1/zz_generated.conversion.go +++ b/internal/apis/storage/v1alpha1/zz_generated.conversion.go @@ -159,6 +159,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*v1alpha1.EphemeralVolumeSpec)(nil), (*storage.EphemeralVolumeSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1alpha1_EphemeralVolumeSpec_To_storage_EphemeralVolumeSpec(a.(*v1alpha1.EphemeralVolumeSpec), b.(*storage.EphemeralVolumeSpec), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*storage.EphemeralVolumeSpec)(nil), (*v1alpha1.EphemeralVolumeSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_storage_EphemeralVolumeSpec_To_v1alpha1_EphemeralVolumeSpec(a.(*storage.EphemeralVolumeSpec), b.(*v1alpha1.EphemeralVolumeSpec), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*v1alpha1.Volume)(nil), (*storage.Volume)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha1_Volume_To_storage_Volume(a.(*v1alpha1.Volume), b.(*storage.Volume), scope) }); err != nil { @@ -638,6 +648,32 @@ func Convert_storage_BucketTemplateSpec_To_v1alpha1_BucketTemplateSpec(in *stora return autoConvert_storage_BucketTemplateSpec_To_v1alpha1_BucketTemplateSpec(in, out, s) } +func autoConvert_v1alpha1_EphemeralVolumeSpec_To_storage_EphemeralVolumeSpec(in *v1alpha1.EphemeralVolumeSpec, out *storage.EphemeralVolumeSpec, s conversion.Scope) error { + if err := Convert_v1alpha1_VolumeSpec_To_storage_VolumeSpec(&in.VolumeSpec, &out.VolumeSpec, s); err != nil { + return err + } + out.ReclaimPolicy = storage.ReclaimPolicy(in.ReclaimPolicy) + return nil +} + +// Convert_v1alpha1_EphemeralVolumeSpec_To_storage_EphemeralVolumeSpec is an autogenerated conversion function. +func Convert_v1alpha1_EphemeralVolumeSpec_To_storage_EphemeralVolumeSpec(in *v1alpha1.EphemeralVolumeSpec, out *storage.EphemeralVolumeSpec, s conversion.Scope) error { + return autoConvert_v1alpha1_EphemeralVolumeSpec_To_storage_EphemeralVolumeSpec(in, out, s) +} + +func autoConvert_storage_EphemeralVolumeSpec_To_v1alpha1_EphemeralVolumeSpec(in *storage.EphemeralVolumeSpec, out *v1alpha1.EphemeralVolumeSpec, s conversion.Scope) error { + if err := Convert_storage_VolumeSpec_To_v1alpha1_VolumeSpec(&in.VolumeSpec, &out.VolumeSpec, s); err != nil { + return err + } + out.ReclaimPolicy = v1alpha1.ReclaimPolicy(in.ReclaimPolicy) + return nil +} + +// Convert_storage_EphemeralVolumeSpec_To_v1alpha1_EphemeralVolumeSpec is an autogenerated conversion function. +func Convert_storage_EphemeralVolumeSpec_To_v1alpha1_EphemeralVolumeSpec(in *storage.EphemeralVolumeSpec, out *v1alpha1.EphemeralVolumeSpec, s conversion.Scope) error { + return autoConvert_storage_EphemeralVolumeSpec_To_v1alpha1_EphemeralVolumeSpec(in, out, s) +} + func autoConvert_v1alpha1_Volume_To_storage_Volume(in *v1alpha1.Volume, out *storage.Volume, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if err := Convert_v1alpha1_VolumeSpec_To_storage_VolumeSpec(&in.Spec, &out.Spec, s); err != nil { @@ -1014,7 +1050,7 @@ func Convert_storage_VolumeStatus_To_v1alpha1_VolumeStatus(in *storage.VolumeSta func autoConvert_v1alpha1_VolumeTemplateSpec_To_storage_VolumeTemplateSpec(in *v1alpha1.VolumeTemplateSpec, out *storage.VolumeTemplateSpec, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - if err := Convert_v1alpha1_VolumeSpec_To_storage_VolumeSpec(&in.Spec, &out.Spec, s); err != nil { + if err := Convert_v1alpha1_EphemeralVolumeSpec_To_storage_EphemeralVolumeSpec(&in.Spec, &out.Spec, s); err != nil { return err } return nil @@ -1027,7 +1063,7 @@ func Convert_v1alpha1_VolumeTemplateSpec_To_storage_VolumeTemplateSpec(in *v1alp func autoConvert_storage_VolumeTemplateSpec_To_v1alpha1_VolumeTemplateSpec(in *storage.VolumeTemplateSpec, out *v1alpha1.VolumeTemplateSpec, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta - if err := Convert_storage_VolumeSpec_To_v1alpha1_VolumeSpec(&in.Spec, &out.Spec, s); err != nil { + if err := Convert_storage_EphemeralVolumeSpec_To_v1alpha1_EphemeralVolumeSpec(&in.Spec, &out.Spec, s); err != nil { return err } return nil diff --git a/internal/apis/storage/validation/volumetemplate.go b/internal/apis/storage/validation/volumetemplate.go index 814753769..3c60ba1bf 100644 --- a/internal/apis/storage/validation/volumetemplate.go +++ b/internal/apis/storage/validation/volumetemplate.go @@ -33,7 +33,7 @@ func ValidateVolumeTemplateSpec(spec *storage.VolumeTemplateSpec, fldPath *field var allErrs field.ErrorList allErrs = append(allErrs, validateVolumeTemplateSpecMetadata(&spec.ObjectMeta, fldPath.Child("metadata"))...) - allErrs = append(allErrs, validateVolumeSpec(&spec.Spec, fldPath.Child("spec"))...) + allErrs = append(allErrs, validateVolumeSpec(&spec.Spec.VolumeSpec, fldPath.Child("spec"))...) return allErrs } diff --git a/internal/apis/storage/volume_types.go b/internal/apis/storage/volume_types.go index 9b12f60b4..d12e99f03 100644 --- a/internal/apis/storage/volume_types.go +++ b/internal/apis/storage/volume_types.go @@ -122,8 +122,24 @@ type VolumeList struct { Items []Volume } +// ReclaimPolicy for a Volume tells what to do with the volume after its managing resource has been deleted +type ReclaimPolicy string + +const ( + // The Volume resource is not deleted after the managing resource has been deleted + Retain ReclaimPolicy = "Retain" + // The Volume resource is garbage-collected when the managing resource has been deleted + Delete ReclaimPolicy = "Delete" +) + +type EphemeralVolumeSpec struct { + VolumeSpec + // ReclaimPolicy defines the strategy for the corresponding volume resource after its managing resource has been deleted + ReclaimPolicy ReclaimPolicy +} + // VolumeTemplateSpec is the specification of a Volume template. type VolumeTemplateSpec struct { metav1.ObjectMeta - Spec VolumeSpec + Spec EphemeralVolumeSpec } diff --git a/internal/apis/storage/zz_generated.deepcopy.go b/internal/apis/storage/zz_generated.deepcopy.go index f27d9ffd5..ccedf552f 100644 --- a/internal/apis/storage/zz_generated.deepcopy.go +++ b/internal/apis/storage/zz_generated.deepcopy.go @@ -371,6 +371,23 @@ func (in *BucketTemplateSpec) DeepCopy() *BucketTemplateSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EphemeralVolumeSpec) DeepCopyInto(out *EphemeralVolumeSpec) { + *out = *in + in.VolumeSpec.DeepCopyInto(&out.VolumeSpec) + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EphemeralVolumeSpec. +func (in *EphemeralVolumeSpec) DeepCopy() *EphemeralVolumeSpec { + if in == nil { + return nil + } + out := new(EphemeralVolumeSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Volume) DeepCopyInto(out *Volume) { *out = *in diff --git a/internal/controllers/compute/machine_ephemeralvolume_controller.go b/internal/controllers/compute/machine_ephemeralvolume_controller.go index 3f90aaa69..5bcf2ee4f 100644 --- a/internal/controllers/compute/machine_ephemeralvolume_controller.go +++ b/internal/controllers/compute/machine_ephemeralvolume_controller.go @@ -21,6 +21,7 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/handler" "sigs.k8s.io/controller-runtime/pkg/predicate" ) @@ -71,19 +72,29 @@ func (r *MachineEphemeralVolumeReconciler) ephemeralMachineVolumeByName(machine Labels: ephemeral.VolumeTemplate.Labels, Annotations: maps.Clone(ephemeral.VolumeTemplate.Annotations), }, - Spec: ephemeral.VolumeTemplate.Spec, + Spec: ephemeral.VolumeTemplate.Spec.VolumeSpec, } annotations.SetDefaultEphemeralManagedBy(volume) - _ = ctrl.SetControllerReference(machine, volume, r.Scheme()) + if machineVolume.Ephemeral.VolumeTemplate.Spec.ReclaimPolicy != storagev1alpha1.Retain { + _ = ctrl.SetControllerReference(machine, volume, r.Scheme()) + } res[volumeName] = volume } return res } -func (r *MachineEphemeralVolumeReconciler) handleExistingVolume(ctx context.Context, log logr.Logger, machine *computev1alpha1.Machine, shouldManage bool, volume *storagev1alpha1.Volume) error { - if annotations.IsDefaultEphemeralControlledBy(volume, machine) { +func (r *MachineEphemeralVolumeReconciler) handleExistingVolume(ctx context.Context, log logr.Logger, machine *computev1alpha1.Machine, shouldManage bool, volume *storagev1alpha1.Volume, ephemVolume *storagev1alpha1.Volume) error { + log.WithValues("Volume", klog.KObj(volume), "ShouldManage", shouldManage) + if annotations.IsDefaultEphemeralOrControlledBy(volume, machine) { if shouldManage { - log.V(1).Info("Ephemeral volume is present and controlled by machine") + log.V(1).Info("Ephemeral volume is present") + if controllerutil.HasControllerReference(ephemVolume) && !controllerutil.HasControllerReference(volume) { + baseVol := volume.DeepCopy() + _ = ctrl.SetControllerReference(machine, volume, r.Scheme()) + if err := r.Patch(ctx, volume, client.StrategicMergeFrom(baseVol, client.MergeFromWithOptimisticLock{})); err != nil { + return fmt.Errorf("error patching volume: %w", err) + } + } return nil } @@ -123,7 +134,9 @@ func (r *MachineEphemeralVolumeReconciler) handleCreateVolume(ctx context.Contex } // Treat a retrieved volume as an existing we should manage. - return r.handleExistingVolume(ctx, log, machine, true, volume) + ephemVolumeByName := r.ephemeralMachineVolumeByName(machine) + ephemVolume, shouldManage := ephemVolumeByName[volume.Name] + return r.handleExistingVolume(ctx, log, machine, shouldManage, volume, ephemVolume) } func (r *MachineEphemeralVolumeReconciler) reconcile(ctx context.Context, log logr.Logger, machine *computev1alpha1.Machine) (ctrl.Result, error) { @@ -143,10 +156,10 @@ func (r *MachineEphemeralVolumeReconciler) reconcile(ctx context.Context, log lo ) for _, volume := range volumeList.Items { volumeName := volume.Name - _, shouldManage := ephemVolumeByName[volumeName] + ephemVolume, shouldManage := ephemVolumeByName[volumeName] delete(ephemVolumeByName, volumeName) log := log.WithValues("Volume", klog.KObj(&volume), "ShouldManage", shouldManage) - if err := r.handleExistingVolume(ctx, log, machine, shouldManage, &volume); err != nil { + if err := r.handleExistingVolume(ctx, log, machine, shouldManage, &volume, ephemVolume); err != nil { errs = append(errs, err) } } diff --git a/internal/controllers/compute/machine_ephemeralvolume_controller_test.go b/internal/controllers/compute/machine_ephemeralvolume_controller_test.go index bace486be..f977313f8 100644 --- a/internal/controllers/compute/machine_ephemeralvolume_controller_test.go +++ b/internal/controllers/compute/machine_ephemeralvolume_controller_test.go @@ -10,10 +10,12 @@ import ( . "github.com/ironcore-dev/ironcore/utils/testing" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" . "sigs.k8s.io/controller-runtime/pkg/envtest/komega" ) @@ -52,7 +54,7 @@ var _ = Describe("MachineEphemeralVolumeController", func() { VolumeSource: computev1alpha1.VolumeSource{ Ephemeral: &computev1alpha1.EphemeralVolumeSource{ VolumeTemplate: &storagev1alpha1.VolumeTemplateSpec{ - Spec: storagev1alpha1.VolumeSpec{}, + Spec: storagev1alpha1.EphemeralVolumeSpec{}, }, }, }, @@ -82,6 +84,13 @@ var _ = Describe("MachineEphemeralVolumeController", func() { }, } Eventually(Get(ephemVolume)).Should(Succeed()) + By("Verifying OwnerRef is updated for ephemeral volume") + Expect(ephemVolume).Should(HaveField("ObjectMeta.OwnerReferences", ConsistOf(MatchFields(IgnoreExtras, Fields{ + "APIVersion": Equal(computev1alpha1.SchemeGroupVersion.String()), + "Kind": Equal("Machine"), + "Name": Equal(machine.Name), + })), + )) By("asserting the referenced volume still exists") Consistently(Get(refVolume)).Should(Succeed()) @@ -91,6 +100,16 @@ var _ = Describe("MachineEphemeralVolumeController", func() { }) It("should not delete externally managed volumes for a machine", func(ctx SpecContext) { + By("creating an undesired controlled volume") + externalVolume := &storagev1alpha1.Volume{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + GenerateName: "external-volume-", + }, + Spec: storagev1alpha1.VolumeSpec{}, + } + Expect(k8sClient.Create(ctx, externalVolume)).To(Succeed()) + By("creating a machine") machine := &computev1alpha1.Machine{ ObjectMeta: metav1.ObjectMeta{ @@ -99,22 +118,78 @@ var _ = Describe("MachineEphemeralVolumeController", func() { }, Spec: computev1alpha1.MachineSpec{ MachineClassRef: corev1.LocalObjectReference{Name: machineClass.Name}, + Volumes: []computev1alpha1.Volume{ + { + Name: "primary", + VolumeSource: computev1alpha1.VolumeSource{ + VolumeRef: &corev1.LocalObjectReference{Name: externalVolume.Name}, + }, + }, + }, }, } Expect(k8sClient.Create(ctx, machine)).To(Succeed()) - By("creating an undesired controlled volume") - externalVolume := &storagev1alpha1.Volume{ + By("asserting that the external volume is not being deleted") + Consistently(Object(externalVolume)).Should(HaveField("DeletionTimestamp", BeNil())) + }) + + It("verify ownerRef is set for ephemeral volumes based on reclaim policy", func(ctx SpecContext) { + By("creating a machine") + machine := &computev1alpha1.Machine{ ObjectMeta: metav1.ObjectMeta{ Namespace: ns.Name, - GenerateName: "external-volume-", + GenerateName: "machine-", + }, + Spec: computev1alpha1.MachineSpec{ + MachineClassRef: corev1.LocalObjectReference{Name: machineClass.Name}, + Volumes: []computev1alpha1.Volume{ + { + Name: "ephem-volume", + VolumeSource: computev1alpha1.VolumeSource{ + Ephemeral: &computev1alpha1.EphemeralVolumeSource{ + VolumeTemplate: &storagev1alpha1.VolumeTemplateSpec{ + Spec: storagev1alpha1.EphemeralVolumeSpec{ + ReclaimPolicy: storagev1alpha1.Retain, + }, + }, + }, + }, + }, + }, }, - Spec: storagev1alpha1.VolumeSpec{}, } - _ = ctrl.SetControllerReference(machine, externalVolume, k8sClient.Scheme()) - Expect(k8sClient.Create(ctx, externalVolume)).To(Succeed()) + Expect(k8sClient.Create(ctx, machine)).To(Succeed()) + + By("waiting for the ephemeral volume to exist") + ephemVolume := &storagev1alpha1.Volume{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns.Name, + Name: computev1alpha1.MachineEphemeralVolumeName(machine.Name, "ephem-volume"), + }, + } + Eventually(Get(ephemVolume)).Should(Succeed()) + By("Verifying OwnerRef is not set for ephemeral volume when reclaim policy is retain") + Eventually(Object(ephemVolume)).Should(HaveField("ObjectMeta.OwnerReferences", BeEmpty())) + + By("Updating reclaim policy to delete") + baseMachine := machine.DeepCopy() + machineVolumes := machine.Spec.Volumes + for _, machineVolume := range machineVolumes { + if machineVolume.Ephemeral == nil { + continue + } + machineVolume.Ephemeral.VolumeTemplate.Spec.ReclaimPolicy = storagev1alpha1.Delete + } + machine.Spec.Volumes = machineVolumes + Expect(k8sClient.Patch(ctx, machine, client.MergeFrom(baseMachine))).To(Succeed()) + By("Verifying OwnerRef is updated for ephemeral volume") + Eventually(Object(ephemVolume)).Should(HaveField("ObjectMeta.OwnerReferences", ConsistOf(MatchFields(IgnoreExtras, Fields{ + "APIVersion": Equal(computev1alpha1.SchemeGroupVersion.String()), + "Kind": Equal("Machine"), + "Name": Equal(machine.Name), + })), + )) - By("asserting that the external volume is not being deleted") - Consistently(Object(externalVolume)).Should(HaveField("DeletionTimestamp", BeNil())) }) }) diff --git a/poollet/machinepoollet/controllers/machine_controller_test.go b/poollet/machinepoollet/controllers/machine_controller_test.go index ce3439161..e2e56e643 100644 --- a/poollet/machinepoollet/controllers/machine_controller_test.go +++ b/poollet/machinepoollet/controllers/machine_controller_test.go @@ -421,7 +421,7 @@ var _ = Describe("MachineController", func() { VolumeSource: computev1alpha1.VolumeSource{ Ephemeral: &computev1alpha1.EphemeralVolumeSource{ VolumeTemplate: &storagev1alpha1.VolumeTemplateSpec{ - Spec: storagev1alpha1.VolumeSpec{}, + Spec: storagev1alpha1.EphemeralVolumeSpec{}, }, }, }, diff --git a/utils/annotations/annotations.go b/utils/annotations/annotations.go index b92eda05b..37a4bd3eb 100644 --- a/utils/annotations/annotations.go +++ b/utils/annotations/annotations.go @@ -36,6 +36,10 @@ func IsDefaultEphemeralControlledBy(o metav1.Object, owner metav1.Object) bool { return metav1.IsControlledBy(o, owner) && IsEphemeralManagedBy(o, commonv1alpha1.DefaultEphemeralManager) } +func IsDefaultEphemeralOrControlledBy(o metav1.Object, owner metav1.Object) bool { + return metav1.IsControlledBy(o, owner) || IsEphemeralManagedBy(o, commonv1alpha1.DefaultEphemeralManager) +} + func SetDefaultEphemeralManagedBy(o metav1.Object) { metautils.SetAnnotation(o, commonv1alpha1.EphemeralManagedByAnnotation, commonv1alpha1.DefaultEphemeralManager) }