diff --git a/api/v1alpha1/server_types.go b/api/v1alpha1/server_types.go
index f4f40b279..760252753 100644
--- a/api/v1alpha1/server_types.go
+++ b/api/v1alpha1/server_types.go
@@ -158,12 +158,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
@@ -194,6 +188,11 @@ type ServerResourceSpec struct {
// +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"`
}
// ServerMetadata represents a key-value pair for server metadata.
@@ -211,8 +210,57 @@ type ServerMetadata struct {
Value string `json:"value,omitempty"`
}
-// +kubebuilder:validation:MinProperties:=1
-// +kubebuilder:validation:MaxProperties:=1
+// 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"`
+}
+
type UserDataSpec struct {
// secretRef is a reference to a Secret containing the user data for this server.
// +optional
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index 74fd9dcdf..bf614a475 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -4271,11 +4271,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)
@@ -4296,6 +4291,11 @@ func (in *ServerResourceSpec) DeepCopyInto(out *ServerResourceSpec) {
*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.
@@ -4350,6 +4350,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
diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go
index 3b90d275e..6c3422f49 100644
--- a/cmd/models-schema/zz_generated.openapi.go
+++ b/cmd/models-schema/zz_generated.openapi.go
@@ -180,6 +180,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"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),
@@ -8255,13 +8256,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.",
@@ -8322,12 +8316,18 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceSpec(ref
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"},
},
},
Dependencies: []string{
- "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.ServerVolumeSpec", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.UserDataSpec"},
+ "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"},
}
}
@@ -8485,6 +8485,123 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_ServerResourceStatus(r
}
}
+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: "",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
func schema_openstack_resource_controller_v2_api_v1alpha1_ServerSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
diff --git a/config/crd/bases/openstack.k-orc.cloud_servers.yaml b/config/crd/bases/openstack.k-orc.cloud_servers.yaml
index 2a882bad3..6bea0627e 100644
--- a/config/crd/bases/openstack.k-orc.cloud_servers.yaml
+++ b/config/crd/bases/openstack.k-orc.cloud_servers.yaml
@@ -290,15 +290,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
@@ -315,8 +375,6 @@ spec:
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
properties:
secretRef:
description: secretRef is a reference to a Secret containing
diff --git a/config/samples/openstack_v1alpha1_server.yaml b/config/samples/openstack_v1alpha1_server.yaml
index 382d6f9b4..0ee8c24ff 100644
--- a/config/samples/openstack_v1alpha1_server.yaml
+++ b/config/samples/openstack_v1alpha1_server.yaml
@@ -14,8 +14,9 @@ spec:
- portRef: server-sample
volumes:
- volumeRef: server-sample
- serverGroupRef: server-sample
keypairRef: server-sample
+ schedulerHints:
+ serverGroupRef: server-sample
availabilityZone: nova
tags:
- tag1
diff --git a/internal/controllers/server/actuator.go b/internal/controllers/server/actuator.go
index acadd0be1..0a441193d 100644
--- a/internal/controllers/server/actuator.go
+++ b/internal/controllers/server/actuator.go
@@ -150,6 +150,90 @@ func (actuator serverActuator) ListOSResourcesForImport(ctx context.Context, obj
return wrapServers(actuator.osClient.ListServers(ctx, listOpts)), nil
}
+func (actuator serverActuator) getSchedulerHints(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
+ sg, sgReconcileStatus := dependency.FetchDependency(
+ ctx, actuator.k8sClient, obj.Namespace,
+ schedHints.ServerGroupRef, "ServerGroup",
+ func(sg *orcv1alpha1.ServerGroup) bool {
+ return orcv1alpha1.IsAvailable(sg) && sg.Status.ID != nil
+ },
+ )
+ 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 i := range schedHints.DifferentHostServerRefs {
+ ref := &schedHints.DifferentHostServerRefs[i]
+ server, serverReconcileStatus := dependency.FetchDependency(
+ ctx, actuator.k8sClient, obj.Namespace,
+ ref, "Server",
+ func(s *orcv1alpha1.Server) bool {
+ return s.Status.ID != nil
+ },
+ )
+ 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 i := range schedHints.SameHostServerRefs {
+ ref := &schedHints.SameHostServerRefs[i]
+ server, serverReconcileStatus := dependency.FetchDependency(
+ ctx, actuator.k8sClient, obj.Namespace,
+ ref, "Server",
+ func(s *orcv1alpha1.Server) bool {
+ return s.Status.ID != nil
+ },
+ )
+ 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}
+ }
+ 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) CreateResource(ctx context.Context, obj *orcv1alpha1.Server) (*osResourceT, progress.ReconcileStatus) {
resource := obj.Spec.Resource
if resource == nil {
@@ -206,12 +290,8 @@ func (actuator serverActuator) CreateResource(ctx context.Context, obj *orcv1alp
}
}
- serverGroup, serverGroupReconcileStatus := dependency.FetchDependency(
- ctx, actuator.k8sClient, obj.Namespace,
- resource.ServerGroupRef, "ServerGroup",
- func(sg *orcv1alpha1.ServerGroup) bool { return orcv1alpha1.IsAvailable(sg) && sg.Status.ID != nil },
- )
- reconcileStatus = reconcileStatus.WithReconcileStatus(serverGroupReconcileStatus)
+ schedulerHints, schedulerHintsReconcileStatus := actuator.getSchedulerHints(ctx, obj, resource)
+ reconcileStatus = reconcileStatus.WithReconcileStatus(schedulerHintsReconcileStatus)
keypair, keypairReconcileStatus := dependency.FetchDependency(
ctx, actuator.k8sClient, obj.Namespace,
@@ -277,10 +357,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
diff --git a/internal/controllers/server/controller.go b/internal/controllers/server/controller.go
index 95ac9f595..3d7c00771 100644
--- a/internal/controllers/server/controller.go
+++ b/internal/controllers/server/controller.go
@@ -105,14 +105,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)}
},
)
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 6f82c53f2..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,8 +40,9 @@ 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
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-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/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go
index c3308477a..26c2f50ab 100644
--- a/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverresourcespec.go
@@ -25,18 +25,18 @@ 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"`
- Metadata []ServerMetadataApplyConfiguration `json:"metadata,omitempty"`
- ConfigDrive *bool `json:"configDrive,omitempty"`
+ 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"`
+ 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
@@ -103,14 +103,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.
@@ -157,3 +149,11 @@ func (b *ServerResourceSpecApplyConfiguration) WithConfigDrive(value bool) *Serv
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/serverschedulerhints.go b/pkg/clients/applyconfiguration/api/v1alpha1/serverschedulerhints.go
new file mode 100644
index 000000000..0a60d0a1e
--- /dev/null
+++ b/pkg/clients/applyconfiguration/api/v1alpha1/serverschedulerhints.go
@@ -0,0 +1,118 @@
+/*
+Copyright 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/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go
index abaeca27f..227185dbe 100644
--- a/pkg/clients/applyconfiguration/internal/internal.go
+++ b/pkg/clients/applyconfiguration/internal/internal.go
@@ -2418,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:
@@ -2487,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:
diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go
index 5a3990951..924e3d540 100644
--- a/pkg/clients/applyconfiguration/utils.go
+++ b/pkg/clients/applyconfiguration/utils.go
@@ -302,6 +302,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
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 32018f962..484798f19 100644
--- a/website/docs/crd-reference.md
+++ b/website/docs/crd-reference.md
@@ -1629,6 +1629,7 @@ _Appears in:_
- [SecurityGroupResourceSpec](#securitygroupresourcespec)
- [ServerPortSpec](#serverportspec)
- [ServerResourceSpec](#serverresourcespec)
+- [ServerSchedulerHints](#serverschedulerhints)
- [ServerVolumeSpec](#servervolumespec)
- [SubnetFilter](#subnetfilter)
- [SubnetResourceSpec](#subnetresourcespec)
@@ -3363,15 +3364,15 @@ _Appears in:_
| `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
|
| `flavorRef` _[KubernetesNameRef](#kubernetesnameref)_ | flavorRef references the flavor to use for the server instance. | | MaxLength: 253
MinLength: 1
|
-| `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
|
+| `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. | | |
| `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
@@ -3400,6 +3401,29 @@ _Appears in:_
| `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
@@ -3829,9 +3853,7 @@ _Appears in:_
-_Validation:_
-- MaxProperties: 1
-- MinProperties: 1
+
_Appears in:_
- [ServerResourceSpec](#serverresourcespec)