@@ -27,6 +27,7 @@ import (
2727 "github.com/gophercloud/gophercloud/v2"
2828 "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portsbinding"
2929 "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portsecurity"
30+ "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/extensions/portstrustedvif"
3031 "github.com/gophercloud/gophercloud/v2/openstack/networking/v2/ports"
3132 "k8s.io/apimachinery/pkg/runtime"
3233 "k8s.io/apimachinery/pkg/util/wait"
@@ -240,14 +241,33 @@ func (s *Service) EnsurePort(eventObject runtime.Object, portSpec *infrav1.Resol
240241 builder = portSecurityOpts
241242 }
242243
244+ // Determine if port_trusted_vif extension is available when TrustedVF is requested.
245+ // If available, we use the dedicated port attribute instead of binding:profile.
246+ var usePortTrustedVIF bool
247+ if portSpec .Profile != nil && ptr .Deref (portSpec .Profile .TrustedVF , false ) {
248+ usePortTrustedVIF , err = s .HasPortTrustedVIFExtension ()
249+ if err != nil {
250+ return nil , err
251+ }
252+ }
253+
243254 portsBindingOpts := portsbinding.CreateOptsExt {
244255 CreateOptsBuilder : builder ,
245256 HostID : ptr .Deref (portSpec .HostID , "" ),
246257 VNICType : ptr .Deref (portSpec .VNICType , "" ),
247- Profile : getPortProfile (portSpec .Profile ),
258+ Profile : getPortProfile (portSpec .Profile , usePortTrustedVIF ),
248259 }
249260 builder = portsBindingOpts
250261
262+ // If the port_trusted_vif extension is available, set trusted mode via the
263+ // dedicated port attribute rather than through binding:profile.
264+ if usePortTrustedVIF {
265+ builder = portstrustedvif.PortCreateOptsExt {
266+ CreateOptsBuilder : builder ,
267+ PortTrustedVIF : portSpec .Profile .TrustedVF ,
268+ }
269+ }
270+
251271 port , err := s .client .CreatePort (builder )
252272 if err != nil {
253273 record .Warnf (eventObject , "FailedCreatePort" , "Failed to create port %s: %v" , portSpec .Name , err )
@@ -262,7 +282,7 @@ func (s *Service) EnsurePort(eventObject runtime.Object, portSpec *infrav1.Resol
262282 return port , nil
263283}
264284
265- func getPortProfile (p * infrav1.BindingProfile ) map [string ]interface {} {
285+ func getPortProfile (p * infrav1.BindingProfile , usePortTrustedVIF bool ) map [string ]interface {} {
266286 if p == nil {
267287 return nil
268288 }
@@ -274,7 +294,10 @@ func getPortProfile(p *infrav1.BindingProfile) map[string]interface{} {
274294 if ptr .Deref (p .OVSHWOffload , false ) {
275295 portProfile ["capabilities" ] = []string {"switchdev" }
276296 }
277- if ptr .Deref (p .TrustedVF , false ) {
297+ // Only set trusted in binding:profile if the port_trusted_vif extension
298+ // is not available. When the extension is available, trusted mode is set
299+ // via the dedicated port attribute instead.
300+ if ! usePortTrustedVIF && ptr .Deref (p .TrustedVF , false ) {
278301 portProfile ["trusted" ] = true
279302 }
280303
@@ -607,6 +630,24 @@ func (s *Service) IsTrunkExtSupported() (trunknSupported bool, err error) {
607630 return true , nil
608631}
609632
633+ // HasPortTrustedVIFExtension checks whether the Neutron port_trusted_vif
634+ // extension is available. When this extension is present, trusted VF mode
635+ // should be set via the dedicated port attribute rather than through
636+ // binding:profile.
637+ func (s * Service ) HasPortTrustedVIFExtension () (bool , error ) {
638+ allExts , err := s .client .ListExtensions ()
639+ if err != nil {
640+ return false , err
641+ }
642+
643+ for _ , ext := range allExts {
644+ if ext .Alias == "port-trusted-vif" {
645+ return true , nil
646+ }
647+ }
648+ return false , nil
649+ }
650+
610651// AdoptPortsServer looks for ports in desiredPorts which were previously created, and adds them to resources.Ports.
611652// A port matches if it has the same name and network ID as the desired port.
612653// TODO(emilien): remove this function: https://github.com/kubernetes-sigs/cluster-api-provider-openstack/pull/2071
0 commit comments