From 6901189c4dc8e72bd14534d2c943e58bcd23c94e Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Mon, 12 Jan 2026 11:34:31 -0300 Subject: [PATCH 1/4] port: add propagationUplinkStatus field --- .github/workflows/e2e.yaml | 2 +- api/v1alpha1/port_types.go | 7 +++- api/v1alpha1/zz_generated.deepcopy.go | 10 +++--- cmd/models-schema/zz_generated.openapi.go | 7 ++++ .../bases/openstack.k-orc.cloud_ports.yaml | 5 +++ internal/controllers/port/actuator.go | 24 +++++++++---- internal/controllers/port/actuator_test.go | 28 +++++++++++++++ .../tests/port-create-full/00-assert.yaml | 1 + .../port-create-full/00-create-resource.yaml | 1 + .../tests/port-create-minimal/00-assert.yaml | 2 +- .../port/tests/port-update/00-assert.yaml | 6 ++-- .../port/tests/port-update/01-assert.yaml | 4 +-- .../port-update/01-updated-resource.yaml | 1 + .../port/tests/port-update/02-assert.yaml | 2 +- .../api/v1alpha1/portresourcespec.go | 35 ++++++++++++------- .../applyconfiguration/internal/internal.go | 3 ++ website/docs/crd-reference.md | 1 + 17 files changed, 107 insertions(+), 32 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index da07b7eee..3e8ef73d5 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -36,7 +36,7 @@ jobs: with: enable_workaround_docker_io: 'false' branch: ${{ matrix.openstack_version }} - enabled_services: "openstack-cli-server" + enabled_services: "openstack-cli-server,neutron-uplink-status-propagation" - name: Deploy a Kind Cluster uses: helm/kind-action@92086f6be054225fa813e0a4b13787fc9088faab diff --git a/api/v1alpha1/port_types.go b/api/v1alpha1/port_types.go index 9e51d0153..70ac01628 100644 --- a/api/v1alpha1/port_types.go +++ b/api/v1alpha1/port_types.go @@ -185,6 +185,11 @@ type PortResourceSpec struct { // +kubebuilder:validation:MaxLength=36 // +optional HostID string `json:"hostID,omitempty"` + + // propagateUplinkStatus represents the uplink status propagation of + // the port. + // +optional + PropagateUplinkStatus *bool `json:"propagateUplinkStatus,omitempty"` } type PortResourceStatus struct { @@ -266,7 +271,7 @@ type PortResourceStatus struct { // propagateUplinkStatus represents the uplink status propagation of // the port. // +optional - PropagateUplinkStatus *bool `json:"propagateUplinkStatus,omitempty"` + PropagateUplinkStatus bool `json:"propagateUplinkStatus,omitempty"` // vnicType is the type of vNIC which this port is attached to. // +kubebuilder:validation:MaxLength:=64 diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 74fd9dcdf..3dc3ada57 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -2529,6 +2529,11 @@ func (in *PortResourceSpec) DeepCopyInto(out *PortResourceSpec) { *out = new(KubernetesNameRef) **out = **in } + if in.PropagateUplinkStatus != nil { + in, out := &in.PropagateUplinkStatus, &out.PropagateUplinkStatus + *out = new(bool) + **out = **in + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PortResourceSpec. @@ -2569,11 +2574,6 @@ func (in *PortResourceStatus) DeepCopyInto(out *PortResourceStatus) { *out = make([]string, len(*in)) copy(*out, *in) } - if in.PropagateUplinkStatus != nil { - in, out := &in.PropagateUplinkStatus, &out.PropagateUplinkStatus - *out = new(bool) - **out = **in - } if in.PortSecurityEnabled != nil { in, out := &in.PortSecurityEnabled, &out.PortSecurityEnabled *out = new(bool) diff --git a/cmd/models-schema/zz_generated.openapi.go b/cmd/models-schema/zz_generated.openapi.go index 3b90d275e..1beb237d8 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -4918,6 +4918,13 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref c Format: "", }, }, + "propagateUplinkStatus": { + SchemaProps: spec.SchemaProps{ + Description: "propagateUplinkStatus represents the uplink status propagation of the port.", + Type: []string{"boolean"}, + Format: "", + }, + }, }, Required: []string{"networkRef"}, }, diff --git a/config/crd/bases/openstack.k-orc.cloud_ports.yaml b/config/crd/bases/openstack.k-orc.cloud_ports.yaml index c14173d71..ea3135633 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -347,6 +347,11 @@ spec: x-kubernetes-validations: - message: projectRef is immutable rule: self == oldSelf + propagateUplinkStatus: + description: |- + propagateUplinkStatus represents the uplink status propagation of + the port. + type: boolean securityGroupRefs: description: |- securityGroupRefs are the names of the security groups associated diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index 822f6a5be..5b78e3041 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -171,12 +171,13 @@ func (actuator portActuator) CreateResource(ctx context.Context, obj *orcv1alpha } createOpts := ports.CreateOpts{ - NetworkID: *network.Status.ID, - Name: getResourceName(obj), - Description: string(ptr.Deref(resource.Description, "")), - ProjectID: projectID, - AdminStateUp: resource.AdminStateUp, - MACAddress: resource.MACAddress, + NetworkID: *network.Status.ID, + Name: getResourceName(obj), + Description: string(ptr.Deref(resource.Description, "")), + ProjectID: projectID, + AdminStateUp: resource.AdminStateUp, + MACAddress: resource.MACAddress, + PropagateUplinkStatus: resource.PropagateUplinkStatus, } if len(resource.AllowedAddressPairs) > 0 { @@ -345,6 +346,7 @@ func (actuator portActuator) updateResource(ctx context.Context, obj orcObjectPT handleAllowedAddressPairsUpdate(baseUpdateOpts, resource, osResource) handleSecurityGroupRefsUpdate(baseUpdateOpts, resource, osResource, secGroupMap) handleAdminStateUpUpdate(baseUpdateOpts, resource, osResource) + handlePropagateUplinkStatusUpdate(baseUpdateOpts, resource, osResource) updateOpts = baseUpdateOpts } @@ -530,6 +532,16 @@ func handleAdminStateUpUpdate(updateOpts *ports.UpdateOpts, resource *resourceSp } } +func handlePropagateUplinkStatusUpdate(updateOpts *ports.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { + // When this field is not defined, let's set this as `false` to + // avoid errors in environments where uplink-propagation-status + // extension isn't enabled. + propagateUplinkStatus := ptr.Deref(resource.PropagateUplinkStatus, false) + if propagateUplinkStatus != osResource.PropagateUplinkStatus { + updateOpts.PropagateUplinkStatus = &propagateUplinkStatus + } +} + type portHelperFactory struct{} var _ helperFactory = portHelperFactory{} diff --git a/internal/controllers/port/actuator_test.go b/internal/controllers/port/actuator_test.go index 81a2a7cc6..ee5d3e397 100644 --- a/internal/controllers/port/actuator_test.go +++ b/internal/controllers/port/actuator_test.go @@ -435,3 +435,31 @@ func TestHandleAdminStateUpUpdate(t *testing.T) { }) } } + +func TestHandlePropagateUplinkStatusUpdate(t *testing.T) { + testCases := []struct { + name string + newValue *bool + existingValue bool + expectChange bool + }{ + {name: "Set the same value as the existing one", newValue: ptr.To(true), existingValue: true, expectChange: false}, + {name: "Enabled when was disabled", newValue: ptr.To(true), existingValue: false, expectChange: true}, + {name: "Disable if it is not defined on spec", newValue: nil, existingValue: true, expectChange: true}, + } + + for _, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + resource := &orcv1alpha1.PortResourceSpec{PropagateUplinkStatus: tt.newValue} + osResource := &osclients.PortExt{Port: ports.Port{PropagateUplinkStatus: tt.existingValue}} + updateOpts := &ports.UpdateOpts{} + + handlePropagateUplinkStatusUpdate(updateOpts, resource, osResource) + + got, _ := needsUpdate(updateOpts) + if got != tt.expectChange { + t.Errorf("expected needsUpdate=%v, got %v", tt.expectChange, got) + } + }) + } +} diff --git a/internal/controllers/port/tests/port-create-full/00-assert.yaml b/internal/controllers/port/tests/port-create-full/00-assert.yaml index f026eea4a..6d854ac75 100644 --- a/internal/controllers/port/tests/port-create-full/00-assert.yaml +++ b/internal/controllers/port/tests/port-create-full/00-assert.yaml @@ -17,6 +17,7 @@ status: vnicType: macvtap macAddress: fa:16:3e:23:fd:d7 hostID: devstack + propagateUplinkStatus: true tags: - tag1 --- 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..3e8beeb75 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 @@ -86,3 +86,4 @@ spec: projectRef: port-create-full macAddress: fa:16:3e:23:fd:d7 hostID: devstack + propagateUplinkStatus: true diff --git a/internal/controllers/port/tests/port-create-minimal/00-assert.yaml b/internal/controllers/port/tests/port-create-minimal/00-assert.yaml index 9c6d861fc..d1dc5833a 100644 --- a/internal/controllers/port/tests/port-create-minimal/00-assert.yaml +++ b/internal/controllers/port/tests/port-create-minimal/00-assert.yaml @@ -9,7 +9,7 @@ status: adminStateUp: true portSecurityEnabled: true propagateUplinkStatus: false - revisionNumber: 1 + revisionNumber: 2 status: DOWN vnicType: normal --- diff --git a/internal/controllers/port/tests/port-update/00-assert.yaml b/internal/controllers/port/tests/port-update/00-assert.yaml index 6ec7e451d..8ddd7baae 100644 --- a/internal/controllers/port/tests/port-update/00-assert.yaml +++ b/internal/controllers/port/tests/port-update/00-assert.yaml @@ -31,7 +31,9 @@ status: adminStateUp: true portSecurityEnabled: false propagateUplinkStatus: false - revisionNumber: 1 + # The revisionNumber has increased to enforce `propagateUplinkStatus` default + # value. + revisionNumber: 2 status: DOWN vnicType: normal conditions: @@ -54,7 +56,7 @@ status: adminStateUp: true portSecurityEnabled: true propagateUplinkStatus: false - revisionNumber: 1 + revisionNumber: 2 status: DOWN vnicType: normal conditions: diff --git a/internal/controllers/port/tests/port-update/01-assert.yaml b/internal/controllers/port/tests/port-update/01-assert.yaml index 1bcaf2d7f..0a67ad11f 100644 --- a/internal/controllers/port/tests/port-update/01-assert.yaml +++ b/internal/controllers/port/tests/port-update/01-assert.yaml @@ -34,7 +34,7 @@ status: description: port-update-updated adminStateUp: true portSecurityEnabled: true - propagateUplinkStatus: false + propagateUplinkStatus: true status: DOWN vnicType: direct allowedAddressPairs: @@ -63,4 +63,4 @@ status: reason: Success - type: Progressing status: "False" - reason: Success + reason: Success 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..6dd73b18d 100644 --- a/internal/controllers/port/tests/port-update/01-updated-resource.yaml +++ b/internal/controllers/port/tests/port-update/01-updated-resource.yaml @@ -20,6 +20,7 @@ spec: - tag1 vnicType: direct portSecurity: Enabled + propagateUplinkStatus: true --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Port diff --git a/internal/controllers/port/tests/port-update/02-assert.yaml b/internal/controllers/port/tests/port-update/02-assert.yaml index 314caa5b0..bbea0df06 100644 --- a/internal/controllers/port/tests/port-update/02-assert.yaml +++ b/internal/controllers/port/tests/port-update/02-assert.yaml @@ -35,4 +35,4 @@ status: - type: Progressing message: OpenStack resource is up to date status: "False" - reason: Success \ No newline at end of file + reason: Success diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go index aab07d8bc..a157e70ff 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go @@ -25,19 +25,20 @@ import ( // PortResourceSpecApplyConfiguration represents a declarative configuration of the PortResourceSpec type for use // with apply. type PortResourceSpecApplyConfiguration struct { - Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` - Description *apiv1alpha1.NeutronDescription `json:"description,omitempty"` - NetworkRef *apiv1alpha1.KubernetesNameRef `json:"networkRef,omitempty"` - Tags []apiv1alpha1.NeutronTag `json:"tags,omitempty"` - AllowedAddressPairs []AllowedAddressPairApplyConfiguration `json:"allowedAddressPairs,omitempty"` - Addresses []AddressApplyConfiguration `json:"addresses,omitempty"` - AdminStateUp *bool `json:"adminStateUp,omitempty"` - SecurityGroupRefs []apiv1alpha1.OpenStackName `json:"securityGroupRefs,omitempty"` - VNICType *string `json:"vnicType,omitempty"` - PortSecurity *apiv1alpha1.PortSecurityState `json:"portSecurity,omitempty"` - ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` - MACAddress *string `json:"macAddress,omitempty"` - HostID *string `json:"hostID,omitempty"` + Name *apiv1alpha1.OpenStackName `json:"name,omitempty"` + Description *apiv1alpha1.NeutronDescription `json:"description,omitempty"` + NetworkRef *apiv1alpha1.KubernetesNameRef `json:"networkRef,omitempty"` + Tags []apiv1alpha1.NeutronTag `json:"tags,omitempty"` + AllowedAddressPairs []AllowedAddressPairApplyConfiguration `json:"allowedAddressPairs,omitempty"` + Addresses []AddressApplyConfiguration `json:"addresses,omitempty"` + AdminStateUp *bool `json:"adminStateUp,omitempty"` + SecurityGroupRefs []apiv1alpha1.OpenStackName `json:"securityGroupRefs,omitempty"` + VNICType *string `json:"vnicType,omitempty"` + PortSecurity *apiv1alpha1.PortSecurityState `json:"portSecurity,omitempty"` + ProjectRef *apiv1alpha1.KubernetesNameRef `json:"projectRef,omitempty"` + MACAddress *string `json:"macAddress,omitempty"` + HostID *string `json:"hostID,omitempty"` + PropagateUplinkStatus *bool `json:"propagateUplinkStatus,omitempty"` } // PortResourceSpecApplyConfiguration constructs a declarative configuration of the PortResourceSpec type for use with @@ -163,3 +164,11 @@ func (b *PortResourceSpecApplyConfiguration) WithHostID(value string) *PortResou b.HostID = &value return b } + +// WithPropagateUplinkStatus sets the PropagateUplinkStatus 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 PropagateUplinkStatus field is set to the value of the last call. +func (b *PortResourceSpecApplyConfiguration) WithPropagateUplinkStatus(value bool) *PortResourceSpecApplyConfiguration { + b.PropagateUplinkStatus = &value + return b +} diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index abaeca27f..b6805275f 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -1351,6 +1351,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: projectRef type: scalar: string + - name: propagateUplinkStatus + type: + scalar: boolean - name: securityGroupRefs type: list: diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 32018f962..ae1df1118 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -2172,6 +2172,7 @@ _Appears in:_ | `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
| +| `propagateUplinkStatus` _boolean_ | propagateUplinkStatus represents the uplink status propagation of
the port. | | | #### PortResourceStatus From 4d215478c6b1822b8e1ddbc4ed5dd1fe5ca33984 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Tue, 13 Jan 2026 13:22:41 -0300 Subject: [PATCH 2/4] add neutron plugin to the CI --- .github/workflows/e2e.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 3e8ef73d5..7a2b155ed 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -37,6 +37,8 @@ jobs: enable_workaround_docker_io: 'false' branch: ${{ matrix.openstack_version }} enabled_services: "openstack-cli-server,neutron-uplink-status-propagation" + conf_overrides: | + enable_plugin neutron https://opendev.org/openstack/neutron.git ${{ matrix.openstack_version }} - name: Deploy a Kind Cluster uses: helm/kind-action@92086f6be054225fa813e0a4b13787fc9088faab From f4abfe6400a68778bd7daff5534c08a579aaaa75 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Fri, 16 Jan 2026 01:09:08 -0300 Subject: [PATCH 3/4] port: fix update flow for PropagateUplinkStatus We have a missing pointer on Gophercloud, this is causing a misbehavior. For workarouding purposes, was added a new struct to support a pointer for that field. Tests were also fixed. --- .github/workflows/e2e.yaml | 2 +- api/v1alpha1/port_types.go | 2 +- api/v1alpha1/zz_generated.deepcopy.go | 5 +++++ internal/controllers/port/actuator.go | 19 +++++++++++++------ internal/controllers/port/actuator_test.go | 10 +++++----- internal/controllers/port/status.go | 5 ++++- .../tests/port-create-full/00-assert.yaml | 1 - .../port-create-full/00-create-resource.yaml | 2 +- .../tests/port-create-minimal/00-assert.yaml | 4 ++-- .../tests/port-create-sriov/00-assert.yaml | 4 ++-- .../port/tests/port-update/00-assert.yaml | 10 ++++------ .../port/tests/port-update/01-assert.yaml | 2 +- .../port-update/01-updated-resource.yaml | 2 +- .../port/tests/port-update/02-assert.yaml | 2 +- internal/osclients/networking.go | 9 +++++++++ 15 files changed, 50 insertions(+), 29 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index 7a2b155ed..e48f165fe 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -36,7 +36,7 @@ jobs: with: enable_workaround_docker_io: 'false' branch: ${{ matrix.openstack_version }} - enabled_services: "openstack-cli-server,neutron-uplink-status-propagation" + enabled_services: "openstack-cli-server,neutron-uplink-status-propagation,uplink_status_propagation_updatable" conf_overrides: | enable_plugin neutron https://opendev.org/openstack/neutron.git ${{ matrix.openstack_version }} diff --git a/api/v1alpha1/port_types.go b/api/v1alpha1/port_types.go index 70ac01628..91d5b5ae5 100644 --- a/api/v1alpha1/port_types.go +++ b/api/v1alpha1/port_types.go @@ -271,7 +271,7 @@ type PortResourceStatus struct { // propagateUplinkStatus represents the uplink status propagation of // the port. // +optional - PropagateUplinkStatus bool `json:"propagateUplinkStatus,omitempty"` + PropagateUplinkStatus *bool `json:"propagateUplinkStatus,omitempty"` // vnicType is the type of vNIC which this port is attached to. // +kubebuilder:validation:MaxLength:=64 diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 3dc3ada57..28a9f0c77 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -2574,6 +2574,11 @@ func (in *PortResourceStatus) DeepCopyInto(out *PortResourceStatus) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.PropagateUplinkStatus != nil { + in, out := &in.PropagateUplinkStatus, &out.PropagateUplinkStatus + *out = new(bool) + **out = **in + } if in.PortSecurityEnabled != nil { in, out := &in.PortSecurityEnabled, &out.PortSecurityEnabled *out = new(bool) diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index 5b78e3041..f7e5fe19e 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -533,12 +533,19 @@ func handleAdminStateUpUpdate(updateOpts *ports.UpdateOpts, resource *resourceSp } func handlePropagateUplinkStatusUpdate(updateOpts *ports.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { - // When this field is not defined, let's set this as `false` to - // avoid errors in environments where uplink-propagation-status - // extension isn't enabled. - propagateUplinkStatus := ptr.Deref(resource.PropagateUplinkStatus, false) - if propagateUplinkStatus != osResource.PropagateUplinkStatus { - updateOpts.PropagateUplinkStatus = &propagateUplinkStatus + propagateUplinkStatus := resource.PropagateUplinkStatus + if osResource.PropagateUplinkStatusPtr != nil { + if propagateUplinkStatus != nil { + if *propagateUplinkStatus != *osResource.PropagateUplinkStatusPtr { + updateOpts.PropagateUplinkStatus = propagateUplinkStatus + } + } else { + // Fallback to the default value if unset from spec and extension + // is enabled. + if !*osResource.PropagateUplinkStatusPtr { + updateOpts.PropagateUplinkStatus = ptr.To(true) + } + } } } diff --git a/internal/controllers/port/actuator_test.go b/internal/controllers/port/actuator_test.go index ee5d3e397..abcdc192c 100644 --- a/internal/controllers/port/actuator_test.go +++ b/internal/controllers/port/actuator_test.go @@ -440,18 +440,18 @@ func TestHandlePropagateUplinkStatusUpdate(t *testing.T) { testCases := []struct { name string newValue *bool - existingValue bool + existingValue *bool expectChange bool }{ - {name: "Set the same value as the existing one", newValue: ptr.To(true), existingValue: true, expectChange: false}, - {name: "Enabled when was disabled", newValue: ptr.To(true), existingValue: false, expectChange: true}, - {name: "Disable if it is not defined on spec", newValue: nil, existingValue: true, expectChange: true}, + {name: "If the same value, do nothing", newValue: ptr.To(true), existingValue: ptr.To(true), expectChange: false}, + {name: "Enable it when was disabled", newValue: ptr.To(true), existingValue: ptr.To(false), expectChange: true}, + {name: "Enable if it is not defined on spec", newValue: nil, existingValue: ptr.To(false), expectChange: true}, } for _, tt := range testCases { t.Run(tt.name, func(t *testing.T) { resource := &orcv1alpha1.PortResourceSpec{PropagateUplinkStatus: tt.newValue} - osResource := &osclients.PortExt{Port: ports.Port{PropagateUplinkStatus: tt.existingValue}} + osResource := &osclients.PortExt{PortTmpExt: osclients.PortTmpExt{PropagateUplinkStatusPtr: tt.existingValue}} updateOpts := &ports.UpdateOpts{} handlePropagateUplinkStatusUpdate(updateOpts, resource, osResource) diff --git a/internal/controllers/port/status.go b/internal/controllers/port/status.go index 379e91e70..221f0dcf0 100644 --- a/internal/controllers/port/status.go +++ b/internal/controllers/port/status.go @@ -67,7 +67,6 @@ func (portStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResou WithNetworkID(osResource.NetworkID). WithTags(osResource.Tags...). WithSecurityGroups(osResource.SecurityGroups...). - WithPropagateUplinkStatus(osResource.PropagateUplinkStatus). WithVNICType(osResource.VNICType). WithPortSecurityEnabled(osResource.PortSecurityEnabled). WithRevisionNumber(int64(osResource.RevisionNumber)). @@ -104,5 +103,9 @@ func (portStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResou resourceStatus.WithFixedIPs(fixedIPs...) } + if osResource.PropagateUplinkStatusPtr != nil { + resourceStatus.WithPropagateUplinkStatus(*osResource.PropagateUplinkStatusPtr) + } + statusApply.WithResource(resourceStatus) } diff --git a/internal/controllers/port/tests/port-create-full/00-assert.yaml b/internal/controllers/port/tests/port-create-full/00-assert.yaml index 6d854ac75..f026eea4a 100644 --- a/internal/controllers/port/tests/port-create-full/00-assert.yaml +++ b/internal/controllers/port/tests/port-create-full/00-assert.yaml @@ -17,7 +17,6 @@ status: vnicType: macvtap macAddress: fa:16:3e:23:fd:d7 hostID: devstack - propagateUplinkStatus: true tags: - tag1 --- 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 3e8beeb75..22c97ca52 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 @@ -86,4 +86,4 @@ spec: projectRef: port-create-full macAddress: fa:16:3e:23:fd:d7 hostID: devstack - propagateUplinkStatus: true + propagateUplinkStatus: false diff --git a/internal/controllers/port/tests/port-create-minimal/00-assert.yaml b/internal/controllers/port/tests/port-create-minimal/00-assert.yaml index d1dc5833a..ba68844fe 100644 --- a/internal/controllers/port/tests/port-create-minimal/00-assert.yaml +++ b/internal/controllers/port/tests/port-create-minimal/00-assert.yaml @@ -8,8 +8,8 @@ status: name: port-create-minimal adminStateUp: true portSecurityEnabled: true - propagateUplinkStatus: false - revisionNumber: 2 + propagateUplinkStatus: true + revisionNumber: 1 status: DOWN vnicType: normal --- diff --git a/internal/controllers/port/tests/port-create-sriov/00-assert.yaml b/internal/controllers/port/tests/port-create-sriov/00-assert.yaml index 3277dfaea..180f4a512 100644 --- a/internal/controllers/port/tests/port-create-sriov/00-assert.yaml +++ b/internal/controllers/port/tests/port-create-sriov/00-assert.yaml @@ -9,7 +9,7 @@ status: description: Port from "create sriov" test adminStateUp: true portSecurityEnabled: false - propagateUplinkStatus: false + propagateUplinkStatus: true status: DOWN vnicType: direct tags: @@ -35,4 +35,4 @@ assertAll: - celExpr: "port.status.resource.fixedIPs[0].subnetID == subnet.status.id" - celExpr: "port.status.resource.fixedIPs[0].ip == '192.168.155.122'" - celExpr: "!has(port.status.resource.allowedAddressPairs)" - - celExpr: "!has(port.status.resource.securityGroups)" \ No newline at end of file + - celExpr: "!has(port.status.resource.securityGroups)" diff --git a/internal/controllers/port/tests/port-update/00-assert.yaml b/internal/controllers/port/tests/port-update/00-assert.yaml index 8ddd7baae..836189292 100644 --- a/internal/controllers/port/tests/port-update/00-assert.yaml +++ b/internal/controllers/port/tests/port-update/00-assert.yaml @@ -30,10 +30,8 @@ status: name: port-update adminStateUp: true portSecurityEnabled: false - propagateUplinkStatus: false - # The revisionNumber has increased to enforce `propagateUplinkStatus` default - # value. - revisionNumber: 2 + propagateUplinkStatus: true + revisionNumber: 1 status: DOWN vnicType: normal conditions: @@ -55,8 +53,8 @@ status: name: port-update-admin adminStateUp: true portSecurityEnabled: true - propagateUplinkStatus: false - revisionNumber: 2 + propagateUplinkStatus: true + revisionNumber: 1 status: DOWN vnicType: normal conditions: diff --git a/internal/controllers/port/tests/port-update/01-assert.yaml b/internal/controllers/port/tests/port-update/01-assert.yaml index 0a67ad11f..38cd04ed3 100644 --- a/internal/controllers/port/tests/port-update/01-assert.yaml +++ b/internal/controllers/port/tests/port-update/01-assert.yaml @@ -34,7 +34,7 @@ status: description: port-update-updated adminStateUp: true portSecurityEnabled: true - propagateUplinkStatus: true + propagateUplinkStatus: false status: DOWN vnicType: direct allowedAddressPairs: 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 6dd73b18d..3375507cb 100644 --- a/internal/controllers/port/tests/port-update/01-updated-resource.yaml +++ b/internal/controllers/port/tests/port-update/01-updated-resource.yaml @@ -20,7 +20,7 @@ spec: - tag1 vnicType: direct portSecurity: Enabled - propagateUplinkStatus: true + propagateUplinkStatus: false --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Port diff --git a/internal/controllers/port/tests/port-update/02-assert.yaml b/internal/controllers/port/tests/port-update/02-assert.yaml index bbea0df06..ec19a5fd8 100644 --- a/internal/controllers/port/tests/port-update/02-assert.yaml +++ b/internal/controllers/port/tests/port-update/02-assert.yaml @@ -24,7 +24,7 @@ status: name: port-update adminStateUp: true portSecurityEnabled: false - propagateUplinkStatus: false + propagateUplinkStatus: true status: DOWN vnicType: normal conditions: diff --git a/internal/osclients/networking.go b/internal/osclients/networking.go index 99156d64e..69b6cf753 100644 --- a/internal/osclients/networking.go +++ b/internal/osclients/networking.go @@ -52,10 +52,19 @@ type NetworkExt struct { provider.NetworkProviderExt } +// NOTE(winiciusallan): This is a temporary extension struct to +// workaround a missing pointer on Gophercloud and must be +// removed in future releases. +// See https://github.com/gophercloud/gophercloud/issues/3605 +type PortTmpExt struct { + PropagateUplinkStatusPtr *bool `json:"propagate_uplink_status,omitempty"` +} + type PortExt struct { ports.Port portsecurity.PortSecurityExt portsbinding.PortsBindingExt + PortTmpExt //nolint:govet } type NetworkClient interface { From 7112a07fdb015ba8078dc960c7e6478113ff7ff6 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Fri, 16 Jan 2026 14:43:32 -0300 Subject: [PATCH 4/4] change: make propagateUplinkStatus immutable --- .github/workflows/e2e.yaml | 2 +- api/v1alpha1/port_types.go | 1 + .../bases/openstack.k-orc.cloud_ports.yaml | 3 ++ internal/controllers/port/actuator.go | 18 ------------ internal/controllers/port/actuator_test.go | 28 ------------------- .../port/tests/port-update/01-assert.yaml | 1 - .../port-update/01-updated-resource.yaml | 1 - 7 files changed, 5 insertions(+), 49 deletions(-) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index e48f165fe..7a2b155ed 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -36,7 +36,7 @@ jobs: with: enable_workaround_docker_io: 'false' branch: ${{ matrix.openstack_version }} - enabled_services: "openstack-cli-server,neutron-uplink-status-propagation,uplink_status_propagation_updatable" + enabled_services: "openstack-cli-server,neutron-uplink-status-propagation" conf_overrides: | enable_plugin neutron https://opendev.org/openstack/neutron.git ${{ matrix.openstack_version }} diff --git a/api/v1alpha1/port_types.go b/api/v1alpha1/port_types.go index 91d5b5ae5..055e1855e 100644 --- a/api/v1alpha1/port_types.go +++ b/api/v1alpha1/port_types.go @@ -189,6 +189,7 @@ type PortResourceSpec struct { // propagateUplinkStatus represents the uplink status propagation of // the port. // +optional + // +kubebuilder:validation:XValidation:rule="self == oldSelf",message="propagateUplinkStatus is immutable" PropagateUplinkStatus *bool `json:"propagateUplinkStatus,omitempty"` } diff --git a/config/crd/bases/openstack.k-orc.cloud_ports.yaml b/config/crd/bases/openstack.k-orc.cloud_ports.yaml index ea3135633..1bc2ea45c 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -352,6 +352,9 @@ spec: propagateUplinkStatus represents the uplink status propagation of the port. type: boolean + x-kubernetes-validations: + - message: propagateUplinkStatus is immutable + rule: self == oldSelf securityGroupRefs: description: |- securityGroupRefs are the names of the security groups associated diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index f7e5fe19e..cc26b393a 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -346,7 +346,6 @@ func (actuator portActuator) updateResource(ctx context.Context, obj orcObjectPT handleAllowedAddressPairsUpdate(baseUpdateOpts, resource, osResource) handleSecurityGroupRefsUpdate(baseUpdateOpts, resource, osResource, secGroupMap) handleAdminStateUpUpdate(baseUpdateOpts, resource, osResource) - handlePropagateUplinkStatusUpdate(baseUpdateOpts, resource, osResource) updateOpts = baseUpdateOpts } @@ -532,23 +531,6 @@ func handleAdminStateUpUpdate(updateOpts *ports.UpdateOpts, resource *resourceSp } } -func handlePropagateUplinkStatusUpdate(updateOpts *ports.UpdateOpts, resource *resourceSpecT, osResource *osResourceT) { - propagateUplinkStatus := resource.PropagateUplinkStatus - if osResource.PropagateUplinkStatusPtr != nil { - if propagateUplinkStatus != nil { - if *propagateUplinkStatus != *osResource.PropagateUplinkStatusPtr { - updateOpts.PropagateUplinkStatus = propagateUplinkStatus - } - } else { - // Fallback to the default value if unset from spec and extension - // is enabled. - if !*osResource.PropagateUplinkStatusPtr { - updateOpts.PropagateUplinkStatus = ptr.To(true) - } - } - } -} - type portHelperFactory struct{} var _ helperFactory = portHelperFactory{} diff --git a/internal/controllers/port/actuator_test.go b/internal/controllers/port/actuator_test.go index abcdc192c..81a2a7cc6 100644 --- a/internal/controllers/port/actuator_test.go +++ b/internal/controllers/port/actuator_test.go @@ -435,31 +435,3 @@ func TestHandleAdminStateUpUpdate(t *testing.T) { }) } } - -func TestHandlePropagateUplinkStatusUpdate(t *testing.T) { - testCases := []struct { - name string - newValue *bool - existingValue *bool - expectChange bool - }{ - {name: "If the same value, do nothing", newValue: ptr.To(true), existingValue: ptr.To(true), expectChange: false}, - {name: "Enable it when was disabled", newValue: ptr.To(true), existingValue: ptr.To(false), expectChange: true}, - {name: "Enable if it is not defined on spec", newValue: nil, existingValue: ptr.To(false), expectChange: true}, - } - - for _, tt := range testCases { - t.Run(tt.name, func(t *testing.T) { - resource := &orcv1alpha1.PortResourceSpec{PropagateUplinkStatus: tt.newValue} - osResource := &osclients.PortExt{PortTmpExt: osclients.PortTmpExt{PropagateUplinkStatusPtr: tt.existingValue}} - updateOpts := &ports.UpdateOpts{} - - handlePropagateUplinkStatusUpdate(updateOpts, resource, osResource) - - got, _ := needsUpdate(updateOpts) - if got != tt.expectChange { - t.Errorf("expected needsUpdate=%v, got %v", tt.expectChange, got) - } - }) - } -} diff --git a/internal/controllers/port/tests/port-update/01-assert.yaml b/internal/controllers/port/tests/port-update/01-assert.yaml index 38cd04ed3..a751a1cdd 100644 --- a/internal/controllers/port/tests/port-update/01-assert.yaml +++ b/internal/controllers/port/tests/port-update/01-assert.yaml @@ -34,7 +34,6 @@ status: description: port-update-updated adminStateUp: true portSecurityEnabled: true - propagateUplinkStatus: false status: DOWN vnicType: direct allowedAddressPairs: 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 3375507cb..107b4ab5d 100644 --- a/internal/controllers/port/tests/port-update/01-updated-resource.yaml +++ b/internal/controllers/port/tests/port-update/01-updated-resource.yaml @@ -20,7 +20,6 @@ spec: - tag1 vnicType: direct portSecurity: Enabled - propagateUplinkStatus: false --- apiVersion: openstack.k-orc.cloud/v1alpha1 kind: Port