diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index da07b7eee..5b6fbd766 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -37,6 +37,10 @@ jobs: enable_workaround_docker_io: 'false' branch: ${{ matrix.openstack_version }} enabled_services: "openstack-cli-server" + conf_overrides: | + [[post-config|/etc/nova/nova.conf]] + [filter_scheduler] + enabled_filters = ComputeFilter,ComputeCapabilitiesFilter,ImagePropertiesFilter,ServerGroupAntiAffinityFilter,ServerGroupAffinityFilter,SameHostFilter,DifferentHostFilter,SimpleCIDRAffinityFilter,JsonFilter - name: Deploy a Kind Cluster uses: helm/kind-action@92086f6be054225fa813e0a4b13787fc9088faab diff --git a/api/v1alpha1/server_types.go b/api/v1alpha1/server_types.go index f4f40b279..1e7e1cb33 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,59 @@ type ServerMetadata struct { Value string `json:"value,omitempty"` } -// +kubebuilder:validation:MinProperties:=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. + // The host IP should be provided in an CIDR format like 10.10.10.10/24. + // +optional + BuildNearHostIP *CIDR `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"` +} + // +kubebuilder:validation:MaxProperties:=1 +// +kubebuilder:validation:MinProperties:=1 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..183b2b083 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,53 @@ 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.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(CIDR) + **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..0bbca2439 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. The host IP should be provided in an CIDR format like 10.10.10.10/24.", + 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..661888d5b 100644 --- a/config/crd/bases/openstack.k-orc.cloud_servers.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_servers.yaml @@ -290,15 +290,78 @@ 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. + The host IP should be provided in an CIDR format like 10.10.10.10/24. + format: cidr + maxLength: 49 + minLength: 1 + 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 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..69ada4497 100644 --- a/internal/controllers/server/actuator.go +++ b/internal/controllers/server/actuator.go @@ -18,6 +18,7 @@ package server import ( "context" + "encoding/json" "fmt" "iter" "maps" @@ -150,6 +151,100 @@ 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 && + s.Status.Resource != nil && + s.Status.Resource.Status == "ACTIVE" + }, + ) + 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 && + s.Status.Resource != nil && + s.Status.Resource.Status == "ACTIVE" + }, + ) + reconcileStatus = reconcileStatus.WithReconcileStatus(serverReconcileStatus) + if server.Status.ID != nil { + sameHost = append(sameHost, *server.Status.ID) + } + } + hints.SameHost = sameHost + } + + if schedHints.Query != "" { + var query []any + if err := json.Unmarshal([]byte(schedHints.Query), &query); err != nil { + return hints, progress.WrapError(orcerrors.Terminal( + orcv1alpha1.ConditionReasonInvalidConfiguration, + "invalid scheduler hints query: "+err.Error(), err)) + } + hints.Query = query + } + if schedHints.TargetCell != "" { + hints.TargetCell = schedHints.TargetCell + } + hints.DifferentCell = schedHints.DifferentCell + if schedHints.BuildNearHostIP != nil { + hints.BuildNearHostIP = string(ptr.Deref(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 +301,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 +368,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..e084b2fde 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)} }, ) @@ -161,6 +161,40 @@ var ( }, finalizer, externalObjectFieldOwner, ) + + // No deletion guard for server references in scheduler hints, because they + // are only used on creation for placement decisions + sameHostServerRefDependency = dependency.NewDependency[*orcv1alpha1.ServerList, *orcv1alpha1.Server]( + "spec.resource.schedulerHints.sameHostServerRefs", + func(server *orcv1alpha1.Server) []string { + resource := server.Spec.Resource + if resource == nil || resource.SchedulerHints == nil { + return nil + } + + refs := make([]string, 0, len(resource.SchedulerHints.SameHostServerRefs)) + for _, ref := range resource.SchedulerHints.SameHostServerRefs { + refs = append(refs, string(ref)) + } + return refs + }, + ) + + differentHostServerRefDependency = dependency.NewDependency[*orcv1alpha1.ServerList, *orcv1alpha1.Server]( + "spec.resource.schedulerHints.differentHostServerRefs", + func(server *orcv1alpha1.Server) []string { + resource := server.Spec.Resource + if resource == nil || resource.SchedulerHints == nil { + return nil + } + + refs := make([]string, 0, len(resource.SchedulerHints.DifferentHostServerRefs)) + for _, ref := range resource.SchedulerHints.DifferentHostServerRefs { + refs = append(refs, string(ref)) + } + return refs + }, + ) ) // SetupWithManager sets up the controller with the Manager. @@ -196,6 +230,14 @@ func (c serverReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c if err != nil { return err } + sameHostServerRefWatchEventHandler, err := sameHostServerRefDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + differentHostServerRefWatchEventHandler, err := differentHostServerRefDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } builder := ctrl.NewControllerManagedBy(mgr). WithOptions(options). @@ -218,6 +260,12 @@ func (c serverReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c Watches(&orcv1alpha1.KeyPair{}, keypairWatchEventHandler, builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.KeyPair{})), ). + Watches(&orcv1alpha1.Server{}, sameHostServerRefWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Server{})), + ). + Watches(&orcv1alpha1.Server{}, differentHostServerRefWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Server{})), + ). // XXX: This is a general watch on secrets. A general watch on secrets // is undesirable because: // - It requires problematic RBAC @@ -235,6 +283,8 @@ func (c serverReconcilerConstructor) SetupWithManager(ctx context.Context, mgr c userDataDependency.AddToManager(ctx, mgr), volumeDependency.AddToManager(ctx, mgr), keypairDependency.AddToManager(ctx, mgr), + sameHostServerRefDependency.AddToManager(ctx, mgr), + differentHostServerRefDependency.AddToManager(ctx, mgr), credentialsDependency.AddToManager(ctx, mgr), credentials.AddCredentialsWatch(log, k8sClient, builder, credentialsDependency), ); err != nil { 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 68c65c73b..14b3d5be7 100644 --- a/internal/controllers/server/tests/server-create-full/00-assert.yaml +++ b/internal/controllers/server/tests/server-create-full/00-assert.yaml @@ -14,6 +14,14 @@ resourceRefs: kind: Port name: server-create-full ref: port + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Port + name: server-create-full-dummy + ref: portDummy + - apiVersion: openstack.k-orc.cloud/v1alpha1 + kind: Server + name: server-create-full-dummy + ref: serverDummy - apiVersion: openstack.k-orc.cloud/v1alpha1 kind: ServerGroup name: server-create-full @@ -35,7 +43,9 @@ resourceRefs: name: server-create-full ref: subnet assertAll: + - celExpr: "serverDummy.status.resource.status == 'ACTIVE'" - celExpr: "server.status.resource.hostID != ''" + - celExpr: "server.status.resource.hostID == serverDummy.status.resource.hostID" - celExpr: "server.status.resource.availabilityZone == 'nova'" - celExpr: "server.status.resource.imageID == image.status.id" - celExpr: "server.status.resource.serverGroups[0] == sg.status.id" 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..a06c5e43d 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 @@ -14,6 +14,20 @@ spec: - subnetRef: server-create-full --- apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Port +metadata: + name: server-create-full-dummy +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + networkRef: server-create-full + addresses: + - subnetRef: server-create-full +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Volume metadata: name: server-create-full @@ -27,6 +41,21 @@ spec: --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Server +metadata: + name: server-create-full-dummy +spec: + cloudCredentialsRef: + cloudName: openstack + secretName: openstack-clouds + managementPolicy: managed + resource: + imageRef: server-create-full + flavorRef: server-create-full + ports: + - portRef: server-create-full-dummy +--- +apiVersion: openstack.k-orc.cloud/v1alpha1 +kind: Server metadata: name: server-create-full spec: @@ -40,8 +69,15 @@ spec: flavorRef: server-create-full ports: - portRef: server-create-full - serverGroupRef: server-create-full keypairRef: server-create-full + schedulerHints: + serverGroupRef: server-create-full + query: '[">=", "$free_ram_mb", 32]' + buildNearHostIP: 10.0.0.0/8 + additionalProperties: + custom_hint: custom_value + sameHostServerRefs: + - server-create-full-dummy 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..5fc022118 --- /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 *apiv1alpha1.CIDR `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 apiv1alpha1.CIDR) *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..bf0e22542 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -147,6 +147,7 @@ _Validation:_ _Appears in:_ - [HostRoute](#hostroute) - [SecurityGroupRule](#securitygrouprule) +- [ServerSchedulerHints](#serverschedulerhints) - [SubnetFilter](#subnetfilter) - [SubnetResourceSpec](#subnetresourcespec) @@ -1629,6 +1630,7 @@ _Appears in:_ - [SecurityGroupResourceSpec](#securitygroupresourcespec) - [ServerPortSpec](#serverportspec) - [ServerResourceSpec](#serverresourcespec) +- [ServerSchedulerHints](#serverschedulerhints) - [ServerVolumeSpec](#servervolumespec) - [SubnetFilter](#subnetfilter) - [SubnetResourceSpec](#subnetresourcespec) @@ -3366,12 +3368,12 @@ _Appears in:_ | `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 @@ -3400,6 +3402,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` _[CIDR](#cidr)_ | buildNearHostIP specifies a subnet of compute nodes to host the server.
The host IP should be provided in an CIDR format like 10.10.10.10/24. | | Format: cidr
MaxLength: 49
MinLength: 1
| +| `additionalProperties` _object (keys:string, values:string)_ | additionalProperties is a map of arbitrary key/value pairs that are
not validated by Nova. | | | + + #### ServerSpec