diff --git a/api/v1alpha1/server_types.go b/api/v1alpha1/server_types.go
index 7fefa5f38..4d977a732 100644
--- a/api/v1alpha1/server_types.go
+++ b/api/v1alpha1/server_types.go
@@ -60,6 +60,20 @@ type ServerPortSpec struct {
PortRef *KubernetesNameRef `json:"portRef,omitempty"`
}
+// ServerBootVolumeSpec defines the boot volume for boot-from-volume server creation.
+// When specified, the server boots from this volume instead of an image.
+type ServerBootVolumeSpec struct {
+ // volumeRef is a reference to a Volume object. The volume must be
+ // bootable (created from an image) and available before server creation.
+ // +required
+ VolumeRef KubernetesNameRef `json:"volumeRef,omitempty"`
+
+ // tag is the device tag applied to the volume.
+ // +kubebuilder:validation:MaxLength:=255
+ // +optional
+ Tag *string `json:"tag,omitempty"`
+}
+
// +kubebuilder:validation:MinProperties:=1
type ServerVolumeSpec struct {
// volumeRef is a reference to a Volume object. Server creation will wait for
@@ -122,6 +136,8 @@ type ServerInterfaceStatus struct {
}
// ServerResourceSpec contains the desired state of a server
+// +kubebuilder:validation:XValidation:rule="has(self.imageRef) || has(self.bootVolume)",message="either imageRef or bootVolume must be specified"
+// +kubebuilder:validation:XValidation:rule="!(has(self.imageRef) && has(self.bootVolume))",message="imageRef and bootVolume are mutually exclusive"
type ServerResourceSpec struct {
// name will be the name of the created resource. If not specified, the
// name of the ORC object will be used.
@@ -129,16 +145,23 @@ type ServerResourceSpec struct {
Name *OpenStackName `json:"name,omitempty"`
// imageRef references the image to use for the server instance.
- // NOTE: This is not required in case of boot from volume.
- // +required
+ // This field is required unless bootVolume is specified for boot-from-volume.
+ // +optional
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="imageRef is immutable"
- ImageRef KubernetesNameRef `json:"imageRef,omitempty"`
+ ImageRef *KubernetesNameRef `json:"imageRef,omitempty"`
// flavorRef references the flavor to use for the server instance.
// +required
// +kubebuilder:validation:XValidation:rule="self == oldSelf",message="flavorRef is immutable"
FlavorRef KubernetesNameRef `json:"flavorRef,omitempty"`
+ // bootVolume specifies a volume to boot from instead of an image.
+ // When specified, imageRef must be omitted. The volume must be
+ // bootable (created from an image using imageRef in the Volume spec).
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="bootVolume is immutable"
+ BootVolume *ServerBootVolumeSpec `json:"bootVolume,omitempty"`
+
// userData specifies data which will be made available to the server at
// boot time, either via the metadata service or a config drive. It is
// typically read by a configuration service such as cloud-init or ignition.
@@ -158,12 +181,6 @@ type ServerResourceSpec struct {
// +optional
Volumes []ServerVolumeSpec `json:"volumes,omitempty"`
- // serverGroupRef is a reference to a ServerGroup object. The server
- // will be created in the server group.
- // +optional
- // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="serverGroupRef is immutable"
- ServerGroupRef *KubernetesNameRef `json:"serverGroupRef,omitempty"`
-
// availabilityZone is the availability zone in which to create the server.
// +kubebuilder:validation:MaxLength=255
// +optional
@@ -181,6 +198,90 @@ type ServerResourceSpec struct {
// +listType=set
// +optional
Tags []ServerTag `json:"tags,omitempty"`
+
+ // metadata is a list of metadata key-value pairs which will be set on the server.
+ // +kubebuilder:validation:MaxItems:=128
+ // +listType=atomic
+ // +optional
+ Metadata []ServerMetadata `json:"metadata,omitempty"`
+
+ // configDrive specifies whether to attach a config drive to the server.
+ // When true, configuration data will be available via a special drive
+ // instead of the metadata service.
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="configDrive is immutable"
+ ConfigDrive *bool `json:"configDrive,omitempty"`
+
+ // schedulerHints provides hints to the Nova scheduler for server placement.
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="schedulerHints is immutable"
+ SchedulerHints *ServerSchedulerHints `json:"schedulerHints,omitempty"`
+}
+
+// ServerSchedulerHints provides hints to the Nova scheduler for server placement.
+type ServerSchedulerHints struct {
+ // serverGroupRef is a reference to a ServerGroup object. The server will be
+ // scheduled on a host in the specified server group.
+ // +optional
+ ServerGroupRef *KubernetesNameRef `json:"serverGroupRef,omitempty"`
+
+ // differentHostServerRefs is a list of references to Server objects.
+ // The server will be scheduled on a different host than all specified servers.
+ // +listType=set
+ // +kubebuilder:validation:MaxItems:=64
+ // +optional
+ DifferentHostServerRefs []KubernetesNameRef `json:"differentHostServerRefs,omitempty"`
+
+ // sameHostServerRefs is a list of references to Server objects.
+ // The server will be scheduled on the same host as all specified servers.
+ // +listType=set
+ // +kubebuilder:validation:MaxItems:=64
+ // +optional
+ SameHostServerRefs []KubernetesNameRef `json:"sameHostServerRefs,omitempty"`
+
+ // query is a conditional statement that results in compute nodes
+ // able to host the server.
+ // +kubebuilder:validation:MaxLength:=1024
+ // +optional
+ Query *string `json:"query,omitempty"`
+
+ // targetCell is a cell name where the server will be placed.
+ // +kubebuilder:validation:MaxLength:=255
+ // +optional
+ TargetCell *string `json:"targetCell,omitempty"`
+
+ // differentCell is a list of cell names where the server should not
+ // be placed.
+ // +listType=set
+ // +kubebuilder:validation:MaxItems:=64
+ // +kubebuilder:validation:items:MaxLength=1024
+ // +optional
+ DifferentCell []string `json:"differentCell,omitempty"`
+
+ // buildNearHostIP specifies a subnet of compute nodes to host the server.
+ // +kubebuilder:validation:MaxLength:=255
+ // +optional
+ BuildNearHostIP *string `json:"buildNearHostIP,omitempty"`
+
+ // additionalProperties is a map of arbitrary key/value pairs that are
+ // not validated by Nova.
+ // +optional
+ AdditionalProperties map[string]string `json:"additionalProperties,omitempty"`
+}
+
+// ServerMetadata represents a key-value pair for server metadata.
+type ServerMetadata struct {
+ // key is the metadata key.
+ // +kubebuilder:validation:MinLength:=1
+ // +kubebuilder:validation:MaxLength:=255
+ // +required
+ Key string `json:"key,omitempty"`
+
+ // value is the metadata value.
+ // +kubebuilder:validation:MaxLength:=255
+ // +kubebuilder:validation:MinLength:=1
+ // +required
+ Value string `json:"value,omitempty"`
}
// +kubebuilder:validation:MinProperties:=1
@@ -261,4 +362,27 @@ type ServerResourceStatus struct {
// +listType=atomic
// +optional
Tags []string `json:"tags,omitempty"`
+
+ // metadata is the list of metadata key-value pairs on the resource.
+ // +kubebuilder:validation:MaxItems:=128
+ // +listType=atomic
+ // +optional
+ Metadata []ServerMetadataStatus `json:"metadata,omitempty"`
+
+ // configDrive indicates whether the server was booted with a config drive.
+ // +optional
+ ConfigDrive bool `json:"configDrive,omitempty"`
+}
+
+// ServerMetadataStatus represents a key-value pair for server metadata in status.
+type ServerMetadataStatus struct {
+ // key is the metadata key.
+ // +kubebuilder:validation:MaxLength:=255
+ // +optional
+ Key string `json:"key,omitempty"`
+
+ // value is the metadata value.
+ // +kubebuilder:validation:MaxLength:=255
+ // +optional
+ Value string `json:"value,omitempty"`
}
diff --git a/api/v1alpha1/volume_types.go b/api/v1alpha1/volume_types.go
index f50f83daa..49e2f3d06 100644
--- a/api/v1alpha1/volume_types.go
+++ b/api/v1alpha1/volume_types.go
@@ -56,6 +56,13 @@ type VolumeResourceSpec struct {
// +listType=atomic
// +optional
Metadata []VolumeMetadata `json:"metadata,omitempty"`
+
+ // imageRef is a reference to an ORC Image. If specified, creates a
+ // bootable volume from this image. The volume size must be >= the
+ // image's min_disk requirement.
+ // +optional
+ // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="imageRef is immutable"
+ ImageRef *KubernetesNameRef `json:"imageRef,omitempty"`
}
// VolumeFilter defines an existing resource by its properties
@@ -176,6 +183,11 @@ type VolumeResourceStatus struct {
// +optional
Bootable *bool `json:"bootable,omitempty"`
+ // imageID is the ID of the image this volume was created from, if any.
+ // +kubebuilder:validation:MaxLength=1024
+ // +optional
+ ImageID string `json:"imageID,omitempty"`
+
// encrypted denotes if the volume is encrypted.
// +optional
Encrypted *bool `json:"encrypted,omitempty"`
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 093e63451..01487d620 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -3834,6 +3834,26 @@ func (in *Server) DeepCopyObject() runtime.Object {
return nil
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ServerBootVolumeSpec) DeepCopyInto(out *ServerBootVolumeSpec) {
+ *out = *in
+ if in.Tag != nil {
+ in, out := &in.Tag, &out.Tag
+ *out = new(string)
+ **out = **in
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerBootVolumeSpec.
+func (in *ServerBootVolumeSpec) DeepCopy() *ServerBootVolumeSpec {
+ if in == nil {
+ return nil
+ }
+ out := new(ServerBootVolumeSpec)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServerFilter) DeepCopyInto(out *ServerFilter) {
*out = *in
@@ -4194,6 +4214,36 @@ func (in *ServerList) DeepCopyObject() runtime.Object {
return nil
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ServerMetadata) DeepCopyInto(out *ServerMetadata) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerMetadata.
+func (in *ServerMetadata) DeepCopy() *ServerMetadata {
+ if in == nil {
+ return nil
+ }
+ out := new(ServerMetadata)
+ in.DeepCopyInto(out)
+ return out
+}
+
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ServerMetadataStatus) DeepCopyInto(out *ServerMetadataStatus) {
+ *out = *in
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerMetadataStatus.
+func (in *ServerMetadataStatus) DeepCopy() *ServerMetadataStatus {
+ if in == nil {
+ return nil
+ }
+ out := new(ServerMetadataStatus)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServerPortSpec) DeepCopyInto(out *ServerPortSpec) {
*out = *in
@@ -4222,6 +4272,16 @@ func (in *ServerResourceSpec) DeepCopyInto(out *ServerResourceSpec) {
*out = new(OpenStackName)
**out = **in
}
+ if in.ImageRef != nil {
+ in, out := &in.ImageRef, &out.ImageRef
+ *out = new(KubernetesNameRef)
+ **out = **in
+ }
+ if in.BootVolume != nil {
+ in, out := &in.BootVolume, &out.BootVolume
+ *out = new(ServerBootVolumeSpec)
+ (*in).DeepCopyInto(*out)
+ }
if in.UserData != nil {
in, out := &in.UserData, &out.UserData
*out = new(UserDataSpec)
@@ -4241,11 +4301,6 @@ func (in *ServerResourceSpec) DeepCopyInto(out *ServerResourceSpec) {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
- if in.ServerGroupRef != nil {
- in, out := &in.ServerGroupRef, &out.ServerGroupRef
- *out = new(KubernetesNameRef)
- **out = **in
- }
if in.KeypairRef != nil {
in, out := &in.KeypairRef, &out.KeypairRef
*out = new(KubernetesNameRef)
@@ -4256,6 +4311,21 @@ func (in *ServerResourceSpec) DeepCopyInto(out *ServerResourceSpec) {
*out = make([]ServerTag, len(*in))
copy(*out, *in)
}
+ if in.Metadata != nil {
+ in, out := &in.Metadata, &out.Metadata
+ *out = make([]ServerMetadata, len(*in))
+ copy(*out, *in)
+ }
+ if in.ConfigDrive != nil {
+ in, out := &in.ConfigDrive, &out.ConfigDrive
+ *out = new(bool)
+ **out = **in
+ }
+ if in.SchedulerHints != nil {
+ in, out := &in.SchedulerHints, &out.SchedulerHints
+ *out = new(ServerSchedulerHints)
+ (*in).DeepCopyInto(*out)
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerResourceSpec.
@@ -4293,6 +4363,11 @@ func (in *ServerResourceStatus) DeepCopyInto(out *ServerResourceStatus) {
*out = make([]string, len(*in))
copy(*out, *in)
}
+ if in.Metadata != nil {
+ in, out := &in.Metadata, &out.Metadata
+ *out = make([]ServerMetadataStatus, len(*in))
+ copy(*out, *in)
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerResourceStatus.
@@ -4305,6 +4380,63 @@ func (in *ServerResourceStatus) DeepCopy() *ServerResourceStatus {
return out
}
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ServerSchedulerHints) DeepCopyInto(out *ServerSchedulerHints) {
+ *out = *in
+ if in.ServerGroupRef != nil {
+ in, out := &in.ServerGroupRef, &out.ServerGroupRef
+ *out = new(KubernetesNameRef)
+ **out = **in
+ }
+ if in.DifferentHostServerRefs != nil {
+ in, out := &in.DifferentHostServerRefs, &out.DifferentHostServerRefs
+ *out = make([]KubernetesNameRef, len(*in))
+ copy(*out, *in)
+ }
+ if in.SameHostServerRefs != nil {
+ in, out := &in.SameHostServerRefs, &out.SameHostServerRefs
+ *out = make([]KubernetesNameRef, len(*in))
+ copy(*out, *in)
+ }
+ if in.Query != nil {
+ in, out := &in.Query, &out.Query
+ *out = new(string)
+ **out = **in
+ }
+ if in.TargetCell != nil {
+ in, out := &in.TargetCell, &out.TargetCell
+ *out = new(string)
+ **out = **in
+ }
+ if in.DifferentCell != nil {
+ in, out := &in.DifferentCell, &out.DifferentCell
+ *out = make([]string, len(*in))
+ copy(*out, *in)
+ }
+ if in.BuildNearHostIP != nil {
+ in, out := &in.BuildNearHostIP, &out.BuildNearHostIP
+ *out = new(string)
+ **out = **in
+ }
+ if in.AdditionalProperties != nil {
+ in, out := &in.AdditionalProperties, &out.AdditionalProperties
+ *out = make(map[string]string, len(*in))
+ for key, val := range *in {
+ (*out)[key] = val
+ }
+ }
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ServerSchedulerHints.
+func (in *ServerSchedulerHints) DeepCopy() *ServerSchedulerHints {
+ if in == nil {
+ return nil
+ }
+ out := new(ServerSchedulerHints)
+ in.DeepCopyInto(out)
+ return out
+}
+
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ServerSpec) DeepCopyInto(out *ServerSpec) {
*out = *in
@@ -5175,6 +5307,11 @@ func (in *VolumeResourceSpec) DeepCopyInto(out *VolumeResourceSpec) {
*out = make([]VolumeMetadata, len(*in))
copy(*out, *in)
}
+ if in.ImageRef != nil {
+ in, out := &in.ImageRef, &out.ImageRef
+ *out = new(KubernetesNameRef)
+ **out = **in
+ }
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new VolumeResourceSpec.
diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go
index 8eab33c2d..bf2c14a45 100644
--- a/cmd/models-schema/zz_generated.openapi.go
+++ b/cmd/models-schema/zz_generated.openapi.go
@@ -160,6 +160,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupSpec": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupSpec(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.SecurityGroupStatus": schema_openstack_resource_controller_v2_api_v1alpha1_SecurityGroupStatus(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Server": schema_openstack_resource_controller_v2_api_v1alpha1_Server(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerBootVolumeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerBootVolumeSpec(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ServerFilter(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroup": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroup(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerGroupFilter": schema_openstack_resource_controller_v2_api_v1alpha1_ServerGroupFilter(ref),
@@ -175,9 +176,12 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerInterfaceFixedIP": schema_openstack_resource_controller_v2_api_v1alpha1_ServerInterfaceFixedIP(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerInterfaceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerInterfaceStatus(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerList": schema_openstack_resource_controller_v2_api_v1alpha1_ServerList(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerMetadata": schema_openstack_resource_controller_v2_api_v1alpha1_ServerMetadata(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerMetadataStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerMetadataStatus(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerPortSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerPortSpec(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerResourceSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceSpec(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceStatus(ref),
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerSchedulerHints": schema_openstack_resource_controller_v2_api_v1alpha1_ServerSchedulerHints(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerSpec(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerStatus": schema_openstack_resource_controller_v2_api_v1alpha1_ServerStatus(ref),
"github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeSpec": schema_openstack_resource_controller_v2_api_v1alpha1_ServerVolumeSpec(ref),
@@ -7434,6 +7438,34 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_Server(ref common.Refe
}
}
+func schema_openstack_resource_controller_v2_api_v1alpha1_ServerBootVolumeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "ServerBootVolumeSpec defines the boot volume for boot-from-volume server creation. When specified, the server boots from this volume instead of an image.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "volumeRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "volumeRef is a reference to a Volume object. The volume must be bootable (created from an image) and available before server creation.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "tag": {
+ SchemaProps: spec.SchemaProps{
+ Description: "tag is the device tag applied to the volume.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ Required: []string{"volumeRef"},
+ },
+ },
+ }
+}
+
func schema_openstack_resource_controller_v2_api_v1alpha1_ServerFilter(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -8079,6 +8111,61 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerList(ref common.
}
}
+func schema_openstack_resource_controller_v2_api_v1alpha1_ServerMetadata(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "ServerMetadata represents a key-value pair for server metadata.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "key": {
+ SchemaProps: spec.SchemaProps{
+ Description: "key is the metadata key.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "value": {
+ SchemaProps: spec.SchemaProps{
+ Description: "value is the metadata value.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ Required: []string{"key", "value"},
+ },
+ },
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_ServerMetadataStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "ServerMetadataStatus represents a key-value pair for server metadata in status.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "key": {
+ SchemaProps: spec.SchemaProps{
+ Description: "key is the metadata key.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "value": {
+ SchemaProps: spec.SchemaProps{
+ Description: "value is the metadata value.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
func schema_openstack_resource_controller_v2_api_v1alpha1_ServerPortSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -8114,7 +8201,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceSpec(ref
},
"imageRef": {
SchemaProps: spec.SchemaProps{
- Description: "imageRef references the image to use for the server instance. NOTE: This is not required in case of boot from volume.",
+ Description: "imageRef references the image to use for the server instance. This field is required unless bootVolume is specified for boot-from-volume.",
Type: []string{"string"},
Format: "",
},
@@ -8126,6 +8213,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceSpec(ref
Format: "",
},
},
+ "bootVolume": {
+ SchemaProps: spec.SchemaProps{
+ Description: "bootVolume specifies a volume to boot from instead of an image. When specified, imageRef must be omitted. The volume must be bootable (created from an image using imageRef in the Volume spec).",
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerBootVolumeSpec"),
+ },
+ },
"userData": {
SchemaProps: spec.SchemaProps{
Description: "userData specifies data which will be made available to the server at boot time, either via the metadata service or a config drive. It is typically read by a configuration service such as cloud-init or ignition.",
@@ -8170,13 +8263,6 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceSpec(ref
},
},
},
- "serverGroupRef": {
- SchemaProps: spec.SchemaProps{
- Description: "serverGroupRef is a reference to a ServerGroup object. The server will be created in the server group.",
- Type: []string{"string"},
- Format: "",
- },
- },
"availabilityZone": {
SchemaProps: spec.SchemaProps{
Description: "availabilityZone is the availability zone in which to create the server.",
@@ -8211,12 +8297,44 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceSpec(ref
},
},
},
+ "metadata": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "atomic",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "metadata is a list of metadata key-value pairs which will be set on the server.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerMetadata"),
+ },
+ },
+ },
+ },
+ },
+ "configDrive": {
+ SchemaProps: spec.SchemaProps{
+ Description: "configDrive specifies whether to attach a config drive to the server. When true, configuration data will be available via a special drive instead of the metadata service.",
+ Type: []string{"boolean"},
+ Format: "",
+ },
+ },
+ "schedulerHints": {
+ SchemaProps: spec.SchemaProps{
+ Description: "schedulerHints provides hints to the Nova scheduler for server placement.",
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerSchedulerHints"),
+ },
+ },
},
- Required: []string{"imageRef", "flavorRef", "ports"},
+ Required: []string{"flavorRef", "ports"},
},
},
Dependencies: []string{
- "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerPortSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserDataSpec"},
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerBootVolumeSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerMetadata", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerPortSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerSchedulerHints", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserDataSpec"},
}
}
@@ -8340,11 +8458,154 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceStatus(r
},
},
},
+ "metadata": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "atomic",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "metadata is the list of metadata key-value pairs on the resource.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: map[string]interface{}{},
+ Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerMetadataStatus"),
+ },
+ },
+ },
+ },
+ },
+ "configDrive": {
+ SchemaProps: spec.SchemaProps{
+ Description: "configDrive indicates whether the server was booted with a config drive.",
+ Type: []string{"boolean"},
+ Format: "",
+ },
+ },
},
},
},
Dependencies: []string{
- "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerInterfaceStatus", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeStatus"},
+ "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerInterfaceStatus", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerMetadataStatus", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.ServerVolumeStatus"},
+ }
+}
+
+func schema_openstack_resource_controller_v2_api_v1alpha1_ServerSchedulerHints(ref common.ReferenceCallback) common.OpenAPIDefinition {
+ return common.OpenAPIDefinition{
+ Schema: spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Description: "ServerSchedulerHints provides hints to the Nova scheduler for server placement.",
+ Type: []string{"object"},
+ Properties: map[string]spec.Schema{
+ "serverGroupRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "serverGroupRef is a reference to a ServerGroup object. The server will be scheduled on a host in the specified server group.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "differentHostServerRefs": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "differentHostServerRefs is a list of references to Server objects. The server will be scheduled on a different host than all specified servers.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "sameHostServerRefs": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "sameHostServerRefs is a list of references to Server objects. The server will be scheduled on the same host as all specified servers.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "query": {
+ SchemaProps: spec.SchemaProps{
+ Description: "query is a conditional statement that results in compute nodes able to host the server.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "targetCell": {
+ SchemaProps: spec.SchemaProps{
+ Description: "targetCell is a cell name where the server will be placed.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "differentCell": {
+ VendorExtensible: spec.VendorExtensible{
+ Extensions: spec.Extensions{
+ "x-kubernetes-list-type": "set",
+ },
+ },
+ SchemaProps: spec.SchemaProps{
+ Description: "differentCell is a list of cell names where the server should not be placed.",
+ Type: []string{"array"},
+ Items: &spec.SchemaOrArray{
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ "buildNearHostIP": {
+ SchemaProps: spec.SchemaProps{
+ Description: "buildNearHostIP specifies a subnet of compute nodes to host the server.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ "additionalProperties": {
+ SchemaProps: spec.SchemaProps{
+ Description: "additionalProperties is a map of arbitrary key/value pairs that are not validated by Nova.",
+ Type: []string{"object"},
+ AdditionalProperties: &spec.SchemaOrBool{
+ Allows: true,
+ Schema: &spec.Schema{
+ SchemaProps: spec.SchemaProps{
+ Default: "",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
}
}
@@ -9953,6 +10214,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_VolumeResourceSpec(ref
},
},
},
+ "imageRef": {
+ SchemaProps: spec.SchemaProps{
+ Description: "imageRef is a reference to an ORC Image. If specified, creates a bootable volume from this image. The volume size must be >= the image's min_disk requirement.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
},
Required: []string{"size"},
},
@@ -10084,6 +10352,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_VolumeResourceStatus(r
Format: "",
},
},
+ "imageID": {
+ SchemaProps: spec.SchemaProps{
+ Description: "imageID is the ID of the image this volume was created from, if any.",
+ Type: []string{"string"},
+ Format: "",
+ },
+ },
"encrypted": {
SchemaProps: spec.SchemaProps{
Description: "encrypted denotes if the volume is encrypted.",
diff --git a/config/crd/bases/openstack.k-orc.cloud_servers.yaml b/config/crd/bases/openstack.k-orc.cloud_servers.yaml
index c9feaab67..28ade6ac1 100644
--- a/config/crd/bases/openstack.k-orc.cloud_servers.yaml
+++ b/config/crd/bases/openstack.k-orc.cloud_servers.yaml
@@ -202,6 +202,38 @@ spec:
x-kubernetes-validations:
- message: availabilityZone is immutable
rule: self == oldSelf
+ bootVolume:
+ description: |-
+ bootVolume specifies a volume to boot from instead of an image.
+ When specified, imageRef must be omitted. The volume must be
+ bootable (created from an image using imageRef in the Volume spec).
+ properties:
+ tag:
+ description: tag is the device tag applied to the volume.
+ maxLength: 255
+ type: string
+ volumeRef:
+ description: |-
+ volumeRef is a reference to a Volume object. The volume must be
+ bootable (created from an image) and available before server creation.
+ maxLength: 253
+ minLength: 1
+ type: string
+ required:
+ - volumeRef
+ type: object
+ x-kubernetes-validations:
+ - message: bootVolume is immutable
+ rule: self == oldSelf
+ configDrive:
+ description: |-
+ configDrive specifies whether to attach a config drive to the server.
+ When true, configuration data will be available via a special drive
+ instead of the metadata service.
+ type: boolean
+ x-kubernetes-validations:
+ - message: configDrive is immutable
+ rule: self == oldSelf
flavorRef:
description: flavorRef references the flavor to use for the server
instance.
@@ -214,7 +246,7 @@ spec:
imageRef:
description: |-
imageRef references the image to use for the server instance.
- NOTE: This is not required in case of boot from volume.
+ This field is required unless bootVolume is specified for boot-from-volume.
maxLength: 253
minLength: 1
type: string
@@ -231,6 +263,30 @@ spec:
x-kubernetes-validations:
- message: keypairRef is immutable
rule: self == oldSelf
+ metadata:
+ description: metadata is a list of metadata key-value pairs which
+ will be set on the server.
+ items:
+ description: ServerMetadata represents a key-value pair for
+ server metadata.
+ properties:
+ key:
+ description: key is the metadata key.
+ maxLength: 255
+ minLength: 1
+ type: string
+ value:
+ description: value is the metadata value.
+ maxLength: 255
+ minLength: 1
+ type: string
+ required:
+ - key
+ - value
+ type: object
+ maxItems: 128
+ type: array
+ x-kubernetes-list-type: atomic
name:
description: |-
name will be the name of the created resource. If not specified, the
@@ -257,15 +313,75 @@ spec:
maxItems: 64
type: array
x-kubernetes-list-type: atomic
- serverGroupRef:
- description: |-
- serverGroupRef is a reference to a ServerGroup object. The server
- will be created in the server group.
- maxLength: 253
- minLength: 1
- type: string
+ schedulerHints:
+ description: schedulerHints provides hints to the Nova scheduler
+ for server placement.
+ properties:
+ additionalProperties:
+ additionalProperties:
+ type: string
+ description: |-
+ additionalProperties is a map of arbitrary key/value pairs that are
+ not validated by Nova.
+ type: object
+ buildNearHostIP:
+ description: buildNearHostIP specifies a subnet of compute
+ nodes to host the server.
+ maxLength: 255
+ type: string
+ differentCell:
+ description: |-
+ differentCell is a list of cell names where the server should not
+ be placed.
+ items:
+ maxLength: 1024
+ type: string
+ maxItems: 64
+ type: array
+ x-kubernetes-list-type: set
+ differentHostServerRefs:
+ description: |-
+ differentHostServerRefs is a list of references to Server objects.
+ The server will be scheduled on a different host than all specified servers.
+ items:
+ maxLength: 253
+ minLength: 1
+ type: string
+ maxItems: 64
+ type: array
+ x-kubernetes-list-type: set
+ query:
+ description: |-
+ query is a conditional statement that results in compute nodes
+ able to host the server.
+ maxLength: 1024
+ type: string
+ sameHostServerRefs:
+ description: |-
+ sameHostServerRefs is a list of references to Server objects.
+ The server will be scheduled on the same host as all specified servers.
+ items:
+ maxLength: 253
+ minLength: 1
+ type: string
+ maxItems: 64
+ type: array
+ x-kubernetes-list-type: set
+ serverGroupRef:
+ description: |-
+ serverGroupRef is a reference to a ServerGroup object. The server will be
+ scheduled on a host in the specified server group.
+ maxLength: 253
+ minLength: 1
+ type: string
+ targetCell:
+ description: targetCell is a cell name where the server will
+ be placed.
+ maxLength: 255
+ type: string
+ type: object
x-kubernetes-validations:
- - message: serverGroupRef is immutable
+ - message: schedulerHints is immutable
rule: self == oldSelf
tags:
description: tags is a list of tags which will be applied to the
@@ -321,9 +437,13 @@ spec:
x-kubernetes-list-type: atomic
required:
- flavorRef
- - imageRef
- ports
type: object
+ x-kubernetes-validations:
+ - message: either imageRef or bootVolume must be specified
+ rule: has(self.imageRef) || has(self.bootVolume)
+ - message: imageRef and bootVolume are mutually exclusive
+ rule: '!(has(self.imageRef) && has(self.bootVolume))'
required:
- cloudCredentialsRef
type: object
@@ -431,6 +551,10 @@ spec:
server is located.
maxLength: 1024
type: string
+ configDrive:
+ description: configDrive indicates whether the server was booted
+ with a config drive.
+ type: boolean
hostID:
description: hostID is the host where the server is located in
the cloud.
@@ -488,6 +612,25 @@ spec:
maxItems: 64
type: array
x-kubernetes-list-type: atomic
+ metadata:
+ description: metadata is the list of metadata key-value pairs
+ on the resource.
+ items:
+ description: ServerMetadataStatus represents a key-value pair
+ for server metadata in status.
+ properties:
+ key:
+ description: key is the metadata key.
+ maxLength: 255
+ type: string
+ value:
+ description: value is the metadata value.
+ maxLength: 255
+ type: string
+ type: object
+ maxItems: 128
+ type: array
+ x-kubernetes-list-type: atomic
name:
description: name is the human-readable name of the resource.
Might not be unique.
diff --git a/config/crd/bases/openstack.k-orc.cloud_volumes.yaml b/config/crd/bases/openstack.k-orc.cloud_volumes.yaml
index aca503047..eeaf10a8b 100644
--- a/config/crd/bases/openstack.k-orc.cloud_volumes.yaml
+++ b/config/crd/bases/openstack.k-orc.cloud_volumes.yaml
@@ -173,6 +173,17 @@ spec:
maxLength: 255
minLength: 1
type: string
+ imageRef:
+ description: |-
+ imageRef is a reference to an ORC Image. If specified, creates a
+ bootable volume from this image. The volume size must be >= the
+ image's min_disk requirement.
+ maxLength: 253
+ minLength: 1
+ type: string
+ x-kubernetes-validations:
+ - message: imageRef is immutable
+ rule: self == oldSelf
metadata:
description: |-
metadata key and value pairs to be associated with the volume.
@@ -389,6 +400,11 @@ spec:
description: host is the identifier of the host holding the volume.
maxLength: 1024
type: string
+ imageID:
+ description: imageID is the ID of the image this volume was created
+ from, if any.
+ maxLength: 1024
+ type: string
metadata:
description: metadata key and value pairs to be associated with
the volume.
diff --git a/config/samples/openstack_v1alpha1_server.yaml b/config/samples/openstack_v1alpha1_server.yaml
index 29691f536..0ee8c24ff 100644
--- a/config/samples/openstack_v1alpha1_server.yaml
+++ b/config/samples/openstack_v1alpha1_server.yaml
@@ -14,9 +14,16 @@ spec:
- portRef: server-sample
volumes:
- volumeRef: server-sample
- serverGroupRef: server-sample
keypairRef: server-sample
+ schedulerHints:
+ serverGroupRef: server-sample
availabilityZone: nova
tags:
- tag1
- tag2
+ metadata:
+ - key: environment
+ value: development
+ - key: owner
+ value: sample
+ configDrive: true
diff --git a/config/samples/openstack_v1alpha1_server_boot_from_volume.yaml b/config/samples/openstack_v1alpha1_server_boot_from_volume.yaml
new file mode 100644
index 000000000..a63d01abc
--- /dev/null
+++ b/config/samples/openstack_v1alpha1_server_boot_from_volume.yaml
@@ -0,0 +1,25 @@
+# Example of creating a server that boots from a Cinder volume instead of an image.
+# This is the boot-from-volume (BFV) pattern.
+#
+# Prerequisites:
+# - A bootable volume created from an image (see openstack_v1alpha1_volume_bootable.yaml)
+# - Network, subnet, and port resources
+# - A flavor
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Server
+metadata:
+ name: server-boot-from-volume-sample
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ # Note: No imageRef - booting from volume instead
+ bootVolume:
+ volumeRef: bootable-volume-sample
+ flavorRef: server-sample
+ ports:
+ - portRef: server-sample
+ availabilityZone: nova
diff --git a/config/samples/openstack_v1alpha1_volume_bootable.yaml b/config/samples/openstack_v1alpha1_volume_bootable.yaml
new file mode 100644
index 000000000..a1f80ba0c
--- /dev/null
+++ b/config/samples/openstack_v1alpha1_volume_bootable.yaml
@@ -0,0 +1,29 @@
+---
+# Example of creating a bootable volume from an image.
+# The volume can then be used as a boot device for a server.
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Image
+metadata:
+ name: ubuntu-2404
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ import:
+ filter:
+ name: ubuntu-24.04-server
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Volume
+metadata:
+ name: bootable-volume-sample
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ description: Bootable volume created from Ubuntu image
+ size: 50
+ imageRef: ubuntu-2404
+ volumeTypeRef: fast-ssd
diff --git a/examples/bases/boot-from-volume/kustomization.yaml b/examples/bases/boot-from-volume/kustomization.yaml
new file mode 100644
index 000000000..ce2cb1adc
--- /dev/null
+++ b/examples/bases/boot-from-volume/kustomization.yaml
@@ -0,0 +1,6 @@
+---
+apiVersion: kustomize.config.k8s.io/v1beta1
+kind: Kustomization
+resources:
+- volume.yaml
+- server.yaml
diff --git a/examples/bases/boot-from-volume/server.yaml b/examples/bases/boot-from-volume/server.yaml
new file mode 100644
index 000000000..129203407
--- /dev/null
+++ b/examples/bases/boot-from-volume/server.yaml
@@ -0,0 +1,18 @@
+---
+# Server that boots from a volume instead of an image
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Server
+metadata:
+ name: server
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: cloud-config
+ managementPolicy: managed
+ resource:
+ # No imageRef - booting from volume
+ bootVolume:
+ volumeRef: boot-volume
+ flavorRef: flavor
+ ports:
+ - portRef: port
diff --git a/examples/bases/boot-from-volume/volume.yaml b/examples/bases/boot-from-volume/volume.yaml
new file mode 100644
index 000000000..007353870
--- /dev/null
+++ b/examples/bases/boot-from-volume/volume.yaml
@@ -0,0 +1,14 @@
+---
+# Bootable volume created from the cirros image
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Volume
+metadata:
+ name: boot-volume
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: cloud-config
+ managementPolicy: managed
+ resource:
+ size: 1
+ imageRef: cirros
diff --git a/internal/controllers/server/actuator.go b/internal/controllers/server/actuator.go
index 6a2118695..9de206047 100644
--- a/internal/controllers/server/actuator.go
+++ b/internal/controllers/server/actuator.go
@@ -178,13 +178,76 @@ func (actuator serverActuator) getFlavorHelper(ctx context.Context, obj *orcv1al
}, &orcv1alpha1.Flavor{})
}
-func (actuator serverActuator) getServerGroupHelper(ctx context.Context, obj *orcv1alpha1.Server, resource *orcv1alpha1.ServerResourceSpec) (*orcv1alpha1.ServerGroup, progress.ReconcileStatus) {
- if resource.ServerGroupRef == nil {
- return &orcv1alpha1.ServerGroup{}, progress.NewReconcileStatus()
+func (actuator serverActuator) getSchedulerHintsHelper(ctx context.Context, obj *orcv1alpha1.Server, resource *orcv1alpha1.ServerResourceSpec) (servers.SchedulerHintOpts, progress.ReconcileStatus) {
+ hints := servers.SchedulerHintOpts{}
+
+ if resource.SchedulerHints == nil {
+ return hints, progress.NewReconcileStatus()
+ }
+
+ schedHints := resource.SchedulerHints
+ reconcileStatus := progress.NewReconcileStatus()
+
+ // Resolve ServerGroupRef to server group ID
+ if schedHints.ServerGroupRef != nil {
+ sg, sgReconcileStatus := getDependencyHelper(ctx, actuator.k8sClient, obj, string(*schedHints.ServerGroupRef), "ServerGroup", func(sg *orcv1alpha1.ServerGroup) bool {
+ return orcv1alpha1.IsAvailable(sg) && sg.Status.ID != nil
+ }, &orcv1alpha1.ServerGroup{})
+ reconcileStatus = reconcileStatus.WithReconcileStatus(sgReconcileStatus)
+ if sg.Status.ID != nil {
+ hints.Group = *sg.Status.ID
+ }
+ }
+
+ // Resolve differentHostServerRefs to server IDs
+ if len(schedHints.DifferentHostServerRefs) > 0 {
+ differentHost := make([]string, 0, len(schedHints.DifferentHostServerRefs))
+ for _, ref := range schedHints.DifferentHostServerRefs {
+ server, serverReconcileStatus := getDependencyHelper(ctx, actuator.k8sClient, obj, string(ref), "Server", func(s *orcv1alpha1.Server) bool {
+ return s.Status.ID != nil
+ }, &orcv1alpha1.Server{})
+ reconcileStatus = reconcileStatus.WithReconcileStatus(serverReconcileStatus)
+ if server.Status.ID != nil {
+ differentHost = append(differentHost, *server.Status.ID)
+ }
+ }
+ hints.DifferentHost = differentHost
+ }
+
+ // Resolve sameHostServerRefs to server IDs
+ if len(schedHints.SameHostServerRefs) > 0 {
+ sameHost := make([]string, 0, len(schedHints.SameHostServerRefs))
+ for _, ref := range schedHints.SameHostServerRefs {
+ server, serverReconcileStatus := getDependencyHelper(ctx, actuator.k8sClient, obj, string(ref), "Server", func(s *orcv1alpha1.Server) bool {
+ return s.Status.ID != nil
+ }, &orcv1alpha1.Server{})
+ reconcileStatus = reconcileStatus.WithReconcileStatus(serverReconcileStatus)
+ if server.Status.ID != nil {
+ sameHost = append(sameHost, *server.Status.ID)
+ }
+ }
+ hints.SameHost = sameHost
+ }
+
+ if schedHints.Query != nil {
+ hints.Query = []any{*schedHints.Query}
}
- return getDependencyHelper(ctx, actuator.k8sClient, obj, string(*resource.ServerGroupRef), "ServerGroup", func(sg *orcv1alpha1.ServerGroup) bool {
- return orcv1alpha1.IsAvailable(sg) && sg.Status.ID != nil
- }, &orcv1alpha1.ServerGroup{})
+ if schedHints.TargetCell != nil {
+ hints.TargetCell = *schedHints.TargetCell
+ }
+ hints.DifferentCell = schedHints.DifferentCell
+ if schedHints.BuildNearHostIP != nil {
+ hints.BuildNearHostIP = *schedHints.BuildNearHostIP
+ }
+ if schedHints.AdditionalProperties != nil {
+ additionalProps := make(map[string]any, len(schedHints.AdditionalProperties))
+ for k, v := range schedHints.AdditionalProperties {
+ additionalProps[k] = v
+ }
+ hints.AdditionalProperties = additionalProps
+ }
+
+ return hints, reconcileStatus
}
func (actuator serverActuator) getKeypairHelper(ctx context.Context, obj *orcv1alpha1.Server, resource *orcv1alpha1.ServerResourceSpec) (*orcv1alpha1.KeyPair, progress.ReconcileStatus) {
@@ -223,15 +286,45 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp
reconcileStatus := progress.NewReconcileStatus()
- var image *orcv1alpha1.Image
- {
+ // Determine if we're booting from volume or image
+ bootFromVolume := resource.BootVolume != nil
+
+ var imageID string
+ if !bootFromVolume {
+ // Traditional boot from image
dep, imageReconcileStatus := imageDependency.GetDependency(
ctx, actuator.k8sClient, obj, func(image *orcv1alpha1.Image) bool {
return orcv1alpha1.IsAvailable(image) && image.Status.ID != nil
},
)
reconcileStatus = reconcileStatus.WithReconcileStatus(imageReconcileStatus)
- image = dep
+ if dep != nil && dep.Status.ID != nil {
+ imageID = *dep.Status.ID
+ }
+ }
+
+ // Resolve boot volume for boot-from-volume
+ var blockDevices []servers.BlockDevice
+ if bootFromVolume {
+ bootVolume, bvReconcileStatus := bootVolumeDependency.GetDependency(
+ ctx, actuator.k8sClient, obj, func(volume *orcv1alpha1.Volume) bool {
+ return orcv1alpha1.IsAvailable(volume) && volume.Status.ID != nil
+ },
+ )
+ reconcileStatus = reconcileStatus.WithReconcileStatus(bvReconcileStatus)
+
+ if bootVolume != nil && bootVolume.Status.ID != nil {
+ bd := servers.BlockDevice{
+ SourceType: servers.SourceVolume,
+ DestinationType: servers.DestinationVolume,
+ UUID: *bootVolume.Status.ID,
+ BootIndex: 0, // Always 0 for boot volume
+ }
+ if resource.BootVolume.Tag != nil {
+ bd.Tag = *resource.BootVolume.Tag
+ }
+ blockDevices = append(blockDevices, bd)
+ }
}
flavor, flavorReconcileStatus := actuator.getFlavorHelper(ctx, obj, resource)
@@ -265,8 +358,8 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp
}
}
- serverGroup, serverGroupReconcileStatus := actuator.getServerGroupHelper(ctx, obj, resource)
- reconcileStatus = reconcileStatus.WithReconcileStatus(serverGroupReconcileStatus)
+ schedulerHints, schedulerHintsReconcileStatus := actuator.getSchedulerHintsHelper(ctx, obj, resource)
+ reconcileStatus = reconcileStatus.WithReconcileStatus(schedulerHintsReconcileStatus)
keypair, keypairReconcileStatus := actuator.getKeypairHelper(ctx, obj, resource)
reconcileStatus = reconcileStatus.WithReconcileStatus(keypairReconcileStatus)
@@ -285,14 +378,22 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp
// Sort tags before creation to simplify comparisons
slices.Sort(tags)
+ metadata := make(map[string]string)
+ for _, m := range resource.Metadata {
+ metadata[m.Key] = m.Value
+ }
+
serverCreateOpts := servers.CreateOpts{
Name: getResourceName(obj),
- ImageRef: *image.Status.ID,
+ ImageRef: imageID, // Empty string if boot-from-volume
FlavorRef: *flavor.Status.ID,
Networks: portList,
UserData: userData,
Tags: tags,
+ Metadata: metadata,
AvailabilityZone: resource.AvailabilityZone,
+ ConfigDrive: resource.ConfigDrive,
+ BlockDevice: blockDevices, // Boot volume for BFV
}
/* keypairs.CreateOptsExt was merged into servers.CreateOpts in gopher cloud V3
@@ -306,10 +407,6 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp
}
}
- schedulerHints := servers.SchedulerHintOpts{
- Group: ptr.Deref(serverGroup.Status.ID, ""),
- }
-
server, err := actuator.osClient.CreateServer(ctx, createOpts, schedulerHints)
// We should require the spec to be updated before retrying a create which returned a non-retryable error
@@ -343,6 +440,7 @@ func (actuator serverActuator) GetResourceReconcilers(ctx context.Context, orcOb
actuator.checkStatus,
actuator.updateResource,
actuator.reconcileTags,
+ actuator.reconcileMetadata,
actuator.reconcilePortAttachments,
actuator.reconcileVolumeAttachments,
}, nil
@@ -429,6 +527,39 @@ func (actuator serverActuator) reconcileTags(ctx context.Context, obj orcObjectP
return tags.ReconcileTags[orcObjectPT, osResourceT](obj.Spec.Resource.Tags, ptr.Deref(osResource.Tags, []string{}), tags.NewServerTagReplacer(actuator.osClient, osResource.ID))(ctx, obj, osResource)
}
+func (actuator serverActuator) reconcileMetadata(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus {
+ log := ctrl.LoggerFrom(ctx)
+ resource := obj.Spec.Resource
+ if resource == nil {
+ return progress.WrapError(
+ orcerrors.Terminal(orcv1alpha1.ConditionReasonInvalidConfiguration, "Update requested, but spec.resource is not set"))
+ }
+
+ // Metadata cannot be set on a server that is still building
+ if osResource.Status == "" || osResource.Status == ServerStatusBuild {
+ return progress.NewReconcileStatus().WaitingOnOpenStack(progress.WaitingOnReady, serverActivePollingPeriod)
+ }
+
+ // Build the desired metadata map from spec
+ desiredMetadata := make(map[string]string)
+ for _, m := range resource.Metadata {
+ desiredMetadata[m.Key] = m.Value
+ }
+
+ // Compare with current metadata
+ if maps.Equal(desiredMetadata, osResource.Metadata) {
+ return nil
+ }
+
+ log.V(logging.Verbose).Info("Updating server metadata")
+ _, err := actuator.osClient.ReplaceServerMetadata(ctx, osResource.ID, desiredMetadata)
+ if err != nil {
+ return progress.WrapError(err)
+ }
+
+ return progress.NeedsRefresh()
+}
+
func (actuator serverActuator) reconcilePortAttachments(ctx context.Context, obj orcObjectPT, osResource *osResourceT) progress.ReconcileStatus {
log := ctrl.LoggerFrom(ctx)
resource := obj.Spec.Resource
diff --git a/internal/controllers/server/controller.go b/internal/controllers/server/controller.go
index 95ac9f595..d5ca0f598 100644
--- a/internal/controllers/server/controller.go
+++ b/internal/controllers/server/controller.go
@@ -73,13 +73,31 @@ var (
"spec.resource.imageRef",
func(server *orcv1alpha1.Server) []string {
resource := server.Spec.Resource
- if resource == nil {
+ if resource == nil || resource.ImageRef == nil {
return nil
}
- return []string{string(resource.ImageRef)}
+ return []string{string(*resource.ImageRef)}
+ },
+ finalizer, externalObjectFieldOwner,
+ )
+
+ // bootVolumeDependency handles the boot volume specified in bootVolume for boot-from-volume.
+ // This volume is attached at server creation time as the root disk.
+ // deletion guard is in place because the server cannot boot without its root volume.
+ // OverrideDependencyName is used to avoid conflict with volumeDependency which also
+ // creates a Volume deletion guard for Server.
+ bootVolumeDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ServerList, *orcv1alpha1.Volume](
+ "spec.resource.bootVolume.volumeRef",
+ func(server *orcv1alpha1.Server) []string {
+ resource := server.Spec.Resource
+ if resource == nil || resource.BootVolume == nil {
+ return nil
+ }
+ return []string{string(resource.BootVolume.VolumeRef)}
},
finalizer, externalObjectFieldOwner,
+ dependency.OverrideDependencyName("bootvolume"),
)
portDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.ServerList, *orcv1alpha1.Port](
@@ -105,14 +123,14 @@ var (
// No deletion guard for server group, because server group can be safely deleted while
// referenced by a server
serverGroupDependency = dependency.NewDependency[*orcv1alpha1.ServerList, *orcv1alpha1.ServerGroup](
- "spec.resource.serverGroupRef",
+ "spec.resource.schedulerHints.serverGroupRef",
func(server *orcv1alpha1.Server) []string {
resource := server.Spec.Resource
- if resource == nil || resource.ServerGroupRef == nil {
+ if resource == nil || resource.SchedulerHints == nil || resource.SchedulerHints.ServerGroupRef == nil {
return nil
}
- return []string{string(*resource.ServerGroupRef)}
+ return []string{string(*resource.SchedulerHints.ServerGroupRef)}
},
)
@@ -196,6 +214,10 @@ func (c serverReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c
if err != nil {
return err
}
+ bootVolumeWatchEventHandler, err := bootVolumeDependency.WatchEventHandler(log, k8sClient)
+ if err != nil {
+ return err
+ }
builder := ctrl.NewControllerManagedBy(mgr).
WithOptions(options).
@@ -215,6 +237,9 @@ func (c serverReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c
Watches(&orcv1alpha1.Volume{}, volumeWatchEventHandler,
builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Volume{})),
).
+ Watches(&orcv1alpha1.Volume{}, bootVolumeWatchEventHandler,
+ builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Volume{})),
+ ).
Watches(&orcv1alpha1.KeyPair{}, keypairWatchEventHandler,
builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.KeyPair{})),
).
@@ -234,6 +259,7 @@ func (c serverReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c
serverGroupDependency.AddToManager(ctx, mgr),
userDataDependency.AddToManager(ctx, mgr),
volumeDependency.AddToManager(ctx, mgr),
+ bootVolumeDependency.AddToManager(ctx, mgr),
keypairDependency.AddToManager(ctx, mgr),
credentialsDependency.AddToManager(ctx, mgr),
credentials.AddCredentialsWatch(log, k8sClient, builder, credentialsDependency),
diff --git a/internal/controllers/server/status.go b/internal/controllers/server/status.go
index aa7c47ccf..39956c531 100644
--- a/internal/controllers/server/status.go
+++ b/internal/controllers/server/status.go
@@ -18,6 +18,8 @@ package server
import (
"fmt"
+ "maps"
+ "slices"
"github.com/go-logr/logr"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -69,7 +71,8 @@ func (serverStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osRes
WithHostID(osResource.HostID).
WithAvailabilityZone(osResource.AvailabilityZone).
WithServerGroups(ptr.Deref(osResource.ServerGroups, []string{})...).
- WithTags(ptr.Deref(osResource.Tags, []string{})...)
+ WithTags(ptr.Deref(osResource.Tags, []string{})...).
+ WithConfigDrive(osResource.ConfigDrive)
if imageID, ok := osResource.Image["id"]; ok {
status.WithImageID(fmt.Sprintf("%s", imageID))
@@ -97,5 +100,12 @@ func (serverStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osRes
status.WithInterfaces(interfaceStatus)
}
+ // Sort metadata keys for deterministic output
+ for _, k := range slices.Sorted(maps.Keys(osResource.Metadata)) {
+ status.WithMetadata(orcapplyconfigv1alpha1.ServerMetadataStatus().
+ WithKey(k).
+ WithValue(osResource.Metadata[k]))
+ }
+
statusApply.WithResource(status)
}
diff --git a/internal/controllers/server/tests/server-boot-from-volume/00-assert.yaml b/internal/controllers/server/tests/server-boot-from-volume/00-assert.yaml
new file mode 100644
index 000000000..4fa211ad6
--- /dev/null
+++ b/internal/controllers/server/tests/server-boot-from-volume/00-assert.yaml
@@ -0,0 +1,61 @@
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Server
+ name: server-boot-from-volume
+ ref: server
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Volume
+ name: server-boot-from-volume
+ ref: volume
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Port
+ name: server-boot-from-volume
+ ref: port
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Network
+ name: server-boot-from-volume
+ ref: network
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Subnet
+ name: server-boot-from-volume
+ ref: subnet
+assertAll:
+ - celExpr: "server.status.resource.hostID != ''"
+ - celExpr: "server.status.resource.availabilityZone != ''"
+ # Verify the server booted from volume (imageID may be empty for BFV servers)
+ - celExpr: "port.status.resource.deviceID == server.status.id"
+ - celExpr: "port.status.resource.status == 'ACTIVE'"
+ - celExpr: "size(server.status.resource.interfaces) == 1"
+ - celExpr: "server.status.resource.interfaces[0].portID == port.status.id"
+ - celExpr: "server.status.resource.interfaces[0].netID == network.status.id"
+ - celExpr: "server.status.resource.interfaces[0].macAddr != ''"
+ - celExpr: "server.status.resource.interfaces[0].portState != ''"
+ - celExpr: "size(server.status.resource.interfaces[0].fixedIPs) >= 1"
+ - celExpr: "server.status.resource.interfaces[0].fixedIPs[0].ipAddress != ''"
+ - celExpr: "server.status.resource.interfaces[0].fixedIPs[0].subnetID == subnet.status.id"
+ # Verify volume is bootable
+ - celExpr: "volume.status.resource.bootable == true"
+ # Verify volume is attached to the server
+ - celExpr: "size(volume.status.resource.attachments) == 1"
+ - celExpr: "volume.status.resource.attachments[0].serverID == server.status.id"
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Server
+metadata:
+ name: server-boot-from-volume
+status:
+ resource:
+ name: server-boot-from-volume
+ status: ACTIVE
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Volume
+metadata:
+ name: server-boot-from-volume
+status:
+ resource:
+ bootable: true
+ status: in-use
diff --git a/internal/controllers/server/tests/server-boot-from-volume/00-create-resource.yaml b/internal/controllers/server/tests/server-boot-from-volume/00-create-resource.yaml
new file mode 100644
index 000000000..e14a11c45
--- /dev/null
+++ b/internal/controllers/server/tests/server-boot-from-volume/00-create-resource.yaml
@@ -0,0 +1,30 @@
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Port
+metadata:
+ name: server-boot-from-volume
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: server-boot-from-volume
+ addresses:
+ - subnetRef: server-boot-from-volume
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Server
+metadata:
+ name: server-boot-from-volume
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ # Note: No imageRef - booting from volume!
+ bootVolume:
+ volumeRef: server-boot-from-volume
+ flavorRef: server-boot-from-volume
+ ports:
+ - portRef: server-boot-from-volume
diff --git a/internal/controllers/server/tests/server-boot-from-volume/00-prerequisites.yaml b/internal/controllers/server/tests/server-boot-from-volume/00-prerequisites.yaml
new file mode 100644
index 000000000..a75dc4d64
--- /dev/null
+++ b/internal/controllers/server/tests/server-boot-from-volume/00-prerequisites.yaml
@@ -0,0 +1,63 @@
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
+ - script: |
+ export E2E_KUTTL_CURRENT_TEST=server-boot-from-volume
+ cat ../templates/create-flavor.tmpl | envsubst | kubectl -n ${NAMESPACE} apply -f -
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Image
+metadata:
+ name: server-boot-from-volume
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack-admin
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ content:
+ diskFormat: qcow2
+ download:
+ url: https://github.com/k-orc/openstack-resource-controller/raw/2ddc1857f5e22d2f0df6f5ee033353e4fd907121/internal/controllers/image/testdata/cirros-0.6.3-x86_64-disk.img
+ visibility: public
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Network
+metadata:
+ name: server-boot-from-volume
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ name: server-boot-from-volume
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Subnet
+metadata:
+ name: server-boot-from-volume
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ networkRef: server-boot-from-volume
+ ipVersion: 4
+ cidr: 192.168.201.0/24
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Volume
+metadata:
+ name: server-boot-from-volume
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ size: 1
+ imageRef: server-boot-from-volume
diff --git a/internal/controllers/server/tests/server-boot-from-volume/README.md b/internal/controllers/server/tests/server-boot-from-volume/README.md
new file mode 100644
index 000000000..0a26653ad
--- /dev/null
+++ b/internal/controllers/server/tests/server-boot-from-volume/README.md
@@ -0,0 +1,14 @@
+# Boot from Volume Test
+
+This test creates a server that boots from a Cinder volume instead of an
+image. This is the boot-from-volume (BFV) pattern where:
+
+1. An image is created
+2. A bootable volume is created from that image
+3. A server is created booting from the volume (no imageRef)
+
+The test verifies:
+- Server reaches ACTIVE state
+- Volume is marked as bootable
+- Volume is attached to the server
+- Port is attached to the server
diff --git a/internal/controllers/server/tests/server-create-full/00-assert.yaml b/internal/controllers/server/tests/server-create-full/00-assert.yaml
index 5f351d6d1..68c65c73b 100644
--- a/internal/controllers/server/tests/server-create-full/00-assert.yaml
+++ b/internal/controllers/server/tests/server-create-full/00-assert.yaml
@@ -66,3 +66,9 @@ status:
tags:
- tag1
- tag2
+ metadata:
+ - key: environment
+ value: test
+ - key: owner
+ value: kuttl
+ configDrive: true
diff --git a/internal/controllers/server/tests/server-create-full/00-create-resource.yaml b/internal/controllers/server/tests/server-create-full/00-create-resource.yaml
index 006b18145..28b64f532 100644
--- a/internal/controllers/server/tests/server-create-full/00-create-resource.yaml
+++ b/internal/controllers/server/tests/server-create-full/00-create-resource.yaml
@@ -40,11 +40,18 @@ spec:
flavorRef: server-create-full
ports:
- portRef: server-create-full
- serverGroupRef: server-create-full
keypairRef: server-create-full
+ schedulerHints:
+ serverGroupRef: server-create-full
volumes:
- volumeRef: server-create-full
availabilityZone: nova
tags:
- tag1
- tag2
+ metadata:
+ - key: environment
+ value: test
+ - key: owner
+ value: kuttl
+ configDrive: true
diff --git a/internal/controllers/server/tests/server-dependency/00-create-everything-but-flavor.yaml b/internal/controllers/server/tests/server-dependency/00-create-everything-but-flavor.yaml
index 101976fcf..ae93fc454 100644
--- a/internal/controllers/server/tests/server-dependency/00-create-everything-but-flavor.yaml
+++ b/internal/controllers/server/tests/server-dependency/00-create-everything-but-flavor.yaml
@@ -87,6 +87,7 @@ spec:
flavorRef: server-dependency
ports:
- portRef: server-dependency
- serverGroupRef: server-dependency
+ schedulerHints:
+ serverGroupRef: server-dependency
userData:
secretRef: server-dependency
\ No newline at end of file
diff --git a/internal/controllers/server/tests/server-dependency/01-create-everything-but-image.yaml b/internal/controllers/server/tests/server-dependency/01-create-everything-but-image.yaml
index 5757e4eea..a669f622f 100644
--- a/internal/controllers/server/tests/server-dependency/01-create-everything-but-image.yaml
+++ b/internal/controllers/server/tests/server-dependency/01-create-everything-but-image.yaml
@@ -27,6 +27,7 @@ spec:
flavorRef: server-dependency
ports:
- portRef: server-dependency
- serverGroupRef: server-dependency
+ schedulerHints:
+ serverGroupRef: server-dependency
userData:
secretRef: server-dependency
diff --git a/internal/controllers/server/tests/server-dependency/02-create-everything-but-port.yaml b/internal/controllers/server/tests/server-dependency/02-create-everything-but-port.yaml
index 4dd1e19b0..45f5348cc 100644
--- a/internal/controllers/server/tests/server-dependency/02-create-everything-but-port.yaml
+++ b/internal/controllers/server/tests/server-dependency/02-create-everything-but-port.yaml
@@ -38,6 +38,7 @@ spec:
flavorRef: server-dependency
ports:
- portRef: server-dependency
- serverGroupRef: server-dependency
+ schedulerHints:
+ serverGroupRef: server-dependency
userData:
secretRef: server-dependency
diff --git a/internal/controllers/server/tests/server-dependency/03-create-everything-but-server-group.yaml b/internal/controllers/server/tests/server-dependency/03-create-everything-but-server-group.yaml
index 6483cf47f..f15e8960e 100644
--- a/internal/controllers/server/tests/server-dependency/03-create-everything-but-server-group.yaml
+++ b/internal/controllers/server/tests/server-dependency/03-create-everything-but-server-group.yaml
@@ -37,6 +37,7 @@ spec:
flavorRef: server-dependency
ports:
- portRef: server-dependency
- serverGroupRef: server-dependency
+ schedulerHints:
+ serverGroupRef: server-dependency
userData:
secretRef: server-dependency
diff --git a/internal/controllers/server/tests/server-dependency/04-create-everything-but-userdata-secret.yaml b/internal/controllers/server/tests/server-dependency/04-create-everything-but-userdata-secret.yaml
index bc9196a80..f8b8b4d02 100644
--- a/internal/controllers/server/tests/server-dependency/04-create-everything-but-userdata-secret.yaml
+++ b/internal/controllers/server/tests/server-dependency/04-create-everything-but-userdata-secret.yaml
@@ -35,6 +35,7 @@ spec:
flavorRef: server-dependency
ports:
- portRef: server-dependency
- serverGroupRef: server-dependency
+ schedulerHints:
+ serverGroupRef: server-dependency
userData:
secretRef: server-dependency
diff --git a/internal/controllers/server/tests/server-dependency/05-create-everything-but-keypair.yaml b/internal/controllers/server/tests/server-dependency/05-create-everything-but-keypair.yaml
index eb4776259..031ed37ac 100644
--- a/internal/controllers/server/tests/server-dependency/05-create-everything-but-keypair.yaml
+++ b/internal/controllers/server/tests/server-dependency/05-create-everything-but-keypair.yaml
@@ -27,7 +27,8 @@ spec:
flavorRef: server-dependency
ports:
- portRef: server-dependency
- serverGroupRef: server-dependency
+ schedulerHints:
+ serverGroupRef: server-dependency
keypairRef: server-dependency
userData:
secretRef: server-dependency
diff --git a/internal/controllers/server/tests/server-update/00-assert.yaml b/internal/controllers/server/tests/server-update/00-assert.yaml
index 551244650..6964361e5 100644
--- a/internal/controllers/server/tests/server-update/00-assert.yaml
+++ b/internal/controllers/server/tests/server-update/00-assert.yaml
@@ -24,6 +24,7 @@ assertAll:
- celExpr: "server.status.resource.serverGroups[0] == sg.status.id"
- celExpr: "!has(server.status.resource.tags)"
- celExpr: "!has(server.status.resource.volumes)"
+ - celExpr: "!has(server.status.resource.metadata)"
- celExpr: "size(server.status.resource.interfaces) == 1"
- celExpr: "server.status.resource.interfaces[0].portID == port.status.id"
---
diff --git a/internal/controllers/server/tests/server-update/00-minimal-resource.yaml b/internal/controllers/server/tests/server-update/00-minimal-resource.yaml
index 4a62a151a..95dca9e29 100644
--- a/internal/controllers/server/tests/server-update/00-minimal-resource.yaml
+++ b/internal/controllers/server/tests/server-update/00-minimal-resource.yaml
@@ -13,4 +13,5 @@ spec:
flavorRef: server-update
ports:
- portRef: server-update
- serverGroupRef: server-update
\ No newline at end of file
+ schedulerHints:
+ serverGroupRef: server-update
\ No newline at end of file
diff --git a/internal/controllers/server/tests/server-update/01-assert.yaml b/internal/controllers/server/tests/server-update/01-assert.yaml
index 473aecab0..db83497d8 100644
--- a/internal/controllers/server/tests/server-update/01-assert.yaml
+++ b/internal/controllers/server/tests/server-update/01-assert.yaml
@@ -54,6 +54,11 @@ status:
tags:
- tag1
- tag2
+ metadata:
+ - key: environment
+ value: staging
+ - key: team
+ value: platform
conditions:
- type: Available
status: "True"
diff --git a/internal/controllers/server/tests/server-update/01-updated-resource.yaml b/internal/controllers/server/tests/server-update/01-updated-resource.yaml
index 248b328a2..ae0cac6df 100644
--- a/internal/controllers/server/tests/server-update/01-updated-resource.yaml
+++ b/internal/controllers/server/tests/server-update/01-updated-resource.yaml
@@ -44,3 +44,8 @@ spec:
tags:
- tag1
- tag2
+ metadata:
+ - key: environment
+ value: staging
+ - key: team
+ value: platform
diff --git a/internal/controllers/server/tests/server-update/02-assert.yaml b/internal/controllers/server/tests/server-update/02-assert.yaml
index 68beeb722..ec2db2777 100644
--- a/internal/controllers/server/tests/server-update/02-assert.yaml
+++ b/internal/controllers/server/tests/server-update/02-assert.yaml
@@ -32,6 +32,7 @@ assertAll:
- celExpr: "server.status.resource.serverGroups[0] == sg.status.id"
- celExpr: "!has(server.status.resource.tags)"
- celExpr: "!has(server.status.resource.volumes)"
+ - celExpr: "!has(server.status.resource.metadata)"
- celExpr: "!has(volume.status.resource.attachments)"
- celExpr: "port1.status.resource.deviceID == server.status.id"
- celExpr: "port1.status.resource.status == 'ACTIVE'"
diff --git a/internal/controllers/volume/actuator.go b/internal/controllers/volume/actuator.go
index 4e1e238f9..c1ec4335c 100644
--- a/internal/controllers/volume/actuator.go
+++ b/internal/controllers/volume/actuator.go
@@ -165,6 +165,20 @@ func (actuator volumeActuator) CreateResource(ctx context.Context, obj orcObject
}
}
+ // Resolve image dependency for bootable volumes
+ var imageID string
+ if resource.ImageRef != nil {
+ image, imageDepRS := imageDependency.GetDependency(
+ ctx, actuator.k8sClient, obj, func(dep *orcv1alpha1.Image) bool {
+ return orcv1alpha1.IsAvailable(dep) && dep.Status.ID != nil
+ },
+ )
+ reconcileStatus = reconcileStatus.WithReconcileStatus(imageDepRS)
+ if image != nil {
+ imageID = ptr.Deref(image.Status.ID, "")
+ }
+ }
+
if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule {
return nil, reconcileStatus
}
@@ -181,6 +195,7 @@ func (actuator volumeActuator) CreateResource(ctx context.Context, obj orcObject
Metadata: metadata,
VolumeType: volumetypeID,
AvailabilityZone: resource.AvailabilityZone,
+ ImageID: imageID,
}
osResource, err := actuator.osClient.CreateVolume(ctx, createOpts)
diff --git a/internal/controllers/volume/controller.go b/internal/controllers/volume/controller.go
index 276c4236c..531efbf3e 100644
--- a/internal/controllers/volume/controller.go
+++ b/internal/controllers/volume/controller.go
@@ -74,6 +74,18 @@ var volumetypeDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.Vo
finalizer, externalObjectFieldOwner,
)
+var imageDependency = dependency.NewDeletionGuardDependency[*orcv1alpha1.VolumeList, *orcv1alpha1.Image](
+ "spec.resource.imageRef",
+ func(volume *orcv1alpha1.Volume) []string {
+ resource := volume.Spec.Resource
+ if resource == nil || resource.ImageRef == nil {
+ return nil
+ }
+ return []string{string(*resource.ImageRef)}
+ },
+ finalizer, externalObjectFieldOwner,
+)
+
// serverToVolumeMapFunc creates a mapping function that reconciles volumes when:
// - a volume ID appears in server status but the volume doesn't have attachment info for that server
// - a volume has attachment info for a server, but the server no longer lists that volume
@@ -209,11 +221,19 @@ func (c volumeReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c
return err
}
+ imageWatchEventHandler, err := imageDependency.WatchEventHandler(log, k8sClient)
+ if err != nil {
+ return err
+ }
+
builder := ctrl.NewControllerManagedBy(mgr).
WithOptions(options).
Watches(&orcv1alpha1.VolumeType{}, volumetypeWatchEventHandler,
builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.VolumeType{})),
).
+ Watches(&orcv1alpha1.Image{}, imageWatchEventHandler,
+ builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Image{})),
+ ).
Watches(&orcv1alpha1.Server{}, handler.EnqueueRequestsFromMapFunc(serverToVolumeMapFunc(ctx, k8sClient)),
builder.WithPredicates(predicates.NewServerVolumesChanged(log)),
).
@@ -221,6 +241,7 @@ func (c volumeReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c
if err := errors.Join(
volumetypeDependency.AddToManager(ctx, mgr),
+ imageDependency.AddToManager(ctx, mgr),
credentialsDependency.AddToManager(ctx, mgr),
credentials.AddCredentialsWatch(log, mgr.GetClient(), builder, credentialsDependency),
); err != nil {
diff --git a/internal/controllers/volume/status.go b/internal/controllers/volume/status.go
index 064ef7575..96de129be 100644
--- a/internal/controllers/volume/status.go
+++ b/internal/controllers/volume/status.go
@@ -92,6 +92,13 @@ func (volumeStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osRes
}
}
+ // Extract image ID from volume_image_metadata if present.
+ // When a volume is created from an image, OpenStack stores the source
+ // image ID in the volume's metadata under "image_id".
+ if imageID, ok := osResource.VolumeImageMetadata["image_id"]; ok {
+ resourceStatus.WithImageID(imageID)
+ }
+
for k, v := range osResource.Metadata {
resourceStatus.WithMetadata(orcapplyconfigv1alpha1.VolumeMetadataStatus().
WithName(k).
diff --git a/internal/controllers/volume/tests/volume-create-bootable/00-assert.yaml b/internal/controllers/volume/tests/volume-create-bootable/00-assert.yaml
new file mode 100644
index 000000000..c9444c53c
--- /dev/null
+++ b/internal/controllers/volume/tests/volume-create-bootable/00-assert.yaml
@@ -0,0 +1,48 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Image
+metadata:
+ name: volume-create-bootable-image
+status:
+ resource:
+ status: active
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Volume
+metadata:
+ name: volume-create-bootable
+status:
+ resource:
+ name: volume-create-bootable
+ size: 1
+ status: available
+ bootable: true
+ encrypted: false
+ multiattach: false
+ conditions:
+ - type: Available
+ status: "True"
+ reason: Success
+ - type: Progressing
+ status: "False"
+ reason: Success
+---
+apiVersion: kuttl.dev/v1beta1
+kind: TestAssert
+resourceRefs:
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Volume
+ name: volume-create-bootable
+ ref: volume
+ - apiVersion: openstack.k-orc.cloud/v1alpha1
+ kind: Image
+ name: volume-create-bootable-image
+ ref: image
+assertAll:
+ - celExpr: "volume.status.id != ''"
+ - celExpr: "volume.status.resource.tenantID != ''"
+ - celExpr: "volume.status.resource.userID != ''"
+ - celExpr: "volume.status.resource.volumeType != ''"
+ - celExpr: "volume.status.resource.createdAt != ''"
+ - celExpr: "volume.status.resource.updatedAt != ''"
+ - celExpr: "volume.status.resource.imageID == image.status.id"
diff --git a/internal/controllers/volume/tests/volume-create-bootable/00-create-resource.yaml b/internal/controllers/volume/tests/volume-create-bootable/00-create-resource.yaml
new file mode 100644
index 000000000..7a20ca9d6
--- /dev/null
+++ b/internal/controllers/volume/tests/volume-create-bootable/00-create-resource.yaml
@@ -0,0 +1,28 @@
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Image
+metadata:
+ name: volume-create-bootable-image
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ content:
+ diskFormat: raw
+ download:
+ url: https://github.com/k-orc/openstack-resource-controller/raw/690b760f49dfb61b173755e91cb51ed42472c7f3/internal/controllers/image/testdata/raw.img
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Volume
+metadata:
+ name: volume-create-bootable
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ size: 1
+ imageRef: volume-create-bootable-image
diff --git a/internal/controllers/volume/tests/volume-create-bootable/00-secret.yaml b/internal/controllers/volume/tests/volume-create-bootable/00-secret.yaml
new file mode 100644
index 000000000..f0fb63e85
--- /dev/null
+++ b/internal/controllers/volume/tests/volume-create-bootable/00-secret.yaml
@@ -0,0 +1,5 @@
+apiVersion: kuttl.dev/v1beta1
+kind: TestStep
+commands:
+ - command: kubectl create secret generic openstack-clouds --from-file=clouds.yaml=${E2E_KUTTL_OSCLOUDS} ${E2E_KUTTL_CACERT_OPT}
+ namespaced: true
diff --git a/internal/controllers/volume/tests/volume-dependency/00-assert.yaml b/internal/controllers/volume/tests/volume-dependency/00-assert.yaml
index 92782c001..7461a8dab 100644
--- a/internal/controllers/volume/tests/volume-dependency/00-assert.yaml
+++ b/internal/controllers/volume/tests/volume-dependency/00-assert.yaml
@@ -28,3 +28,18 @@ status:
message: Waiting for VolumeType/volume-dependency to be created
status: "True"
reason: Progressing
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Volume
+metadata:
+ name: volume-dependency-no-image
+status:
+ conditions:
+ - type: Available
+ message: Waiting for Image/volume-dependency-missing-image to be created
+ status: "False"
+ reason: Progressing
+ - type: Progressing
+ message: Waiting for Image/volume-dependency-missing-image to be created
+ status: "True"
+ reason: Progressing
diff --git a/internal/controllers/volume/tests/volume-dependency/00-create-resources-missing-deps.yaml b/internal/controllers/volume/tests/volume-dependency/00-create-resources-missing-deps.yaml
index ac339b291..71224d5ff 100644
--- a/internal/controllers/volume/tests/volume-dependency/00-create-resources-missing-deps.yaml
+++ b/internal/controllers/volume/tests/volume-dependency/00-create-resources-missing-deps.yaml
@@ -23,3 +23,16 @@ spec:
managementPolicy: managed
resource:
size: 1
+---
+apiVersion: openstack.k-orc.cloud/v1alpha1
+kind: Volume
+metadata:
+ name: volume-dependency-no-image
+spec:
+ cloudCredentialsRef:
+ cloudName: openstack
+ secretName: openstack-clouds
+ managementPolicy: managed
+ resource:
+ size: 1
+ imageRef: volume-dependency-missing-image
diff --git a/internal/osclients/compute.go b/internal/osclients/compute.go
index e40154150..43f4396d5 100644
--- a/internal/osclients/compute.go
+++ b/internal/osclients/compute.go
@@ -72,6 +72,7 @@ type ComputeClient interface {
DeleteAttachedInterface(ctx context.Context, serverID, portID string) error
ReplaceAllServerAttributesTags(ctx context.Context, resourceID string, opts tags.ReplaceAllOptsBuilder) ([]string, error)
+ ReplaceServerMetadata(ctx context.Context, serverID string, opts servers.MetadataOpts) (map[string]string, error)
}
type computeClient struct{ client *gophercloud.ServiceClient }
@@ -187,6 +188,10 @@ func (c computeClient) ReplaceAllServerAttributesTags(ctx context.Context, resou
return tags.ReplaceAll(ctx, c.client, resourceID, opts).Extract()
}
+func (c computeClient) ReplaceServerMetadata(ctx context.Context, serverID string, opts servers.MetadataOpts) (map[string]string, error) {
+ return servers.ResetMetadata(ctx, c.client, serverID, opts).Extract()
+}
+
type computeErrorClient struct{ error }
// NewComputeErrorClient returns a ComputeClient in which every method returns the given error.
@@ -275,3 +280,7 @@ func (e computeErrorClient) DeleteAttachedInterface(_ context.Context, _, _ stri
func (e computeErrorClient) ReplaceAllServerAttributesTags(_ context.Context, _ string, _ tags.ReplaceAllOptsBuilder) ([]string, error) {
return nil, e.error
}
+
+func (e computeErrorClient) ReplaceServerMetadata(_ context.Context, _ string, _ servers.MetadataOpts) (map[string]string, error) {
+ return nil, e.error
+}
diff --git a/internal/osclients/mock/compute.go b/internal/osclients/mock/compute.go
index c22ab6984..f3e185299 100644
--- a/internal/osclients/mock/compute.go
+++ b/internal/osclients/mock/compute.go
@@ -324,6 +324,21 @@ func (mr *MockComputeClientMockRecorder) ReplaceAllServerAttributesTags(ctx, res
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReplaceAllServerAttributesTags", reflect.TypeOf((*MockComputeClient)(nil).ReplaceAllServerAttributesTags), ctx, resourceID, opts)
}
+// ReplaceServerMetadata mocks base method.
+func (m *MockComputeClient) ReplaceServerMetadata(ctx context.Context, serverID string, opts servers.MetadataOpts) (map[string]string, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "ReplaceServerMetadata", ctx, serverID, opts)
+ ret0, _ := ret[0].(map[string]string)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// ReplaceServerMetadata indicates an expected call of ReplaceServerMetadata.
+func (mr *MockComputeClientMockRecorder) ReplaceServerMetadata(ctx, serverID, opts any) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReplaceServerMetadata", reflect.TypeOf((*MockComputeClient)(nil).ReplaceServerMetadata), ctx, serverID, opts)
+}
+
// UpdateServer mocks base method.
func (m *MockComputeClient) UpdateServer(ctx context.Context, id string, opts servers.UpdateOptsBuilder) (*servers.Server, error) {
m.ctrl.T.Helper()
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverbootvolumespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverbootvolumespec.go
new file mode 100644
index 000000000..e265034b8
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverbootvolumespec.go
@@ -0,0 +1,52 @@
+/*
+Copyright 2025 The ORC Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+)
+
+// ServerBootVolumeSpecApplyConfiguration represents a declarative configuration of the ServerBootVolumeSpec type for use
+// with apply.
+type ServerBootVolumeSpecApplyConfiguration struct {
+ VolumeRef *apiv1alpha1.KubernetesNameRef `json:"volumeRef,omitempty"`
+ Tag *string `json:"tag,omitempty"`
+}
+
+// ServerBootVolumeSpecApplyConfiguration constructs a declarative configuration of the ServerBootVolumeSpec type for use with
+// apply.
+func ServerBootVolumeSpec() *ServerBootVolumeSpecApplyConfiguration {
+ return &ServerBootVolumeSpecApplyConfiguration{}
+}
+
+// WithVolumeRef sets the VolumeRef 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 VolumeRef field is set to the value of the last call.
+func (b *ServerBootVolumeSpecApplyConfiguration) WithVolumeRef(value apiv1alpha1.KubernetesNameRef) *ServerBootVolumeSpecApplyConfiguration {
+ b.VolumeRef = &value
+ return b
+}
+
+// WithTag sets the Tag 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 Tag field is set to the value of the last call.
+func (b *ServerBootVolumeSpecApplyConfiguration) WithTag(value string) *ServerBootVolumeSpecApplyConfiguration {
+ b.Tag = &value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servermetadata.go b/pkg/clients/applyconfiguration/api/v1alpha1/servermetadata.go
new file mode 100644
index 000000000..796ac43ea
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/servermetadata.go
@@ -0,0 +1,48 @@
+/*
+Copyright 2025 The ORC Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+// ServerMetadataApplyConfiguration represents a declarative configuration of the ServerMetadata type for use
+// with apply.
+type ServerMetadataApplyConfiguration struct {
+ Key *string `json:"key,omitempty"`
+ Value *string `json:"value,omitempty"`
+}
+
+// ServerMetadataApplyConfiguration constructs a declarative configuration of the ServerMetadata type for use with
+// apply.
+func ServerMetadata() *ServerMetadataApplyConfiguration {
+ return &ServerMetadataApplyConfiguration{}
+}
+
+// WithKey sets the Key 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 Key field is set to the value of the last call.
+func (b *ServerMetadataApplyConfiguration) WithKey(value string) *ServerMetadataApplyConfiguration {
+ b.Key = &value
+ return b
+}
+
+// WithValue sets the Value 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 Value field is set to the value of the last call.
+func (b *ServerMetadataApplyConfiguration) WithValue(value string) *ServerMetadataApplyConfiguration {
+ b.Value = &value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/servermetadatastatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/servermetadatastatus.go
new file mode 100644
index 000000000..292dc8045
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/servermetadatastatus.go
@@ -0,0 +1,48 @@
+/*
+Copyright 2025 The ORC Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+// ServerMetadataStatusApplyConfiguration represents a declarative configuration of the ServerMetadataStatus type for use
+// with apply.
+type ServerMetadataStatusApplyConfiguration struct {
+ Key *string `json:"key,omitempty"`
+ Value *string `json:"value,omitempty"`
+}
+
+// ServerMetadataStatusApplyConfiguration constructs a declarative configuration of the ServerMetadataStatus type for use with
+// apply.
+func ServerMetadataStatus() *ServerMetadataStatusApplyConfiguration {
+ return &ServerMetadataStatusApplyConfiguration{}
+}
+
+// WithKey sets the Key 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 Key field is set to the value of the last call.
+func (b *ServerMetadataStatusApplyConfiguration) WithKey(value string) *ServerMetadataStatusApplyConfiguration {
+ b.Key = &value
+ return b
+}
+
+// WithValue sets the Value 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 Value field is set to the value of the last call.
+func (b *ServerMetadataStatusApplyConfiguration) WithValue(value string) *ServerMetadataStatusApplyConfiguration {
+ b.Value = &value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go
index 5233713da..5e123f453 100644
--- a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go
@@ -25,16 +25,19 @@ import (
// ServerResourceSpecApplyConfiguration represents a declarative configuration of the ServerResourceSpec type for use
// with apply.
type ServerResourceSpecApplyConfiguration struct {
- Name *apiv1alpha1.OpenStackName `json:"name,omitempty"`
- ImageRef *apiv1alpha1.KubernetesNameRef `json:"imageRef,omitempty"`
- FlavorRef *apiv1alpha1.KubernetesNameRef `json:"flavorRef,omitempty"`
- UserData *UserDataSpecApplyConfiguration `json:"userData,omitempty"`
- Ports []ServerPortSpecApplyConfiguration `json:"ports,omitempty"`
- Volumes []ServerVolumeSpecApplyConfiguration `json:"volumes,omitempty"`
- ServerGroupRef *apiv1alpha1.KubernetesNameRef `json:"serverGroupRef,omitempty"`
- AvailabilityZone *string `json:"availabilityZone,omitempty"`
- KeypairRef *apiv1alpha1.KubernetesNameRef `json:"keypairRef,omitempty"`
- Tags []apiv1alpha1.ServerTag `json:"tags,omitempty"`
+ Name *apiv1alpha1.OpenStackName `json:"name,omitempty"`
+ ImageRef *apiv1alpha1.KubernetesNameRef `json:"imageRef,omitempty"`
+ FlavorRef *apiv1alpha1.KubernetesNameRef `json:"flavorRef,omitempty"`
+ BootVolume *ServerBootVolumeSpecApplyConfiguration `json:"bootVolume,omitempty"`
+ UserData *UserDataSpecApplyConfiguration `json:"userData,omitempty"`
+ Ports []ServerPortSpecApplyConfiguration `json:"ports,omitempty"`
+ Volumes []ServerVolumeSpecApplyConfiguration `json:"volumes,omitempty"`
+ AvailabilityZone *string `json:"availabilityZone,omitempty"`
+ KeypairRef *apiv1alpha1.KubernetesNameRef `json:"keypairRef,omitempty"`
+ Tags []apiv1alpha1.ServerTag `json:"tags,omitempty"`
+ Metadata []ServerMetadataApplyConfiguration `json:"metadata,omitempty"`
+ ConfigDrive *bool `json:"configDrive,omitempty"`
+ SchedulerHints *ServerSchedulerHintsApplyConfiguration `json:"schedulerHints,omitempty"`
}
// ServerResourceSpecApplyConfiguration constructs a declarative configuration of the ServerResourceSpec type for use with
@@ -67,6 +70,14 @@ func (b *ServerResourceSpecApplyConfiguration) WithFlavorRef(value apiv1alpha1.K
return b
}
+// WithBootVolume sets the BootVolume 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 BootVolume field is set to the value of the last call.
+func (b *ServerResourceSpecApplyConfiguration) WithBootVolume(value *ServerBootVolumeSpecApplyConfiguration) *ServerResourceSpecApplyConfiguration {
+ b.BootVolume = value
+ return b
+}
+
// WithUserData sets the UserData 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 UserData field is set to the value of the last call.
@@ -101,14 +112,6 @@ func (b *ServerResourceSpecApplyConfiguration) WithVolumes(values ...*ServerVolu
return b
}
-// WithServerGroupRef sets the ServerGroupRef 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 ServerGroupRef field is set to the value of the last call.
-func (b *ServerResourceSpecApplyConfiguration) WithServerGroupRef(value apiv1alpha1.KubernetesNameRef) *ServerResourceSpecApplyConfiguration {
- b.ServerGroupRef = &value
- return b
-}
-
// WithAvailabilityZone sets the AvailabilityZone 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 AvailabilityZone field is set to the value of the last call.
@@ -134,3 +137,32 @@ func (b *ServerResourceSpecApplyConfiguration) WithTags(values ...apiv1alpha1.Se
}
return b
}
+
+// WithMetadata adds the given value to the Metadata 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 Metadata field.
+func (b *ServerResourceSpecApplyConfiguration) WithMetadata(values ...*ServerMetadataApplyConfiguration) *ServerResourceSpecApplyConfiguration {
+ for i := range values {
+ if values[i] == nil {
+ panic("nil value passed to WithMetadata")
+ }
+ b.Metadata = append(b.Metadata, *values[i])
+ }
+ return b
+}
+
+// WithConfigDrive sets the ConfigDrive 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 ConfigDrive field is set to the value of the last call.
+func (b *ServerResourceSpecApplyConfiguration) WithConfigDrive(value bool) *ServerResourceSpecApplyConfiguration {
+ b.ConfigDrive = &value
+ return b
+}
+
+// WithSchedulerHints sets the SchedulerHints 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 SchedulerHints field is set to the value of the last call.
+func (b *ServerResourceSpecApplyConfiguration) WithSchedulerHints(value *ServerSchedulerHintsApplyConfiguration) *ServerResourceSpecApplyConfiguration {
+ b.SchedulerHints = value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcestatus.go
index 119583f20..60a359afb 100644
--- a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcestatus.go
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcestatus.go
@@ -30,6 +30,8 @@ type ServerResourceStatusApplyConfiguration struct {
Volumes []ServerVolumeStatusApplyConfiguration `json:"volumes,omitempty"`
Interfaces []ServerInterfaceStatusApplyConfiguration `json:"interfaces,omitempty"`
Tags []string `json:"tags,omitempty"`
+ Metadata []ServerMetadataStatusApplyConfiguration `json:"metadata,omitempty"`
+ ConfigDrive *bool `json:"configDrive,omitempty"`
}
// ServerResourceStatusApplyConfiguration constructs a declarative configuration of the ServerResourceStatus type for use with
@@ -123,3 +125,24 @@ func (b *ServerResourceStatusApplyConfiguration) WithTags(values ...string) *Ser
}
return b
}
+
+// WithMetadata adds the given value to the Metadata 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 Metadata field.
+func (b *ServerResourceStatusApplyConfiguration) WithMetadata(values ...*ServerMetadataStatusApplyConfiguration) *ServerResourceStatusApplyConfiguration {
+ for i := range values {
+ if values[i] == nil {
+ panic("nil value passed to WithMetadata")
+ }
+ b.Metadata = append(b.Metadata, *values[i])
+ }
+ return b
+}
+
+// WithConfigDrive sets the ConfigDrive 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 ConfigDrive field is set to the value of the last call.
+func (b *ServerResourceStatusApplyConfiguration) WithConfigDrive(value bool) *ServerResourceStatusApplyConfiguration {
+ b.ConfigDrive = &value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/serverschedulerhints.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverschedulerhints.go
new file mode 100644
index 000000000..d0e89425b
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverschedulerhints.go
@@ -0,0 +1,118 @@
+/*
+Copyright 2025 The ORC Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by applyconfiguration-gen. DO NOT EDIT.
+
+package v1alpha1
+
+import (
+ apiv1alpha1 "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1"
+)
+
+// ServerSchedulerHintsApplyConfiguration represents a declarative configuration of the ServerSchedulerHints type for use
+// with apply.
+type ServerSchedulerHintsApplyConfiguration struct {
+ ServerGroupRef *apiv1alpha1.KubernetesNameRef `json:"serverGroupRef,omitempty"`
+ DifferentHostServerRefs []apiv1alpha1.KubernetesNameRef `json:"differentHostServerRefs,omitempty"`
+ SameHostServerRefs []apiv1alpha1.KubernetesNameRef `json:"sameHostServerRefs,omitempty"`
+ Query *string `json:"query,omitempty"`
+ TargetCell *string `json:"targetCell,omitempty"`
+ DifferentCell []string `json:"differentCell,omitempty"`
+ BuildNearHostIP *string `json:"buildNearHostIP,omitempty"`
+ AdditionalProperties map[string]string `json:"additionalProperties,omitempty"`
+}
+
+// ServerSchedulerHintsApplyConfiguration constructs a declarative configuration of the ServerSchedulerHints type for use with
+// apply.
+func ServerSchedulerHints() *ServerSchedulerHintsApplyConfiguration {
+ return &ServerSchedulerHintsApplyConfiguration{}
+}
+
+// WithServerGroupRef sets the ServerGroupRef 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 ServerGroupRef field is set to the value of the last call.
+func (b *ServerSchedulerHintsApplyConfiguration) WithServerGroupRef(value apiv1alpha1.KubernetesNameRef) *ServerSchedulerHintsApplyConfiguration {
+ b.ServerGroupRef = &value
+ return b
+}
+
+// WithDifferentHostServerRefs adds the given value to the DifferentHostServerRefs 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 DifferentHostServerRefs field.
+func (b *ServerSchedulerHintsApplyConfiguration) WithDifferentHostServerRefs(values ...apiv1alpha1.KubernetesNameRef) *ServerSchedulerHintsApplyConfiguration {
+ for i := range values {
+ b.DifferentHostServerRefs = append(b.DifferentHostServerRefs, values[i])
+ }
+ return b
+}
+
+// WithSameHostServerRefs adds the given value to the SameHostServerRefs 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 SameHostServerRefs field.
+func (b *ServerSchedulerHintsApplyConfiguration) WithSameHostServerRefs(values ...apiv1alpha1.KubernetesNameRef) *ServerSchedulerHintsApplyConfiguration {
+ for i := range values {
+ b.SameHostServerRefs = append(b.SameHostServerRefs, values[i])
+ }
+ return b
+}
+
+// WithQuery sets the Query 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 Query field is set to the value of the last call.
+func (b *ServerSchedulerHintsApplyConfiguration) WithQuery(value string) *ServerSchedulerHintsApplyConfiguration {
+ b.Query = &value
+ return b
+}
+
+// WithTargetCell sets the TargetCell 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 TargetCell field is set to the value of the last call.
+func (b *ServerSchedulerHintsApplyConfiguration) WithTargetCell(value string) *ServerSchedulerHintsApplyConfiguration {
+ b.TargetCell = &value
+ return b
+}
+
+// WithDifferentCell adds the given value to the DifferentCell 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 DifferentCell field.
+func (b *ServerSchedulerHintsApplyConfiguration) WithDifferentCell(values ...string) *ServerSchedulerHintsApplyConfiguration {
+ for i := range values {
+ b.DifferentCell = append(b.DifferentCell, values[i])
+ }
+ return b
+}
+
+// WithBuildNearHostIP sets the BuildNearHostIP 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 BuildNearHostIP field is set to the value of the last call.
+func (b *ServerSchedulerHintsApplyConfiguration) WithBuildNearHostIP(value string) *ServerSchedulerHintsApplyConfiguration {
+ b.BuildNearHostIP = &value
+ return b
+}
+
+// WithAdditionalProperties puts the entries into the AdditionalProperties 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 AdditionalProperties field,
+// overwriting an existing map entries in AdditionalProperties field with the same key.
+func (b *ServerSchedulerHintsApplyConfiguration) WithAdditionalProperties(entries map[string]string) *ServerSchedulerHintsApplyConfiguration {
+ if b.AdditionalProperties == nil && len(entries) > 0 {
+ b.AdditionalProperties = make(map[string]string, len(entries))
+ }
+ for k, v := range entries {
+ b.AdditionalProperties[k] = v
+ }
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcespec.go
index 7386c2714..e345fc165 100644
--- a/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcespec.go
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcespec.go
@@ -31,6 +31,7 @@ type VolumeResourceSpecApplyConfiguration struct {
VolumeTypeRef *apiv1alpha1.KubernetesNameRef `json:"volumeTypeRef,omitempty"`
AvailabilityZone *string `json:"availabilityZone,omitempty"`
Metadata []VolumeMetadataApplyConfiguration `json:"metadata,omitempty"`
+ ImageRef *apiv1alpha1.KubernetesNameRef `json:"imageRef,omitempty"`
}
// VolumeResourceSpecApplyConfiguration constructs a declarative configuration of the VolumeResourceSpec type for use with
@@ -91,3 +92,11 @@ func (b *VolumeResourceSpecApplyConfiguration) WithMetadata(values ...*VolumeMet
}
return b
}
+
+// WithImageRef sets the ImageRef 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 ImageRef field is set to the value of the last call.
+func (b *VolumeResourceSpecApplyConfiguration) WithImageRef(value apiv1alpha1.KubernetesNameRef) *VolumeResourceSpecApplyConfiguration {
+ b.ImageRef = &value
+ return b
+}
diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcestatus.go
index d3113778e..c1ec97e00 100644
--- a/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcestatus.go
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/volumeresourcestatus.go
@@ -38,6 +38,7 @@ type VolumeResourceStatusApplyConfiguration struct {
Metadata []VolumeMetadataStatusApplyConfiguration `json:"metadata,omitempty"`
UserID *string `json:"userID,omitempty"`
Bootable *bool `json:"bootable,omitempty"`
+ ImageID *string `json:"imageID,omitempty"`
Encrypted *bool `json:"encrypted,omitempty"`
ReplicationStatus *string `json:"replicationStatus,omitempty"`
ConsistencyGroupID *string `json:"consistencyGroupID,omitempty"`
@@ -168,6 +169,14 @@ func (b *VolumeResourceStatusApplyConfiguration) WithBootable(value bool) *Volum
return b
}
+// WithImageID sets the ImageID 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 ImageID field is set to the value of the last call.
+func (b *VolumeResourceStatusApplyConfiguration) WithImageID(value string) *VolumeResourceStatusApplyConfiguration {
+ b.ImageID = &value
+ return b
+}
+
// WithEncrypted sets the Encrypted 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 Encrypted field is set to the value of the last call.
diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go
index 5b5cb5142..2a4f3fd67 100644
--- a/pkg/clients/applyconfiguration/internal/internal.go
+++ b/pkg/clients/applyconfiguration/internal/internal.go
@@ -2163,6 +2163,15 @@ var schemaYAML = typed.YAMLObject(`types:
type:
namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerStatus
default: {}
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerBootVolumeSpec
+ map:
+ fields:
+ - name: tag
+ type:
+ scalar: string
+ - name: volumeRef
+ type:
+ scalar: string
- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerFilter
map:
fields:
@@ -2349,6 +2358,24 @@ var schemaYAML = typed.YAMLObject(`types:
- name: portState
type:
scalar: string
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerMetadata
+ map:
+ fields:
+ - name: key
+ type:
+ scalar: string
+ - name: value
+ type:
+ scalar: string
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerMetadataStatus
+ map:
+ fields:
+ - name: key
+ type:
+ scalar: string
+ - name: value
+ type:
+ scalar: string
- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerPortSpec
map:
fields:
@@ -2361,6 +2388,12 @@ var schemaYAML = typed.YAMLObject(`types:
- name: availabilityZone
type:
scalar: string
+ - name: bootVolume
+ type:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerBootVolumeSpec
+ - name: configDrive
+ type:
+ scalar: boolean
- name: flavorRef
type:
scalar: string
@@ -2370,6 +2403,12 @@ var schemaYAML = typed.YAMLObject(`types:
- name: keypairRef
type:
scalar: string
+ - name: metadata
+ type:
+ list:
+ elementType:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerMetadata
+ elementRelationship: atomic
- name: name
type:
scalar: string
@@ -2379,9 +2418,9 @@ var schemaYAML = typed.YAMLObject(`types:
elementType:
namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerPortSpec
elementRelationship: atomic
- - name: serverGroupRef
+ - name: schedulerHints
type:
- scalar: string
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerSchedulerHints
- name: tags
type:
list:
@@ -2403,6 +2442,9 @@ var schemaYAML = typed.YAMLObject(`types:
- name: availabilityZone
type:
scalar: string
+ - name: configDrive
+ type:
+ scalar: boolean
- name: hostID
type:
scalar: string
@@ -2415,6 +2457,12 @@ var schemaYAML = typed.YAMLObject(`types:
elementType:
namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerInterfaceStatus
elementRelationship: atomic
+ - name: metadata
+ type:
+ list:
+ elementType:
+ namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerMetadataStatus
+ elementRelationship: atomic
- name: name
type:
scalar: string
@@ -2439,6 +2487,44 @@ var schemaYAML = typed.YAMLObject(`types:
elementType:
namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerVolumeStatus
elementRelationship: atomic
+- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerSchedulerHints
+ map:
+ fields:
+ - name: additionalProperties
+ type:
+ map:
+ elementType:
+ scalar: string
+ - name: buildNearHostIP
+ type:
+ scalar: string
+ - name: differentCell
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+ - name: differentHostServerRefs
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+ - name: query
+ type:
+ scalar: string
+ - name: sameHostServerRefs
+ type:
+ list:
+ elementType:
+ scalar: string
+ elementRelationship: associative
+ - name: serverGroupRef
+ type:
+ scalar: string
+ - name: targetCell
+ type:
+ scalar: string
- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.ServerSpec
map:
fields:
@@ -2953,6 +3039,9 @@ var schemaYAML = typed.YAMLObject(`types:
- name: description
type:
scalar: string
+ - name: imageRef
+ type:
+ scalar: string
- name: metadata
type:
list:
@@ -3001,6 +3090,9 @@ var schemaYAML = typed.YAMLObject(`types:
- name: host
type:
scalar: string
+ - name: imageID
+ type:
+ scalar: string
- name: metadata
type:
list:
diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go
index e3166fefe..9540b6aec 100644
--- a/pkg/clients/applyconfiguration/utils.go
+++ b/pkg/clients/applyconfiguration/utils.go
@@ -266,6 +266,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &apiv1alpha1.SecurityGroupStatusApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("Server"):
return &apiv1alpha1.ServerApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("ServerBootVolumeSpec"):
+ return &apiv1alpha1.ServerBootVolumeSpecApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("ServerFilter"):
return &apiv1alpha1.ServerFilterApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("ServerGroup"):
@@ -292,12 +294,18 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
return &apiv1alpha1.ServerInterfaceFixedIPApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("ServerInterfaceStatus"):
return &apiv1alpha1.ServerInterfaceStatusApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("ServerMetadata"):
+ return &apiv1alpha1.ServerMetadataApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("ServerMetadataStatus"):
+ return &apiv1alpha1.ServerMetadataStatusApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("ServerPortSpec"):
return &apiv1alpha1.ServerPortSpecApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("ServerResourceSpec"):
return &apiv1alpha1.ServerResourceSpecApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("ServerResourceStatus"):
return &apiv1alpha1.ServerResourceStatusApplyConfiguration{}
+ case v1alpha1.SchemeGroupVersion.WithKind("ServerSchedulerHints"):
+ return &apiv1alpha1.ServerSchedulerHintsApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("ServerSpec"):
return &apiv1alpha1.ServerSpecApplyConfiguration{}
case v1alpha1.SchemeGroupVersion.WithKind("ServerStatus"):
diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md
index 23b3b2403..b3ebf5161 100644
--- a/website/docs/crd-reference.md
+++ b/website/docs/crd-reference.md
@@ -1627,8 +1627,10 @@ _Appears in:_
- [RouterResourceSpec](#routerresourcespec)
- [SecurityGroupFilter](#securitygroupfilter)
- [SecurityGroupResourceSpec](#securitygroupresourcespec)
+- [ServerBootVolumeSpec](#serverbootvolumespec)
- [ServerPortSpec](#serverportspec)
- [ServerResourceSpec](#serverresourcespec)
+- [ServerSchedulerHints](#serverschedulerhints)
- [ServerVolumeSpec](#servervolumespec)
- [SubnetFilter](#subnetfilter)
- [SubnetResourceSpec](#subnetresourcespec)
@@ -3028,6 +3030,24 @@ Server is the Schema for an ORC resource.
| `status` _[ServerStatus](#serverstatus)_ | status defines the observed state of the resource. | | |
+#### ServerBootVolumeSpec
+
+
+
+ServerBootVolumeSpec defines the boot volume for boot-from-volume server creation.
+When specified, the server boots from this volume instead of an image.
+
+
+
+_Appears in:_
+- [ServerResourceSpec](#serverresourcespec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `volumeRef` _[KubernetesNameRef](#kubernetesnameref)_ | volumeRef is a reference to a Volume object. The volume must be
bootable (created from an image) and available before server creation. | | MaxLength: 253
MinLength: 1
|
+| `tag` _string_ | tag is the device tag applied to the volume. | | MaxLength: 255
|
+
+
#### ServerFilter
@@ -3291,6 +3311,40 @@ _Appears in:_
| `fixedIPs` _[ServerInterfaceFixedIP](#serverinterfacefixedip) array_ | fixedIPs is the list of fixed IP addresses assigned to the interface. | | MaxItems: 32
|
+#### ServerMetadata
+
+
+
+ServerMetadata represents a key-value pair for server metadata.
+
+
+
+_Appears in:_
+- [ServerResourceSpec](#serverresourcespec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `key` _string_ | key is the metadata key. | | MaxLength: 255
MinLength: 1
|
+| `value` _string_ | value is the metadata value. | | MaxLength: 255
MinLength: 1
|
+
+
+#### ServerMetadataStatus
+
+
+
+ServerMetadataStatus represents a key-value pair for server metadata in status.
+
+
+
+_Appears in:_
+- [ServerResourceStatus](#serverresourcestatus)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `key` _string_ | key is the metadata key. | | MaxLength: 255
|
+| `value` _string_ | value is the metadata value. | | MaxLength: 255
|
+
+
#### ServerPortSpec
@@ -3323,15 +3377,18 @@ _Appears in:_
| Field | Description | Default | Validation |
| --- | --- | --- | --- |
| `name` _[OpenStackName](#openstackname)_ | name will be the name of the created resource. If not specified, the
name of the ORC object will be used. | | MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
|
-| `imageRef` _[KubernetesNameRef](#kubernetesnameref)_ | imageRef references the image to use for the server instance.
NOTE: This is not required in case of boot from volume. | | MaxLength: 253
MinLength: 1
|
+| `imageRef` _[KubernetesNameRef](#kubernetesnameref)_ | imageRef references the image to use for the server instance.
This field is required unless bootVolume is specified for boot-from-volume. | | MaxLength: 253
MinLength: 1
|
| `flavorRef` _[KubernetesNameRef](#kubernetesnameref)_ | flavorRef references the flavor to use for the server instance. | | MaxLength: 253
MinLength: 1
|
+| `bootVolume` _[ServerBootVolumeSpec](#serverbootvolumespec)_ | bootVolume specifies a volume to boot from instead of an image.
When specified, imageRef must be omitted. The volume must be
bootable (created from an image using imageRef in the Volume spec). | | |
| `userData` _[UserDataSpec](#userdataspec)_ | userData specifies data which will be made available to the server at
boot time, either via the metadata service or a config drive. It is
typically read by a configuration service such as cloud-init or ignition. | | MaxProperties: 1
MinProperties: 1
|
| `ports` _[ServerPortSpec](#serverportspec) array_ | ports defines a list of ports which will be attached to the server. | | MaxItems: 64
MaxProperties: 1
MinProperties: 1
|
| `volumes` _[ServerVolumeSpec](#servervolumespec) array_ | volumes is a list of volumes attached to the server. | | MaxItems: 64
MinProperties: 1
|
-| `serverGroupRef` _[KubernetesNameRef](#kubernetesnameref)_ | serverGroupRef is a reference to a ServerGroup object. The server
will be created in the server group. | | MaxLength: 253
MinLength: 1
|
| `availabilityZone` _string_ | availabilityZone is the availability zone in which to create the server. | | MaxLength: 255
|
| `keypairRef` _[KubernetesNameRef](#kubernetesnameref)_ | keypairRef is a reference to a KeyPair object. The server will be
created with this keypair for SSH access. | | MaxLength: 253
MinLength: 1
|
| `tags` _[ServerTag](#servertag) array_ | tags is a list of tags which will be applied to the server. | | MaxItems: 50
MaxLength: 80
MinLength: 1
|
+| `metadata` _[ServerMetadata](#servermetadata) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 128
|
+| `configDrive` _boolean_ | configDrive specifies whether to attach a config drive to the server.
When true, configuration data will be available via a special drive
instead of the metadata service. | | |
+| `schedulerHints` _[ServerSchedulerHints](#serverschedulerhints)_ | schedulerHints provides hints to the Nova scheduler for server placement. | | |
#### ServerResourceStatus
@@ -3356,6 +3413,31 @@ _Appears in:_
| `volumes` _[ServerVolumeStatus](#servervolumestatus) array_ | volumes contains the volumes attached to the server. | | MaxItems: 64
|
| `interfaces` _[ServerInterfaceStatus](#serverinterfacestatus) array_ | interfaces contains the list of interfaces attached to the server. | | MaxItems: 64
|
| `tags` _string array_ | tags is the list of tags on the resource. | | MaxItems: 50
items:MaxLength: 1024
|
+| `metadata` _[ServerMetadataStatus](#servermetadatastatus) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 128
|
+| `configDrive` _boolean_ | configDrive indicates whether the server was booted with a config drive. | | |
+
+
+#### ServerSchedulerHints
+
+
+
+ServerSchedulerHints provides hints to the Nova scheduler for server placement.
+
+
+
+_Appears in:_
+- [ServerResourceSpec](#serverresourcespec)
+
+| Field | Description | Default | Validation |
+| --- | --- | --- | --- |
+| `serverGroupRef` _[KubernetesNameRef](#kubernetesnameref)_ | serverGroupRef is a reference to a ServerGroup object. The server will be
scheduled on a host in the specified server group. | | MaxLength: 253
MinLength: 1
|
+| `differentHostServerRefs` _[KubernetesNameRef](#kubernetesnameref) array_ | differentHostServerRefs is a list of references to Server objects.
The server will be scheduled on a different host than all specified servers. | | MaxItems: 64
MaxLength: 253
MinLength: 1
|
+| `sameHostServerRefs` _[KubernetesNameRef](#kubernetesnameref) array_ | sameHostServerRefs is a list of references to Server objects.
The server will be scheduled on the same host as all specified servers. | | MaxItems: 64
MaxLength: 253
MinLength: 1
|
+| `query` _string_ | query is a conditional statement that results in compute nodes
able to host the server. | | MaxLength: 1024
|
+| `targetCell` _string_ | targetCell is a cell name where the server will be placed. | | MaxLength: 255
|
+| `differentCell` _string array_ | differentCell is a list of cell names where the server should not
be placed. | | MaxItems: 64
items:MaxLength: 1024
|
+| `buildNearHostIP` _string_ | buildNearHostIP specifies a subnet of compute nodes to host the server. | | MaxLength: 255
|
+| `additionalProperties` _object (keys:string, values:string)_ | additionalProperties is a map of arbitrary key/value pairs that are
not validated by Nova. | | |
#### ServerSpec
@@ -3930,6 +4012,7 @@ _Appears in:_
| `volumeTypeRef` _[KubernetesNameRef](#kubernetesnameref)_ | volumeTypeRef is a reference to the ORC VolumeType which this resource is associated with. | | MaxLength: 253
MinLength: 1
|
| `availabilityZone` _string_ | availabilityZone is the availability zone in which to create the volume. | | MaxLength: 255
|
| `metadata` _[VolumeMetadata](#volumemetadata) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 64
|
+| `imageRef` _[KubernetesNameRef](#kubernetesnameref)_ | imageRef is a reference to an ORC Image. If specified, creates a
bootable volume from this image. The volume size must be >= the
image's min_disk requirement. | | MaxLength: 253
MinLength: 1
|
#### VolumeResourceStatus
@@ -3958,6 +4041,7 @@ _Appears in:_
| `metadata` _[VolumeMetadataStatus](#volumemetadatastatus) array_ | Refer to Kubernetes API documentation for fields of `metadata`. | | MaxItems: 64
|
| `userID` _string_ | userID is the ID of the user who created the volume. | | MaxLength: 1024
|
| `bootable` _boolean_ | bootable indicates whether this is a bootable volume. | | |
+| `imageID` _string_ | imageID is the ID of the image this volume was created from, if any. | | MaxLength: 1024
|
| `encrypted` _boolean_ | encrypted denotes if the volume is encrypted. | | |
| `replicationStatus` _string_ | replicationStatus is the status of replication. | | MaxLength: 1024
|
| `consistencyGroupID` _string_ | consistencyGroupID is the consistency group ID. | | MaxLength: 1024
|