From c8d87a4a9f817ce436191e6ebe61b67cf3ab3a06 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Sun, 21 Dec 2025 13:42:21 -0300 Subject: [PATCH 1/2] port: add binding profile to the port controller Signed-off-by: Winicius Silva --- api/v1alpha1/port_types.go | 31 ++++++++++ api/v1alpha1/zz_generated.deepcopy.go | 35 ++++++++++++ cmd/models-schema/zz_generated.openapi.go | 56 +++++++++++++++++- .../bases/openstack.k-orc.cloud_ports.yaml | 50 ++++++++++++++++ internal/controllers/port/actuator.go | 57 +++++++++++++++++++ internal/controllers/port/status.go | 18 ++++++ .../tests/port-create-full/00-assert.yaml | 5 ++ .../port-create-full/00-create-resource.yaml | 4 ++ .../port-update/01-updated-resource.yaml | 6 +- .../api/v1alpha1/bindingprofile.go | 50 ++++++++++++++++ .../api/v1alpha1/portresourcespec.go | 9 +++ .../api/v1alpha1/portresourcestatus.go | 9 +++ .../applyconfiguration/internal/internal.go | 18 ++++++ pkg/clients/applyconfiguration/utils.go | 2 + website/docs/crd-reference.md | 20 +++++++ 15 files changed, 367 insertions(+), 3 deletions(-) create mode 100644 pkg/clients/applyconfiguration/api/v1alpha1/bindingprofile.go diff --git a/api/v1alpha1/port_types.go b/api/v1alpha1/port_types.go index 868748c17..c308fe142 100644 --- a/api/v1alpha1/port_types.go +++ b/api/v1alpha1/port_types.go @@ -96,6 +96,23 @@ type FixedIPStatus struct { SubnetID string `json:"subnetID,omitempty"` } +type BindingProfile struct { + // trustedVF indicates whether the VF for the port will become + // trusted by physical function to perform some privileged + // operations. + // +optional + TrustedVF *bool `json:"trustedVF,omitempty"` + + // capabilities is a list of values that describe capabilities to + // be enabled and used by OVS. In principle, the main usage is for + // enabling OVS hardware offload. + // +kubebuilder:validation:MaxItems:=10 + // +kubebuilder:validation:items:MaxLength:=64 + // +listType=set + // +optional + Capabilities []string `json:"capabilities,omitempty"` +} + // +kubebuilder:validation:XValidation:rule="has(self.portSecurity) && self.portSecurity == 'Disabled' ? !has(self.securityGroupRefs) : true",message="securityGroupRefs must be empty when portSecurity is set to Disabled" // +kubebuilder:validation:XValidation:rule="has(self.portSecurity) && self.portSecurity == 'Disabled' ? !has(self.allowedAddressPairs) : true",message="allowedAddressPairs must be empty when portSecurity is set to Disabled" type PortResourceSpec struct { @@ -137,6 +154,13 @@ type PortResourceSpec struct { // +optional AdminStateUp *bool `json:"adminStateUp,omitempty"` + // profile is a set of key-value pairs that enable the host to pass + // and receive information to the networking backend about the VIF port. + // We intentionally don't expose it as a map with free-form values + // to enforce Neutron supported values. + // +optional + Profile *BindingProfile `json:"profile,omitempty"` + // securityGroupRefs are the names of the security groups associated // with this port. // +kubebuilder:validation:MaxItems:=64 @@ -258,6 +282,13 @@ type PortResourceStatus struct { // +optional VNICType string `json:"vnicType,omitempty"` + // profile is a set of key-value pairs that enable the host to pass + // and receive information to the networking backend about the VIF port. + // We intentionally don't expose it as a map with free-form values + // to enforce Neutron supported values. + // +optional + Profile *BindingProfile `json:"profile,omitempty"` + // portSecurityEnabled indicates whether port security is enabled or not. // +optional PortSecurityEnabled *bool `json:"portSecurityEnabled,omitempty"` diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 093e63451..72ebd957d 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -110,6 +110,31 @@ func (in *AllowedAddressPairStatus) DeepCopy() *AllowedAddressPairStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BindingProfile) DeepCopyInto(out *BindingProfile) { + *out = *in + if in.TrustedVF != nil { + in, out := &in.TrustedVF, &out.TrustedVF + *out = new(bool) + **out = **in + } + if in.Capabilities != nil { + in, out := &in.Capabilities, &out.Capabilities + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BindingProfile. +func (in *BindingProfile) DeepCopy() *BindingProfile { + if in == nil { + return nil + } + out := new(BindingProfile) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *CloudCredentialsReference) DeepCopyInto(out *CloudCredentialsReference) { *out = *in @@ -2519,6 +2544,11 @@ func (in *PortResourceSpec) DeepCopyInto(out *PortResourceSpec) { *out = new(bool) **out = **in } + if in.Profile != nil { + in, out := &in.Profile, &out.Profile + *out = new(BindingProfile) + (*in).DeepCopyInto(*out) + } if in.SecurityGroupRefs != nil { in, out := &in.SecurityGroupRefs, &out.SecurityGroupRefs *out = make([]OpenStackName, len(*in)) @@ -2574,6 +2604,11 @@ func (in *PortResourceStatus) DeepCopyInto(out *PortResourceStatus) { *out = new(bool) **out = **in } + if in.Profile != nil { + in, out := &in.Profile, &out.Profile + *out = new(BindingProfile) + (*in).DeepCopyInto(*out) + } 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 8eab33c2d..355c482cb 100644 --- a/cmd/models-schema/zz_generated.openapi.go +++ b/cmd/models-schema/zz_generated.openapi.go @@ -35,6 +35,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllocationPoolStatus": schema_openstack_resource_controller_v2_api_v1alpha1_AllocationPoolStatus(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllowedAddressPair": schema_openstack_resource_controller_v2_api_v1alpha1_AllowedAddressPair(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllowedAddressPairStatus": schema_openstack_resource_controller_v2_api_v1alpha1_AllowedAddressPairStatus(ref), + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.BindingProfile": schema_openstack_resource_controller_v2_api_v1alpha1_BindingProfile(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.CloudCredentialsReference": schema_openstack_resource_controller_v2_api_v1alpha1_CloudCredentialsReference(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.Domain": schema_openstack_resource_controller_v2_api_v1alpha1_Domain(ref), "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.DomainFilter": schema_openstack_resource_controller_v2_api_v1alpha1_DomainFilter(ref), @@ -646,6 +647,45 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_AllowedAddressPairStat } } +func schema_openstack_resource_controller_v2_api_v1alpha1_BindingProfile(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "trustedVF": { + SchemaProps: spec.SchemaProps{ + Description: "trustedVF indicates whether the VF for the port will become trusted by physical function to perform some privileged operations.", + Type: []string{"boolean"}, + Format: "", + }, + }, + "capabilities": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "set", + }, + }, + SchemaProps: spec.SchemaProps{ + Description: "capabilities is a list of values that describe capabilities to be enabled and used by OVS. In principle, the main usage is for enabling OVS hardware offload.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + }, + }, + }, + }, + }, + } +} + func schema_openstack_resource_controller_v2_api_v1alpha1_CloudCredentialsReference(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -4854,6 +4894,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceSpec(ref c Format: "", }, }, + "profile": { + SchemaProps: spec.SchemaProps{ + Description: "profile is a set of key-value pairs that enable the host to pass and receive information to the networking backend about the VIF port. We intentionally don't expose it as a map with free-form values to enforce Neutron supported values.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.BindingProfile"), + }, + }, "securityGroupRefs": { VendorExtensible: spec.VendorExtensible{ Extensions: spec.Extensions{ @@ -4900,7 +4946,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.BindingProfile"}, } } @@ -5065,6 +5111,12 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceStatus(ref Format: "", }, }, + "profile": { + SchemaProps: spec.SchemaProps{ + Description: "profile is a set of key-value pairs that enable the host to pass and receive information to the networking backend about the VIF port. We intentionally don't expose it as a map with free-form values to enforce Neutron supported values.", + Ref: ref("github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.BindingProfile"), + }, + }, "portSecurityEnabled": { SchemaProps: spec.SchemaProps{ Description: "portSecurityEnabled indicates whether port security is enabled or not.", @@ -5095,7 +5147,7 @@ func schema_openstack_resource_controller_v2_api_v1alpha1_PortResourceStatus(ref }, }, Dependencies: []string{ - "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllowedAddressPairStatus", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FixedIPStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, + "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.AllowedAddressPairStatus", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.BindingProfile", "github.com/k-orc/openstack-resource-controller/v2/api/v1alpha1.FixedIPStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.Time"}, } } diff --git a/config/crd/bases/openstack.k-orc.cloud_ports.yaml b/config/crd/bases/openstack.k-orc.cloud_ports.yaml index 64c66bbf5..2349d51b3 100644 --- a/config/crd/bases/openstack.k-orc.cloud_ports.yaml +++ b/config/crd/bases/openstack.k-orc.cloud_ports.yaml @@ -325,6 +325,31 @@ spec: x-kubernetes-validations: - message: portSecurity cannot be changed to Inherit rule: '!(oldSelf != ''Inherit'' && self == ''Inherit'')' + profile: + description: |- + profile is a set of key-value pairs that enable the host to pass + and receive information to the networking backend about the VIF port. + We intentionally don't expose it as a map with free-form values + to enforce Neutron supported values. + properties: + capabilities: + description: |- + capabilities is a list of values that describe capabilities to + be enabled and used by OVS. In principle, the main usage is for + enabling OVS hardware offload. + items: + maxLength: 64 + type: string + maxItems: 10 + type: array + x-kubernetes-list-type: set + trustedVF: + description: |- + trustedVF indicates whether the VF for the port will become + trusted by physical function to perform some privileged + operations. + type: boolean + type: object projectRef: description: |- projectRef is a reference to the ORC Project this resource is associated with. @@ -570,6 +595,31 @@ spec: description: portSecurityEnabled indicates whether port security is enabled or not. type: boolean + profile: + description: |- + profile is a set of key-value pairs that enable the host to pass + and receive information to the networking backend about the VIF port. + We intentionally don't expose it as a map with free-form values + to enforce Neutron supported values. + properties: + capabilities: + description: |- + capabilities is a list of values that describe capabilities to + be enabled and used by OVS. In principle, the main usage is for + enabling OVS hardware offload. + items: + maxLength: 64 + type: string + maxItems: 10 + type: array + x-kubernetes-list-type: set + trustedVF: + description: |- + trustedVF indicates whether the VF for the port will become + trusted by physical function to perform some privileged + operations. + type: boolean + type: object projectID: description: projectID is the project owner of the resource. maxLength: 1024 diff --git a/internal/controllers/port/actuator.go b/internal/controllers/port/actuator.go index 570645623..385bb2fd4 100644 --- a/internal/controllers/port/actuator.go +++ b/internal/controllers/port/actuator.go @@ -254,6 +254,11 @@ func (actuator portActuator) CreateResource(ctx context.Context, obj *orcv1alpha VNICType: resource.VNICType, } + if resource.Profile != nil { + profile := bindingProfileToMap(resource.Profile) + portsBindingOpts.Profile = profile + } + portSecurityOpts := portsecurity.PortCreateOptsExt{ CreateOptsBuilder: portsBindingOpts, } @@ -502,6 +507,45 @@ func handlePortBindingUpdate(updateOpts ports.UpdateOptsBuilder, resource *resou } } } + + if resource.Profile != nil { + changed := false + + profile := bindingProfileToMap(resource.Profile) + trusted := resource.Profile.TrustedVF + capabilities := resource.Profile.Capabilities + oscCapabilities, _ := osResource.Profile["capabilities"].([]any) + + if trusted != nil { + if *trusted != osResource.Profile["trusted"] { + profile["trusted"] = *trusted + changed = true + } + } + + if capabilities != nil { + if oscCapabilities == nil || len(capabilities) != len(oscCapabilities) { + profile["capabilities"] = capabilities + changed = true + } else { + for i, e := range capabilities { + if e != oscCapabilities[i].(string) { + profile["capabilities"] = capabilities + changed = true + break + } + } + } + } + + if changed { + updateOpts = &portsbinding.UpdateOptsExt{ + UpdateOptsBuilder: updateOpts, + Profile: profile, + } + } + } + return updateOpts } @@ -539,6 +583,19 @@ func handleAdminStateUpUpdate(updateOpts *ports.UpdateOpts, resource *resourceSp } } +func bindingProfileToMap(bp *orcv1alpha1.BindingProfile) map[string]any { + profile := map[string]any{} + if bp.TrustedVF != nil { + profile["trusted"] = bp.TrustedVF + } + + if bp.Capabilities != nil { + profile["capabilities"] = bp.Capabilities + } + + return profile +} + type portHelperFactory struct{} var _ helperFactory = portHelperFactory{} diff --git a/internal/controllers/port/status.go b/internal/controllers/port/status.go index d740caac0..7e5646b1a 100644 --- a/internal/controllers/port/status.go +++ b/internal/controllers/port/status.go @@ -104,5 +104,23 @@ func (portStatusWriter) ApplyResourceStatus(log logr.Logger, osResource *osResou resourceStatus.WithFixedIPs(fixedIPs...) } + if osResource.Profile != nil { + profileStatus := orcapplyconfigv1alpha1.BindingProfile() + if trusted, ok := osResource.Profile["trusted"]; ok { + profileStatus.WithTrustedVF(trusted.(bool)) + } + + if capabilities, ok := osResource.Profile["capabilities"]; ok { + capStatus := make([]string, 0, len(capabilities.([]any))) + for _, e := range capabilities.([]any) { + capStatus = append(capStatus, e.(string)) + } + + profileStatus.WithCapabilities(capStatus...) + } + + resourceStatus.WithProfile(profileStatus) + } + 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 3f2ea0750..6b22963af 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,10 @@ status: vnicType: direct tags: - tag1 + profile: + trustedVF: false + capabilities: + - "switchdev" --- apiVersion: kuttl.dev/v1beta1 kind: TestAssert @@ -47,3 +51,4 @@ assertAll: - celExpr: "port.status.resource.fixedIPs[0].ip == '192.168.155.122'" - celExpr: "port.status.resource.securityGroups[0] == sg.status.id" - celExpr: "port.status.resource.projectID == project.status.id" + - celExpr: "port.status.resource.profile.capabilities.size() == 1" 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 31174591f..b662adaf2 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 @@ -84,3 +84,7 @@ spec: portSecurity: Enabled vnicType: direct projectRef: port-create-full + profile: + trustedVF: false + capabilities: + - "switchdev" 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 796726336..e822b8309 100644 --- a/internal/controllers/port/tests/port-update/01-updated-resource.yaml +++ b/internal/controllers/port/tests/port-update/01-updated-resource.yaml @@ -19,4 +19,8 @@ spec: tags: - tag1 vnicType: direct - portSecurity: Enabled \ No newline at end of file + portSecurity: Enabled + profile: + trustedVF: False + capabilities: + - switchdev diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/bindingprofile.go b/pkg/clients/applyconfiguration/api/v1alpha1/bindingprofile.go new file mode 100644 index 000000000..d9cc9a162 --- /dev/null +++ b/pkg/clients/applyconfiguration/api/v1alpha1/bindingprofile.go @@ -0,0 +1,50 @@ +/* +Copyright 2025 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 + +// BindingProfileApplyConfiguration represents a declarative configuration of the BindingProfile type for use +// with apply. +type BindingProfileApplyConfiguration struct { + TrustedVF *bool `json:"trustedVF,omitempty"` + Capabilities []string `json:"capabilities,omitempty"` +} + +// BindingProfileApplyConfiguration constructs a declarative configuration of the BindingProfile type for use with +// apply. +func BindingProfile() *BindingProfileApplyConfiguration { + return &BindingProfileApplyConfiguration{} +} + +// WithTrustedVF sets the TrustedVF 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 TrustedVF field is set to the value of the last call. +func (b *BindingProfileApplyConfiguration) WithTrustedVF(value bool) *BindingProfileApplyConfiguration { + b.TrustedVF = &value + return b +} + +// WithCapabilities adds the given value to the Capabilities 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 Capabilities field. +func (b *BindingProfileApplyConfiguration) WithCapabilities(values ...string) *BindingProfileApplyConfiguration { + for i := range values { + b.Capabilities = append(b.Capabilities, values[i]) + } + return b +} diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go index 67351b05c..911ac6cc5 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcespec.go @@ -32,6 +32,7 @@ type PortResourceSpecApplyConfiguration struct { AllowedAddressPairs []AllowedAddressPairApplyConfiguration `json:"allowedAddressPairs,omitempty"` Addresses []AddressApplyConfiguration `json:"addresses,omitempty"` AdminStateUp *bool `json:"adminStateUp,omitempty"` + Profile *BindingProfileApplyConfiguration `json:"profile,omitempty"` SecurityGroupRefs []apiv1alpha1.OpenStackName `json:"securityGroupRefs,omitempty"` VNICType *string `json:"vnicType,omitempty"` PortSecurity *apiv1alpha1.PortSecurityState `json:"portSecurity,omitempty"` @@ -112,6 +113,14 @@ func (b *PortResourceSpecApplyConfiguration) WithAdminStateUp(value bool) *PortR return b } +// WithProfile sets the Profile 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 Profile field is set to the value of the last call. +func (b *PortResourceSpecApplyConfiguration) WithProfile(value *BindingProfileApplyConfiguration) *PortResourceSpecApplyConfiguration { + b.Profile = value + return b +} + // WithSecurityGroupRefs adds the given value to the SecurityGroupRefs 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 SecurityGroupRefs field. diff --git a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcestatus.go b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcestatus.go index 1fd734822..275f86e30 100644 --- a/pkg/clients/applyconfiguration/api/v1alpha1/portresourcestatus.go +++ b/pkg/clients/applyconfiguration/api/v1alpha1/portresourcestatus.go @@ -40,6 +40,7 @@ type PortResourceStatusApplyConfiguration struct { SecurityGroups []string `json:"securityGroups,omitempty"` PropagateUplinkStatus *bool `json:"propagateUplinkStatus,omitempty"` VNICType *string `json:"vnicType,omitempty"` + Profile *BindingProfileApplyConfiguration `json:"profile,omitempty"` PortSecurityEnabled *bool `json:"portSecurityEnabled,omitempty"` NeutronStatusMetadataApplyConfiguration `json:",inline"` } @@ -184,6 +185,14 @@ func (b *PortResourceStatusApplyConfiguration) WithVNICType(value string) *PortR return b } +// WithProfile sets the Profile 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 Profile field is set to the value of the last call. +func (b *PortResourceStatusApplyConfiguration) WithProfile(value *BindingProfileApplyConfiguration) *PortResourceStatusApplyConfiguration { + b.Profile = value + return b +} + // WithPortSecurityEnabled sets the PortSecurityEnabled 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 PortSecurityEnabled field is set to the value of the last call. diff --git a/pkg/clients/applyconfiguration/internal/internal.go b/pkg/clients/applyconfiguration/internal/internal.go index 5b5cb5142..c0d989057 100644 --- a/pkg/clients/applyconfiguration/internal/internal.go +++ b/pkg/clients/applyconfiguration/internal/internal.go @@ -84,6 +84,18 @@ var schemaYAML = typed.YAMLObject(`types: - name: mac type: scalar: string +- name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.BindingProfile + map: + fields: + - name: capabilities + type: + list: + elementType: + scalar: string + elementRelationship: associative + - name: trustedVF + type: + scalar: boolean - name: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.CloudCredentialsReference map: fields: @@ -1339,6 +1351,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: portSecurity type: scalar: string + - name: profile + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.BindingProfile - name: projectRef type: scalar: string @@ -1399,6 +1414,9 @@ var schemaYAML = typed.YAMLObject(`types: - name: portSecurityEnabled type: scalar: boolean + - name: profile + type: + namedType: com.github.k-orc.openstack-resource-controller.v2.api.v1alpha1.BindingProfile - name: projectID type: scalar: string diff --git a/pkg/clients/applyconfiguration/utils.go b/pkg/clients/applyconfiguration/utils.go index e3166fefe..ca33621f7 100644 --- a/pkg/clients/applyconfiguration/utils.go +++ b/pkg/clients/applyconfiguration/utils.go @@ -42,6 +42,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &apiv1alpha1.AllowedAddressPairApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("AllowedAddressPairStatus"): return &apiv1alpha1.AllowedAddressPairStatusApplyConfiguration{} + case v1alpha1.SchemeGroupVersion.WithKind("BindingProfile"): + return &apiv1alpha1.BindingProfileApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("CloudCredentialsReference"): return &apiv1alpha1.CloudCredentialsReferenceApplyConfiguration{} case v1alpha1.SchemeGroupVersion.WithKind("Domain"): diff --git a/website/docs/crd-reference.md b/website/docs/crd-reference.md index 23b3b2403..5b6dfa52a 100644 --- a/website/docs/crd-reference.md +++ b/website/docs/crd-reference.md @@ -133,6 +133,24 @@ _Appears in:_ +#### BindingProfile + + + + + + + +_Appears in:_ +- [PortResourceSpec](#portresourcespec) +- [PortResourceStatus](#portresourcestatus) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `trustedVF` _boolean_ | trustedVF indicates whether the VF for the port will become
trusted by physical function to perform some privileged
operations. | | | +| `capabilities` _string array_ | capabilities is a list of values that describe capabilities to
be enabled and used by OVS. In principle, the main usage is for
enabling OVS hardware offload. | | MaxItems: 10
items:MaxLength: 64
| + + #### CIDR _Underlying type:_ _string_ @@ -2165,6 +2183,7 @@ _Appears in:_ | `allowedAddressPairs` _[AllowedAddressPair](#allowedaddresspair) array_ | allowedAddressPairs are allowed addresses associated with this port. | | MaxItems: 128
| | `addresses` _[Address](#address) array_ | addresses are the IP addresses for the port. | | MaxItems: 128
| | `adminStateUp` _boolean_ | adminStateUp is the administrative state of the port,
which is up (true) or down (false). The default value is true. | true | | +| `profile` _[BindingProfile](#bindingprofile)_ | profile is a set of key-value pairs that enable the host to pass
and receive information to the networking backend about the VIF port.
We intentionally don't expose it as a map with free-form values
to enforce Neutron supported values. | | | | `securityGroupRefs` _[OpenStackName](#openstackname) array_ | securityGroupRefs are the names of the security groups associated
with this port. | | MaxItems: 64
MaxLength: 255
MinLength: 1
Pattern: `^[^,]+$`
| | `vnicType` _string_ | vnicType specifies the type of vNIC which this port should be
attached to. This is used to determine which mechanism driver(s) to
be used to bind the port. The valid values are normal, macvtap,
direct, baremetal, direct-physical, virtio-forwarder, smart-nic and
remote-managed, although these values will not be validated in this
API to ensure compatibility with future neutron changes or custom
implementations. What type of vNIC is actually available depends on
deployments. If not specified, the Neutron default value is used. | | MaxLength: 64
| | `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]
| @@ -2199,6 +2218,7 @@ _Appears in:_ | `securityGroups` _string array_ | securityGroups contains the IDs of security groups applied to the port. | | MaxItems: 64
items:MaxLength: 1024
| | `propagateUplinkStatus` _boolean_ | propagateUplinkStatus represents the uplink status propagation of
the port. | | | | `vnicType` _string_ | vnicType is the type of vNIC which this port is attached to. | | MaxLength: 64
| +| `profile` _[BindingProfile](#bindingprofile)_ | profile is a set of key-value pairs that enable the host to pass
and receive information to the networking backend about the VIF port.
We intentionally don't expose it as a map with free-form values
to enforce Neutron supported values. | | | | `portSecurityEnabled` _boolean_ | portSecurityEnabled indicates whether port security is enabled or not. | | | | `createdAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | createdAt shows the date and time when the resource was created. The date and time stamp format is ISO 8601 | | | | `updatedAt` _[Time](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#time-v1-meta)_ | updatedAt shows the date and time when the resource was updated. The date and time stamp format is ISO 8601 | | | From 545b0132ef7409dee23df3cd22487efcaf8003a6 Mon Sep 17 00:00:00 2001 From: Winicius Silva Date: Tue, 30 Dec 2025 12:23:28 -0300 Subject: [PATCH 2/2] CI: add additional neutron policy To test properly the binding:profile field, we need to override the default policies to allow stack user to create and update it. The warning section here[1] on docs discuss better. [1] https://docs.openstack.org/api-ref/network/v2/index.html#port-binding-extended-attributes Signed-off-by: Winicius Silva --- .github/workflows/e2e.yaml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.github/workflows/e2e.yaml b/.github/workflows/e2e.yaml index da07b7eee..9b25434a2 100644 --- a/.github/workflows/e2e.yaml +++ b/.github/workflows/e2e.yaml @@ -31,12 +31,25 @@ jobs: steps: - uses: actions/checkout@v6.0.1 + - name: Define additional Neutron policies + run: | + mkdir /tmp/neutron-policies + cat << EOF >> /tmp/neutron-policies/port-binding.yaml + --- + "create_port:binding:profile": "rule:member or rule:admin or rule:service_api" + "update_port:binding:profile": "rule:member or rule:admin or rule:service_api" + EOF + - name: Deploy devstack uses: gophercloud/devstack-action@60ca1042045c0c9e3e001c64575d381654ffcba1 with: enable_workaround_docker_io: 'false' branch: ${{ matrix.openstack_version }} enabled_services: "openstack-cli-server" + conf_overrides: | + [[post-config|\$NEUTRON_CONF]] + [oslo-policy] + policy_dirs = /tmp/neutron-policies - name: Deploy a Kind Cluster uses: helm/kind-action@92086f6be054225fa813e0a4b13787fc9088faab