[Bug 2085409] [NEW] Heat wrongly passing "no_fixed_ips" to Neutron

Alejandro Santoyo Gonzalez 2085409 at bugs.launchpad.net
Wed Oct 23 07:19:15 UTC 2024


Public bug reported:

Summary:
--------

When creating an instance via the "OS::Nova::Server" resource type in a Heat template, specifying a network and a subnet to get a port created on the instance, it fails as below:
    

2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource     nic_info['port-id'] = self._create_internal_port(
2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource   File "/usr/lib/python3/dist-packages/heat/engine/resources/openstack/nova/server_network_mixin.py", line 103, in _create_internal_port
2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource     port = self.client('neutron').create_port({'port': kwargs})['port']
2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource   File "/usr/lib/python3/dist-packages/neutronclient/v2_0/client.py", line 822, in create_port
2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource     return self.post(self.ports_path, body=body)
2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource   File "/usr/lib/python3/dist-packages/neutronclient/v2_0/client.py", line 361, in post
2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource     return self.do_request("POST", action, body=body,
2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource   File "/usr/lib/python3/dist-packages/neutronclient/v2_0/client.py", line 297, in do_request
2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource     self._handle_fault_response(status_code, replybody, resp)
2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource   File "/usr/lib/python3/dist-packages/neutronclient/v2_0/client.py", line 272, in _handle_fault_response
2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource     exception_handler_v20(status_code, error_body)
2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource   File "/usr/lib/python3/dist-packages/neutronclient/v2_0/client.py", line 90, in exception_handler_v20
2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource     raise client_exc(message=error_message,
2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource neutronclient.common.exceptions.BadRequest: Unrecognized attribute(s) 'no_fixed_ips'
2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource Neutron server returns request_ids: ['req-a5b98318-dd6d-468e-ac84-cd904060dd14']
2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource
2024-10-21 15:11:27.933 5089 INFO heat.engine.stack [req-e5c2d5d4-973c-4a69-bba5-3de9f62fc685 - - - - -] Stack CREATE FAILED (test-tests-zsdvasfbha-1-pfwwntfb5x5g-nodes-p4nciwpibd2g-0-l2t3ak6lgqsc): Resource CREATE failed: BadRequest: resources.host: Unrecognized attribute(s) 'no_fixed_ips'


The reason why this fails is because for ports without fixed IPs, Neutron expects "fixed_ips = []" as it does not understand "no_fixed_ips", but when a subnet is specified, _create_internal_port() is called to do the API request to Neutron which internally calls _prepare_internal_port_kwargs().
Now, _prepare_internal_port_kwargs() builds the arguments to be passed to the request, including the extra port properties among which no_fixed_ips is included.
 
First extra_props is checked and in our case is not None. Then port_extra_keys gets populated with neutron_port.Port.EXTRA_PROPERTIES which includes no_fixed_ips and that is never removed:

from heat/engine/resources/openstack/neutron/port.py:
...
    EXTRA_PROPERTIES = (
        VALUE_SPECS, ADMIN_STATE_UP, MAC_ADDRESS,
        ALLOWED_ADDRESS_PAIRS, VNIC_TYPE, QOS_POLICY,
        PORT_SECURITY_ENABLED, PROPAGATE_UPLINK_STATUS, NO_FIXED_IPS,
    ) = (
        'value_specs', 'admin_state_up', 'mac_address',
        'allowed_address_pairs', 'binding:vnic_type', 'qos_policy',
        'port_security_enabled', 'propagate_uplink_status', 'no_fixed_ips',
    )
...
 
from heat/engine/resources/openstack/nova/server_network_mixin.py:
...
    def _prepare_internal_port_kwargs(self, net_data, security_groups=None):
        kwargs = {'network_id': self._get_network_id(net_data)}
        fixed_ip = net_data.get(self.NETWORK_FIXED_IP)
        subnet = net_data.get(self.NETWORK_SUBNET)
        body = {}
        if fixed_ip:
            body['ip_address'] = fixed_ip
        if subnet:
            body['subnet_id'] = subnet
        # we should add fixed_ips only if subnet or ip were provided
        if body:
            kwargs.update({'fixed_ips': [body]})
 
        if security_groups:
            sec_uuids = self.client_plugin(
                'neutron').get_secgroup_uuids(security_groups)
            kwargs['security_groups'] = sec_uuids
 
        extra_props = net_data.get(self.NETWORK_PORT_EXTRA)
        if extra_props is not None:
            specs = extra_props.pop(neutron_port.Port.VALUE_SPECS)
            if specs:
                kwargs.update(specs)
            port_extra_keys = list(neutron_port.Port.EXTRA_PROPERTIES)
            port_extra_keys.remove(neutron_port.Port.ALLOWED_ADDRESS_PAIRS)
            for key in port_extra_keys:
                if extra_props.get(key) is not None:
                    kwargs[key] = extra_props.get(key)
 
            allowed_address_pairs = extra_props.get(
                neutron_port.Port.ALLOWED_ADDRESS_PAIRS)
            if allowed_address_pairs is not None:
                for pair in allowed_address_pairs:
                    if (neutron_port.Port.ALLOWED_ADDRESS_PAIR_MAC_ADDRESS
                        in pair and pair.get(
                            neutron_port.Port.ALLOWED_ADDRESS_PAIR_MAC_ADDRESS)
                            is None):
                        del pair[
                            neutron_port.Port.ALLOWED_ADDRESS_PAIR_MAC_ADDRESS]
                port_address_pairs = neutron_port.Port.ALLOWED_ADDRESS_PAIRS
                kwargs[port_address_pairs] = allowed_address_pairs
```

Again, the reason why this fails is because for ports without fixed IPs,
Neutron expects "fixed_ips = []" as it does not understand
"no_fixed_ips", so Heat should not be passing that. Commit [1] where the
"no_fixed_ips" feature was introduced in Heat, added logic to avoid
passing "no_fixed_ips" to Neutron, but that logic was never added for
the case where the port is requested to be created via
"OS::Nova::Server" (and there could be other edge cases missing this
logic too)

[1]
https://opendev.org/openstack/heat/commit/9292264aa74d6d9e6e8f58045c7e3faf755ea725

Reproducer:
-----------

Here's a template reproducing the issue:

heat_template_version: wallaby
 
resources:
 
  server_test:
    type: OS::Nova::Server
    properties:
      name: server_test
      config_drive: true
      flavor: m1.small
      image: "focal-raw"
      networks:
        - network: 0924ec50-c1b7-4ea9-bdd2-334772fd3398
          port_extra_properties:
            port_security_enabled: false
          subnet: cb49c91d-62d0-4677-b121-0dcf0476ecf0

Openstack versions affected: 
---------------------------
>= Wallaby


Potential diff for a fix:
-------------------------

index 794406457..0e27e33b8 100644
--- a/heat/engine/resources/openstack/nova/server_network_mixin.py
+++ b/heat/engine/resources/openstack/nova/server_network_mixin.py
@@ -120,6 +120,8 @@ class ServerNetworkMixin(object):
         # we should add fixed_ips only if subnet or ip were provided
         if body:
             kwargs.update({'fixed_ips': [body]})
+        if net_data.get(neutron_port.Port.NO_FIXED_IPS):
+            kwargs.update({'fixed_ips': []})
 
         if security_groups:
             sec_uuids = self.client_plugin(
@@ -133,6 +135,7 @@ class ServerNetworkMixin(object):
                 kwargs.update(specs)
             port_extra_keys = list(neutron_port.Port.EXTRA_PROPERTIES)
             port_extra_keys.remove(neutron_port.Port.ALLOWED_ADDRESS_PAIRS)
+            port_extra_keys.remove(neutron_port.Port.NO_FIXED_IPS)
             for key in port_extra_keys:
                 if extra_props.get(key) is not None:
                     kwargs[key] = extra_props.get(key)

** Affects: heat (Ubuntu)
     Importance: Undecided
         Status: New

-- 
You received this bug notification because you are a member of Ubuntu
OpenStack, which is subscribed to heat in Ubuntu.
https://bugs.launchpad.net/bugs/2085409

Title:
  Heat wrongly passing "no_fixed_ips" to Neutron

Status in heat package in Ubuntu:
  New

Bug description:
  Summary:
  --------

  When creating an instance via the "OS::Nova::Server" resource type in a Heat template, specifying a network and a subnet to get a port created on the instance, it fails as below:
      

  2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource     nic_info['port-id'] = self._create_internal_port(
  2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource   File "/usr/lib/python3/dist-packages/heat/engine/resources/openstack/nova/server_network_mixin.py", line 103, in _create_internal_port
  2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource     port = self.client('neutron').create_port({'port': kwargs})['port']
  2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource   File "/usr/lib/python3/dist-packages/neutronclient/v2_0/client.py", line 822, in create_port
  2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource     return self.post(self.ports_path, body=body)
  2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource   File "/usr/lib/python3/dist-packages/neutronclient/v2_0/client.py", line 361, in post
  2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource     return self.do_request("POST", action, body=body,
  2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource   File "/usr/lib/python3/dist-packages/neutronclient/v2_0/client.py", line 297, in do_request
  2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource     self._handle_fault_response(status_code, replybody, resp)
  2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource   File "/usr/lib/python3/dist-packages/neutronclient/v2_0/client.py", line 272, in _handle_fault_response
  2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource     exception_handler_v20(status_code, error_body)
  2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource   File "/usr/lib/python3/dist-packages/neutronclient/v2_0/client.py", line 90, in exception_handler_v20
  2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource     raise client_exc(message=error_message,
  2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource neutronclient.common.exceptions.BadRequest: Unrecognized attribute(s) 'no_fixed_ips'
  2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource Neutron server returns request_ids: ['req-a5b98318-dd6d-468e-ac84-cd904060dd14']
  2024-10-21 15:11:27.924 5089 ERROR heat.engine.resource
  2024-10-21 15:11:27.933 5089 INFO heat.engine.stack [req-e5c2d5d4-973c-4a69-bba5-3de9f62fc685 - - - - -] Stack CREATE FAILED (test-tests-zsdvasfbha-1-pfwwntfb5x5g-nodes-p4nciwpibd2g-0-l2t3ak6lgqsc): Resource CREATE failed: BadRequest: resources.host: Unrecognized attribute(s) 'no_fixed_ips'


  The reason why this fails is because for ports without fixed IPs, Neutron expects "fixed_ips = []" as it does not understand "no_fixed_ips", but when a subnet is specified, _create_internal_port() is called to do the API request to Neutron which internally calls _prepare_internal_port_kwargs().
  Now, _prepare_internal_port_kwargs() builds the arguments to be passed to the request, including the extra port properties among which no_fixed_ips is included.
   
  First extra_props is checked and in our case is not None. Then port_extra_keys gets populated with neutron_port.Port.EXTRA_PROPERTIES which includes no_fixed_ips and that is never removed:

  from heat/engine/resources/openstack/neutron/port.py:
  ...
      EXTRA_PROPERTIES = (
          VALUE_SPECS, ADMIN_STATE_UP, MAC_ADDRESS,
          ALLOWED_ADDRESS_PAIRS, VNIC_TYPE, QOS_POLICY,
          PORT_SECURITY_ENABLED, PROPAGATE_UPLINK_STATUS, NO_FIXED_IPS,
      ) = (
          'value_specs', 'admin_state_up', 'mac_address',
          'allowed_address_pairs', 'binding:vnic_type', 'qos_policy',
          'port_security_enabled', 'propagate_uplink_status', 'no_fixed_ips',
      )
  ...
   
  from heat/engine/resources/openstack/nova/server_network_mixin.py:
  ...
      def _prepare_internal_port_kwargs(self, net_data, security_groups=None):
          kwargs = {'network_id': self._get_network_id(net_data)}
          fixed_ip = net_data.get(self.NETWORK_FIXED_IP)
          subnet = net_data.get(self.NETWORK_SUBNET)
          body = {}
          if fixed_ip:
              body['ip_address'] = fixed_ip
          if subnet:
              body['subnet_id'] = subnet
          # we should add fixed_ips only if subnet or ip were provided
          if body:
              kwargs.update({'fixed_ips': [body]})
   
          if security_groups:
              sec_uuids = self.client_plugin(
                  'neutron').get_secgroup_uuids(security_groups)
              kwargs['security_groups'] = sec_uuids
   
          extra_props = net_data.get(self.NETWORK_PORT_EXTRA)
          if extra_props is not None:
              specs = extra_props.pop(neutron_port.Port.VALUE_SPECS)
              if specs:
                  kwargs.update(specs)
              port_extra_keys = list(neutron_port.Port.EXTRA_PROPERTIES)
              port_extra_keys.remove(neutron_port.Port.ALLOWED_ADDRESS_PAIRS)
              for key in port_extra_keys:
                  if extra_props.get(key) is not None:
                      kwargs[key] = extra_props.get(key)
   
              allowed_address_pairs = extra_props.get(
                  neutron_port.Port.ALLOWED_ADDRESS_PAIRS)
              if allowed_address_pairs is not None:
                  for pair in allowed_address_pairs:
                      if (neutron_port.Port.ALLOWED_ADDRESS_PAIR_MAC_ADDRESS
                          in pair and pair.get(
                              neutron_port.Port.ALLOWED_ADDRESS_PAIR_MAC_ADDRESS)
                              is None):
                          del pair[
                              neutron_port.Port.ALLOWED_ADDRESS_PAIR_MAC_ADDRESS]
                  port_address_pairs = neutron_port.Port.ALLOWED_ADDRESS_PAIRS
                  kwargs[port_address_pairs] = allowed_address_pairs
  ```

  Again, the reason why this fails is because for ports without fixed
  IPs, Neutron expects "fixed_ips = []" as it does not understand
  "no_fixed_ips", so Heat should not be passing that. Commit [1] where
  the "no_fixed_ips" feature was introduced in Heat, added logic to
  avoid passing "no_fixed_ips" to Neutron, but that logic was never
  added for the case where the port is requested to be created via
  "OS::Nova::Server" (and there could be other edge cases missing this
  logic too)

  [1]
  https://opendev.org/openstack/heat/commit/9292264aa74d6d9e6e8f58045c7e3faf755ea725

  Reproducer:
  -----------

  Here's a template reproducing the issue:

  heat_template_version: wallaby
   
  resources:
   
    server_test:
      type: OS::Nova::Server
      properties:
        name: server_test
        config_drive: true
        flavor: m1.small
        image: "focal-raw"
        networks:
          - network: 0924ec50-c1b7-4ea9-bdd2-334772fd3398
            port_extra_properties:
              port_security_enabled: false
            subnet: cb49c91d-62d0-4677-b121-0dcf0476ecf0

  Openstack versions affected: 
  ---------------------------
  >= Wallaby

  
  Potential diff for a fix:
  -------------------------

  index 794406457..0e27e33b8 100644
  --- a/heat/engine/resources/openstack/nova/server_network_mixin.py
  +++ b/heat/engine/resources/openstack/nova/server_network_mixin.py
  @@ -120,6 +120,8 @@ class ServerNetworkMixin(object):
           # we should add fixed_ips only if subnet or ip were provided
           if body:
               kwargs.update({'fixed_ips': [body]})
  +        if net_data.get(neutron_port.Port.NO_FIXED_IPS):
  +            kwargs.update({'fixed_ips': []})
   
           if security_groups:
               sec_uuids = self.client_plugin(
  @@ -133,6 +135,7 @@ class ServerNetworkMixin(object):
                   kwargs.update(specs)
               port_extra_keys = list(neutron_port.Port.EXTRA_PROPERTIES)
               port_extra_keys.remove(neutron_port.Port.ALLOWED_ADDRESS_PAIRS)
  +            port_extra_keys.remove(neutron_port.Port.NO_FIXED_IPS)
               for key in port_extra_keys:
                   if extra_props.get(key) is not None:
                       kwargs[key] = extra_props.get(key)

To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/heat/+bug/2085409/+subscriptions




More information about the Ubuntu-openstack-bugs mailing list