[Bug 2085409] Re: [SRU] Heat wrongly passing "no_fixed_ips" to Neutron

James Page 2085409 at bugs.launchpad.net
Tue Dec 17 12:55:11 UTC 2024


I've reviewed and uploaded updates for all impacted series based on the
debdiff's provided above.

I should be able to push the fix into devel today (an eventlet issue was
blocking which is now worked-around).

Thanks Rodrigo

-- 
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:
  [SRU] Heat wrongly passing "no_fixed_ips" to Neutron

Status in Ubuntu Cloud Archive:
  New
Status in Ubuntu Cloud Archive antelope series:
  New
Status in Ubuntu Cloud Archive bobcat series:
  New
Status in Ubuntu Cloud Archive caracal series:
  New
Status in Ubuntu Cloud Archive dalmation series:
  New
Status in Ubuntu Cloud Archive epoxy series:
  New
Status in heat package in Ubuntu:
  Confirmed
Status in heat source package in Jammy:
  New
Status in heat source package in Noble:
  New
Status in heat source package in Oracular:
  New
Status in heat source package in Plucky:
  Confirmed

Bug description:
  ====== SRU TEMPLATE AT THE BOTTOM ======

  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)

  
  ============
  SRU TEMPLATE
  ============

  ===============
  SRU DESCRIPTION
  ===============

  [Impact]

  A mishandling of port parameters in the Nova class when creating
  instances is causing the issue where the user cannot specify a network
  + subnet + no specific IP combination to create a VM. Typically users
  would specify only the network when creating VMs where the specific IP
  or port doesn't matter, but if the network has multiple subnets, then
  the user has to specify the desired subnet, triggering the issue
  because the current code is incorrectly expecting an IP to be also
  specified in such case, not considering the scenario where the IP
  wouldn't be specified:

  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]})

  So if a subnet is specified, the logic is coded to use a fixed ip, not
  allowing the no_fixed_ips scenario. The fix overrides the logic to not
  use a fixed_ip in such case:

  if net_data.get(neutron_port.Port.NO_FIXED_IPS):
      kwargs.update({'fixed_ips': []})

  [Test case]

  1) Deploy OpenStack with Heat

  2) Copy the following template adjusting the image, flavor, network
  and subnet IDs accordingly:

  heat_template_version: wallaby
   
  resources:
    server_test:
      type: OS::Nova::Server
      properties:
        name: server_test
        flavor: 6
        image: "cirros-0.4.0"
        networks:
          - network: cdac5ad3-3084-4885-85a6-b1c547cd3136
            port_extra_properties:
              port_security_enabled: false
            subnet: 68150f54-4548-4d65-a3ae-1714d0156f18

  3) deploy the template using:

  openstack stack create -t template.yaml stack1

  4) Check the status using "openstack stack list". Stack creation
  should fail, and upon running "openstack stack show" you should see
  the following error message (except for a different req-ID):

  | stack_status_reason   | Resource CREATE failed: BadRequest: resources.server_test: Unrecognized attribute(s) 'no_fixed_ips'                       |
  |                       | Neutron server returns request_ids: ['req-b3a2a896-2c0f-47f3-8fe5-68b15d7e6824']         

  
  5) Install fixed package

  6) Retry step (4) and it should succeed creating the stack

  [Regression Potential]

  The code change is affecting the path of creating all VMs. This
  default path is being tested upstream and it passed CI. The code
  modifications introduces behavioral changes on the specific
  situations, specifically when specifying a subnet. This code path is
  being tested in the SRU. Given the current coverage, we can assume any
  potential regression has its scope limited to the affected scenario
  that causes the bug, or to the scenario where a subnet is specified
  along with an IP/port. However, the code logic seems to be mutually
  exclusive at [0], indicating that the parameters that lead to one
  scenario shouldn't cause influence the behavior of the other,
  therefore we can further assume that the scope of the change is
  further limited to only this scenario.

  [0]
  https://github.com/openstack/heat/blob/dbc298b4ccc8f776791745496ff303387ad88f24/heat/engine/resources/openstack/neutron/port.py#L457

  [Other Info]

  None.

To manage notifications about this bug go to:
https://bugs.launchpad.net/cloud-archive/+bug/2085409/+subscriptions




More information about the Ubuntu-openstack-bugs mailing list