Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions api/v1alpha1/port_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand Down
20 changes: 20 additions & 0 deletions api/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

35 changes: 31 additions & 4 deletions cmd/models-schema/zz_generated.openapi.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

28 changes: 25 additions & 3 deletions config/crd/bases/openstack.k-orc.cloud_ports.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
67 changes: 61 additions & 6 deletions internal/controllers/port/actuator.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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{
Expand All @@ -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,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion internal/controllers/port/actuator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
20 changes: 20 additions & 0 deletions internal/controllers/port/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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{}).
Expand All @@ -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)),
)
Expand All @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,5 @@ spec:
vnicType: macvtap
projectRef: port-create-full
macAddress: fa:16:3e:23:fd:d7
hostID: devstack
hostID:
id: devstack
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@ spec:
secretName: openstack-clouds
managementPolicy: managed
resource:
hostID: devstack
hostID:
id: devstack
Loading
Loading