diff --git a/api/v1alpha1/port_types.go b/api/v1alpha1/port_types.go index 9e51d0153..d54f3cf95 100644 --- a/api/v1alpha1/port_types.go +++ b/api/v1alpha1/port_types.go @@ -49,6 +49,26 @@ type PortFilter struct { FilterByNeutronTags `json:",inline"` } +// HostID specifies how to determine the host ID for port binding. +// Exactly one of the fields must be set. +// +kubebuilder:validation:MinProperties:=1 +// +kubebuilder:validation:MaxProperties:=1 +// +kubebuilder:validation:XValidation:rule="(has(self.id) && size(self.id) > 0) != (has(self.serverRef) && size(self.serverRef) > 0)",message="exactly one of id or serverRef must be set" +type HostID struct { + // id is the literal host ID string to use for binding:host_id. + // This is mutually exclusive with serverRef. + // +kubebuilder:validation:MaxLength=36 + // +optional + ID string `json:"id,omitempty"` + + // serverRef is a reference to an ORC Server resource from which to + // retrieve the hostID for port binding. The hostID will be read from + // the Server's status.resource.hostID field. + // This is mutually exclusive with id. + // +optional + ServerRef KubernetesNameRef `json:"serverRef,omitempty"` +} + type AllowedAddressPair struct { // ip contains an IP address which a server connected to the port can // send packets with. It can be an IP Address or a CIDR (if supported @@ -181,10 +201,9 @@ type PortResourceSpec struct { // +optional MACAddress string `json:"macAddress,omitempty"` - // hostID is the ID of host where the port resides. - // +kubebuilder:validation:MaxLength=36 + // hostID specifies the host where the port will be bound. // +optional - HostID string `json:"hostID,omitempty"` + HostID *HostID `json:"hostID,omitempty"` } type PortResourceStatus struct { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 74fd9dcdf..1965cca7c 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1235,6 +1235,21 @@ func (in *GroupStatus) DeepCopy() *GroupStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *HostID) DeepCopyInto(out *HostID) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HostID. +func (in *HostID) DeepCopy() *HostID { + if in == nil { + return nil + } + out := new(HostID) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *HostRoute) DeepCopyInto(out *HostRoute) { *out = *in @@ -2529,6 +2544,11 @@ func (in *PortResourceSpec) DeepCopyInto(out *PortResourceSpec) { *out = new(KubernetesNameRef) **out = **in } + if in.HostID != nil { + in, out := &in.HostID, &out.HostID + *out = new(HostID) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortResourceSpec. diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 3b90d275e..7f65e1b99 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -74,6 +74,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupResourceStatus": schema_openstack_resource_controller_v2_api_v1alpha1_GroupResourceStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupSpec": schema_openstack_resource_controller_v2_api_v1alpha1_GroupSpec(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.GroupStatus": schema_openstack_resource_controller_v2_api_v1alpha1_GroupStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostID": schema_openstack_resource_controller_v2_api_v1alpha1_HostID(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostRoute": schema_openstack_resource_controller_v2_api_v1alpha1_HostRoute(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostRouteStatus": schema_openstack_resource_controller_v2_api_v1alpha1_HostRouteStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.IPv6Options": schema_openstack_resource_controller_v2_api_v1alpha1_IPv6Options(ref), @@ -2637,6 +2638,33 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_GroupStatus(ref common } } +func schema_openstack_resource_controller_v2_api_v1alpha1_HostID(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "HostID specifies how to determine the host ID for port binding. Exactly one of the fields must be set.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "id": { + SchemaProps: spec.SchemaProps{ + Description: "id is the literal host ID string to use for binding:host_id. This is mutually exclusive with serverRef.", + Type: []string{"string"}, + Format: "", + }, + }, + "serverRef": { + SchemaProps: spec.SchemaProps{ + Description: "serverRef is a reference to an ORC Server resource from which to retrieve the hostID for port binding. The hostID will be read from the Server's status.resource.hostID field. This is mutually exclusive with id.", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_HostRoute(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -4913,9 +4941,8 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref c }, "hostID": { SchemaProps: spec.SchemaProps{ - Description: "hostID is the ID of host where the port resides.", - Type: []string{"string"}, - Format: "", + Description: "hostID specifies the host where the port will be bound.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostID"), }, }, }, @@ -4923,7 +4950,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref c }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Address", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllowedAddressPair"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Address", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllowedAddressPair", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.HostID"}, } } diff --git a/config/crd/bases/openstack.k-orc.cloud_ports.yaml b/config/crd/bases/openstack.k-orc.cloud_ports.yaml index c14173d71..a533fefb8 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -299,9 +299,31 @@ spec: minLength: 1 type: string hostID: - description: hostID is the ID of host where the port resides. - maxLength: 36 - type: string + description: hostID specifies the host where the port will be + bound. + maxProperties: 1 + minProperties: 1 + properties: + id: + description: |- + id is the literal host ID string to use for binding:host_id. + This is mutually exclusive with serverRef. + maxLength: 36 + type: string + serverRef: + description: |- + serverRef is a reference to an ORC Server resource from which to + retrieve the hostID for port binding. The hostID will be read from + the Server's status.resource.hostID field. + This is mutually exclusive with id. + maxLength: 253 + minLength: 1 + type: string + type: object + x-kubernetes-validations: + - message: exactly one of id or serverRef must be set + rule: (has(self.id) && size(self.id) > 0) != (has(self.serverRef) + && size(self.serverRef) > 0) macAddress: description: macAddress is the MAC address of the port. maxLength: 32 diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index f4890c03a..983cddb18 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -57,6 +57,45 @@ const ( serverBuildPollingPeriod = 15 * time.Second ) +// resolveHostID resolves the actual host ID string to use for port binding. +// It handles both direct ID specification and server reference. +// Returns the resolved host ID and a reconcile status (for waiting on dependencies). +func resolveHostID( + ctx context.Context, + k8sClient client.Client, + obj orcObjectPT, + hostIDSpec *orcv1alpha1.HostID, +) (string, progress.ReconcileStatus) { + if hostIDSpec == nil { + return "", nil + } + + // Direct ID specification + if hostIDSpec.ID != "" { + return hostIDSpec.ID, nil + } + + // Server reference - fetch the server and extract its hostID + if hostIDSpec.ServerRef != "" { + server, serverDepRS := dependency.FetchDependency( + ctx, k8sClient, obj.Namespace, &hostIDSpec.ServerRef, "Server", + func(dep *orcv1alpha1.Server) bool { + return orcv1alpha1.IsAvailable(dep) && + dep.Status.Resource != nil && + dep.Status.Resource.HostID != "" + }, + ) + if needsReschedule, _ := serverDepRS.NeedsReschedule(); needsReschedule { + return "", serverDepRS + } + if server != nil && server.Status.Resource != nil { + return server.Status.Resource.HostID, nil + } + } + + return "", nil +} + type portActuator struct { osClient osclients.NetworkClient k8sClient client.Client @@ -166,6 +205,14 @@ func (actuator portActuator) CreateResource(ctx context.Context, obj *orcv1alpha } } + // Resolve hostID if specified + var resolvedHostID string + if resource.HostID != nil { + var hostIDReconcileStatus progress.ReconcileStatus + resolvedHostID, hostIDReconcileStatus = resolveHostID(ctx, actuator.k8sClient, obj, resource.HostID) + reconcileStatus = reconcileStatus.WithReconcileStatus(hostIDReconcileStatus) + } + if needsReschedule, _ := reconcileStatus.NeedsReschedule(); needsReschedule { return nil, reconcileStatus } @@ -232,7 +279,7 @@ func (actuator portActuator) CreateResource(ctx context.Context, obj *orcv1alpha portsBindingOpts := portsbinding.CreateOptsExt{ CreateOptsBuilder: createOpts, VNICType: resource.VNICType, - HostID: resource.HostID, + HostID: resolvedHostID, } portSecurityOpts := portsecurity.PortCreateOptsExt{ @@ -330,6 +377,14 @@ func (actuator portActuator) updateResource(ctx context.Context, obj orcObjectPT reconcileStatus := progress.NewReconcileStatus(). WithReconcileStatus(secGroupDepRS) + // Resolve hostID if specified + var resolvedHostID string + if resource.HostID != nil { + var hostIDReconcileStatus progress.ReconcileStatus + resolvedHostID, hostIDReconcileStatus = resolveHostID(ctx, actuator.k8sClient, obj, resource.HostID) + reconcileStatus = reconcileStatus.WithReconcileStatus(hostIDReconcileStatus) + } + needsReschedule, _ := reconcileStatus.NeedsReschedule() if needsReschedule { return reconcileStatus @@ -348,7 +403,7 @@ func (actuator portActuator) updateResource(ctx context.Context, obj orcObjectPT updateOpts = baseUpdateOpts } - updateOpts = handlePortBindingUpdate(updateOpts, resource, osResource) + updateOpts = handlePortBindingUpdate(updateOpts, resource, osResource, resolvedHostID) updateOpts = handlePortSecurityUpdate(updateOpts, resource, osResource) needsUpdate, err := needsUpdate(updateOpts) @@ -474,7 +529,7 @@ func handleSecurityGroupRefsUpdate(updateOpts *ports.UpdateOpts, resource *resou } } -func handlePortBindingUpdate(updateOpts ports.UpdateOptsBuilder, resource *resourceSpecT, osResource *osResourceT) ports.UpdateOptsBuilder { +func handlePortBindingUpdate(updateOpts ports.UpdateOptsBuilder, resource *resourceSpecT, osResource *osResourceT, resolvedHostID string) ports.UpdateOptsBuilder { if resource.VNICType != "" { if resource.VNICType != osResource.VNICType { updateOpts = &portsbinding.UpdateOptsExt{ @@ -484,11 +539,11 @@ func handlePortBindingUpdate(updateOpts ports.UpdateOptsBuilder, resource *resou } } - if resource.HostID != "" { - if resource.HostID != osResource.HostID { + if resolvedHostID != "" { + if resolvedHostID != osResource.HostID { updateOpts = &portsbinding.UpdateOptsExt{ UpdateOptsBuilder: updateOpts, - HostID: &resource.HostID, + HostID: &resolvedHostID, } } } diff --git a/internal/controllers/port/actuator_test.go b/internal/controllers/port/actuator_test.go index 81a2a7cc6..d6f7186c0 100644 --- a/internal/controllers/port/actuator_test.go +++ b/internal/controllers/port/actuator_test.go @@ -359,7 +359,7 @@ func TestHandlePortBindingUpdate(t *testing.T) { }, } - updateOpts := handlePortBindingUpdate(&ports.UpdateOpts{}, resource, osResource) + updateOpts := handlePortBindingUpdate(&ports.UpdateOpts{}, resource, osResource, "") got, _ := needsUpdate(updateOpts) if got != tt.expectChange { diff --git a/internal/controllers/port/controller.go b/internal/controllers/port/controller.go index 6191d84b3..ae0d73b37 100644 --- a/internal/controllers/port/controller.go +++ b/internal/controllers/port/controller.go @@ -127,6 +127,17 @@ var ( return []string{string(*resource.Filter.ProjectRef)} }, ) + + serverDependency = dependency.NewDependency[*orcv1alpha1.PortList, *orcv1alpha1.Server]( + "spec.resource.hostID.serverRef", + func(port *orcv1alpha1.Port) []string { + resource := port.Spec.Resource + if resource == nil || resource.HostID == nil || resource.HostID.ServerRef == "" { + return nil + } + return []string{string(resource.HostID.ServerRef)} + }, + ) ) // serverToPortMapFunc creates a mapping function that reconciles ports when: @@ -291,6 +302,11 @@ func (c portReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctr return err } + serverWatchEventHandler, err := serverDependency.WatchEventHandler(log, k8sClient) + if err != nil { + return err + } + builder := ctrl.NewControllerManagedBy(mgr). WithOptions(options). For(&orcv1alpha1.Port{}). @@ -314,6 +330,9 @@ func (c portReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctr Watches(&orcv1alpha1.Project{}, projectImportWatchEventHandler, builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Project{})), ). + Watches(&orcv1alpha1.Server{}, serverWatchEventHandler, + builder.WithPredicates(predicates.NewBecameAvailable(log, &orcv1alpha1.Server{})), + ). Watches(&orcv1alpha1.Server{}, handler.EnqueueRequestsFromMapFunc(serverToPortMapFunc(ctx, k8sClient)), builder.WithPredicates(predicates.NewServerInterfacesChanged(log)), ) @@ -325,6 +344,7 @@ func (c portReconcilerConstructor) SetupWithManager(ctx context.Context, mgr ctr securityGroupDependency.AddToManager(ctx, mgr), projectDependency.AddToManager(ctx, mgr), projectImportDependency.AddToManager(ctx, mgr), + serverDependency.AddToManager(ctx, mgr), credentialsDependency.AddToManager(ctx, mgr), credentials.AddCredentialsWatch(log, k8sClient, builder, credentialsDependency), ); err != nil { diff --git a/internal/controllers/port/tests/port-create-full/00-create-resource.yaml b/internal/controllers/port/tests/port-create-full/00-create-resource.yaml index bbb52641d..26e1d9d27 100644 --- a/internal/controllers/port/tests/port-create-full/00-create-resource.yaml +++ b/internal/controllers/port/tests/port-create-full/00-create-resource.yaml @@ -85,4 +85,5 @@ spec: vnicType: macvtap projectRef: port-create-full macAddress: fa:16:3e:23:fd:d7 - hostID: devstack + hostID: + id: devstack diff --git a/internal/controllers/port/tests/port-update/01-updated-resource.yaml b/internal/controllers/port/tests/port-update/01-updated-resource.yaml index 107b4ab5d..2af467f7c 100644 --- a/internal/controllers/port/tests/port-update/01-updated-resource.yaml +++ b/internal/controllers/port/tests/port-update/01-updated-resource.yaml @@ -31,4 +31,5 @@ spec: secretName: openstack-clouds managementPolicy: managed resource: - hostID: devstack + hostID: + id: devstack diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/hostid.go b/pkg/clients/applyconfiguration/api/v1alpha1/hostid.go new file mode 100644 index 000000000..3f571fe0b --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/hostid.go @@ -0,0 +1,52 @@ +/* +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" +) + +// HostIDApplyConfiguration represents a declarative configuration of the HostID type for use +// with apply. +type HostIDApplyConfiguration struct { + ID *string `json:"id,omitempty"` + ServerRef *apiv1alpha1.KubernetesNameRef `json:"serverRef,omitempty"` +} + +// HostIDApplyConfiguration constructs a declarative configuration of the HostID type for use with +// apply. +func HostID() *HostIDApplyConfiguration { + return &HostIDApplyConfiguration{} +} + +// WithID sets the ID 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 ID field is set to the value of the last call. +func (b *HostIDApplyConfiguration) WithID(value string) *HostIDApplyConfiguration { + b.ID = &value + return b +} + +// WithServerRef sets the ServerRef 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 ServerRef field is set to the value of the last call. +func (b *HostIDApplyConfiguration) WithServerRef(value apiv1alpha1.KubernetesNameRef) *HostIDApplyConfiguration { + b.ServerRef = &value + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go index aab07d8bc..491a5c818 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go @@ -37,7 +37,7 @@ type PortResourceSpecApplyConfiguration struct { PortSecurity *apiv1alpha1.PortSecurityState `json:"portSecurity,omitempty"` ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` MACAddress *string `json:"macAddress,omitempty"` - HostID *string `json:"hostID,omitempty"` + HostID *HostIDApplyConfiguration `json:"hostID,omitempty"` } // PortResourceSpecApplyConfiguration constructs a declarative configuration of the PortResourceSpec type for use with @@ -159,7 +159,7 @@ func (b *PortResourceSpecApplyConfiguration) WithMACAddress(value string) *PortR // WithHostID sets the HostID 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 HostID field is set to the value of the last call. -func (b *PortResourceSpecApplyConfiguration) WithHostID(value string) *PortResourceSpecApplyConfiguration { - b.HostID = &value +func (b *PortResourceSpecApplyConfiguration) WithHostID(value *HostIDApplyConfiguration) *PortResourceSpecApplyConfiguration { + b.HostID = value return b } diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index abaeca27f..f545b52e7 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -634,6 +634,15 @@ var schemaYAML = typed.YAMLObject(`types: - name: resource type: namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.GroupResourceStatus +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostID + map: + fields: + - name: id + type: + scalar: string + - name: serverRef + type: + scalar: string - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostRoute map: fields: @@ -1335,7 +1344,7 @@ var schemaYAML = typed.YAMLObject(`types: scalar: string - name: hostID type: - scalar: string + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.HostID - name: macAddress type: scalar: string diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index 5a3990951..3df1b92e4 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -112,6 +112,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.GroupSpecApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("GroupStatus"): return &apiv1alpha1.GroupStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("HostID"): + return &apiv1alpha1.HostIDApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("HostRoute"): return &apiv1alpha1.HostRouteApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("HostRouteStatus"): diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 32018f962..9709d49a9 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -892,6 +892,26 @@ _Appears in:_ | `resource` _[GroupResourceStatus](#groupresourcestatus)_ | resource contains the observed state of the OpenStack resource. | | | +#### HostID + + + +HostID specifies how to determine the host ID for port binding. +Exactly one of the fields must be set. + +_Validation:_ +- MaxProperties: 1 +- MinProperties: 1 + +_Appears in:_ +- [PortResourceSpec](#portresourcespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `id` _string_ | id is the literal host ID string to use for binding:host_id.
This is mutually exclusive with serverRef. | | MaxLength: 36
| +| `serverRef` _[KubernetesNameRef](#kubernetesnameref)_ | serverRef is a reference to an ORC Server resource from which to
retrieve the hostID for port binding. The hostID will be read from
the Server's status.resource.hostID field.
This is mutually exclusive with id. | | MaxLength: 253
MinLength: 1
| + + #### HostRoute @@ -1616,6 +1636,7 @@ _Appears in:_ - [FloatingIPResourceSpec](#floatingipresourcespec) - [GroupFilter](#groupfilter) - [GroupResourceSpec](#groupresourcespec) +- [HostID](#hostid) - [NetworkFilter](#networkfilter) - [NetworkResourceSpec](#networkresourcespec) - [PortFilter](#portfilter) @@ -2171,7 +2192,7 @@ _Appears in:_ | `portSecurity` _[PortSecurityState](#portsecuritystate)_ | portSecurity controls port security for this port.
When set to Enabled, port security is enabled.
When set to Disabled, port security is disabled and SecurityGroupRefs must be empty.
When set to Inherit (default), it takes the value from the network level. | Inherit | Enum: [Enabled Disabled Inherit]
| | `projectRef` _[KubernetesNameRef](#kubernetesnameref)_ | projectRef is a reference to the ORC Project this resource is associated with.
Typically, only used by admin. | | MaxLength: 253
MinLength: 1
| | `macAddress` _string_ | macAddress is the MAC address of the port. | | MaxLength: 32
| -| `hostID` _string_ | hostID is the ID of host where the port resides. | | MaxLength: 36
| +| `hostID` _[HostID](#hostid)_ | hostID specifies the host where the port will be bound. | | MaxProperties: 1
MinProperties: 1
| #### PortResourceStatus