[Bug 2114984] Re: Incorrect handling of value_specs upon port update via stack update

Alejandro Santoyo Gonzalez 2114984 at bugs.launchpad.net
Fri Jun 20 09:17:43 UTC 2025


** Description changed:

  When a port's value_specs are updated via a stack update action, the
  current value_specs content is wiped and replaced with the new values.
  This causes issues in various use cases. For example:
  
  - An SRIOV port has 'binding:profile' data such as {pci_slot='0000:17:02.1', pci_vendor_info='8086:1889', pf_mac_address='b4:96:91:f7:f1:d6'...}
  - A stack update is triggered with the below template:
  
  port2:
  type: OS::Neutron::Port
  properties:
  network: { get_param: network2 }
  binding:vnic_type: direct
  value_specs: {"binding:profile": {"trusted": "on"}}
  
  - Given that value_specs is updatable as per the schema definition, one would expect the above update to return a port with {pci_slot='0000:17:02.1', pci_vendor_info='8086:1889', pf_mac_address='b4:96:91:f7:f1:d6'..., "trusted": "on"}
  - The issue is that the above update returns a port with binding:profile reduced to "binding:profile": {"trusted": "on"}
  
- I guess one could argue that you need to pass the current value_specs +
- new key/value(s) in the template, but I'd argue that is not the best
- approach since values such as pf_mac_address change per port making it
- difficult to use more generic templates for complex deployments.
- 
- Creating a new stack, defining this parameter doesn't cause the issue,
- but this is expected as the remaining 'binding:profile' properties are
- added during the creation process. However, setting the property via
- "openstack port set --binding-profile trusted=true <port-id>" doesn't
- cause the issue, meaning that the CLI client is correctly handling the
- request.
+ Setting the property via "openstack port set --binding-profile
+ trusted=true <port-id>" doesn't cause the issue, meaning that the CLI
+ client is correctly handling the request.
  
  Reproducer:
  -----------
  
  1. Create a simple stack by adding the below content to port.yaml and
  running the stack creation
  
  heat_template_version: 2015-10-15
  resources:
-   my_port:
-     type: OS::Neutron::Port
-     properties:
-       network: e08dd8fd-b484-4d3c-864a-b96a2dad61e3
-       value_specs: {"binding:profile": {"trusted": "off"}}
-       
- then run: 
+   my_port:
+     type: OS::Neutron::Port
+     properties:
+       network: e08dd8fd-b484-4d3c-864a-b96a2dad61e3
+       value_specs: {"binding:profile": {"trusted": "off"}}
+ 
+ then run:
  
  openstack stack create -t ./port.yaml test-port-val-spec --wait
  
  2. Change port.yaml to:
  
  heat_template_version: 2015-10-15
  resources:
-   my_port:
-     type: OS::Neutron::Port
-     properties:
-       network: e08dd8fd-b484-4d3c-864a-b96a2dad61e3
-       value_specs: {"binding:profile": {"spoof": "true"}}
+   my_port:
+     type: OS::Neutron::Port
+     properties:
+       network: e08dd8fd-b484-4d3c-864a-b96a2dad61e3
+       value_specs: {"binding:profile": {"spoof": "true"}}
  
  3. Run the stack update:
  
  openstack stack update -t ./port.yaml test-port-val-spec --wait
  
  4. Check the 'Request body' passed to Neutron:
  2025-06-19 11:36:33.216 4191656 DEBUG neutron.api.v2.base [req-e6dd17f9-b306-4add-b6fa-8cd81e364443 ...] Request body: {'port': {'device_id': '', 'device_owner': '', 'binding:host_id': None, 'binding:profile': {'spoof': 'true'}, 'dns_name': ''}} prepare_request_body /usr/lib/python3/dist-packages/neutron/api/v2/base.py:730
  
  Notice how the 'binding:profile' data is wiped and only contains the new
  value {'spoof': 'true'}, instead of {'trusted': 'off', 'spoof': 'true'}
  
  5. Delete the stack and change port.yaml to how it looked in step 1
  6. Recreate the stack
  7. Update the port via openstack client:
-     
+ 
  openstack port set --binding-profile spoof=true --debug <port-id>
  
  8. Check the 'Request body' passed to Neutron:
  
  2025-06-19 12:46:42.961 3785 DEBUG neutron.api.v2.base
  [req-2604229e-e09f-4e65-98a2-3cd682837d5f ...] Request body: {'port':
  {'binding:profile': {'trusted': 'off', 'spoof': 'true'}}}
  prepare_request_body /usr/lib/python3/dist-
  packages/neutron/api/v2/base.py:730
  
  Notice how the 'binding:profile' data is now updated with the old + new
  key/value(s): 'binding:profile': {'trusted': 'off', 'spoof': 'true'}
  
- Now, I believe the  issue is in merge_value_specs(). The call tree is:
+ Now, I believe the issue is in merge_value_specs(). The call tree is:
  
  handle_update()
  \_ prepare_update_properties()
-   \_ NeutronResource.merge_value_specs()
-   
- and merge_value_specs() seems not to be merging the values as it should. See the example behavior below with the corresponding values in each step:
-     
-     def merge_value_specs(props, before_value_specs=None):
+   \_ NeutronResource.merge_value_specs()
  
-         props: {'value_specs': {'binding:profile': {'trusted': 'off'}}}
+ and merge_value_specs() seems not to be merging the values as it should.
+ See the example behavior below with the corresponding values in each
+ step:
  
-         value_spec_props = props.pop('value_specs')
+     def merge_value_specs(props, before_value_specs=None):
  
-         props: {}
-         before_value_specs: {'binding:profile': {'spoof': 'off'}}
-         value_spec_props: {'binding:profile': {'trusted': 'off'}}
+         props: {'value_specs': {'binding:profile': {'trusted': 'off'}}}
  
-         if value_spec_props is not None:
-             if before_value_specs:
-                 for k in list(value_spec_props):
-                     if value_spec_props[k] == before_value_specs.get(k, None):
-                         value_spec_props.pop(k)
-             props: {}
-             props.update(value_spec_props)
-             props: {'binding:profile': {'trusted': 'off'}} 
+         value_spec_props = props.pop('value_specs')
+ 
+         props: {}
+         before_value_specs: {'binding:profile': {'spoof': 'off'}}
+         value_spec_props: {'binding:profile': {'trusted': 'off'}}
+ 
+         if value_spec_props is not None:
+             if before_value_specs:
+                 for k in list(value_spec_props):
+                     if value_spec_props[k] == before_value_specs.get(k, None):
+                         value_spec_props.pop(k)
+             props: {}
+             props.update(value_spec_props)
+             props: {'binding:profile': {'trusted': 'off'}}
+ 
+ The problem seems to be that this function is incorrectly handling
+ properties containing nested dicts such as 'binding:profile'.
  
  I will submit a patch shortly.

-- 
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/2114984

Title:
  Incorrect handling of value_specs upon port update via stack update

Status in heat package in Ubuntu:
  New

Bug description:
  When a port's value_specs are updated via a stack update action, the
  current value_specs content is wiped and replaced with the new values.
  This causes issues in various use cases. For example:

  - An SRIOV port has 'binding:profile' data such as {pci_slot='0000:17:02.1', pci_vendor_info='8086:1889', pf_mac_address='b4:96:91:f7:f1:d6'...}
  - A stack update is triggered with the below template:

  port2:
  type: OS::Neutron::Port
  properties:
  network: { get_param: network2 }
  binding:vnic_type: direct
  value_specs: {"binding:profile": {"trusted": "on"}}

  - Given that value_specs is updatable as per the schema definition, one would expect the above update to return a port with {pci_slot='0000:17:02.1', pci_vendor_info='8086:1889', pf_mac_address='b4:96:91:f7:f1:d6'..., "trusted": "on"}
  - The issue is that the above update returns a port with binding:profile reduced to "binding:profile": {"trusted": "on"}

  Setting the property via "openstack port set --binding-profile
  trusted=true <port-id>" doesn't cause the issue, meaning that the CLI
  client is correctly handling the request.

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

  1. Create a simple stack by adding the below content to port.yaml and
  running the stack creation

  heat_template_version: 2015-10-15
  resources:
    my_port:
      type: OS::Neutron::Port
      properties:
        network: e08dd8fd-b484-4d3c-864a-b96a2dad61e3
        value_specs: {"binding:profile": {"trusted": "off"}}

  then run:

  openstack stack create -t ./port.yaml test-port-val-spec --wait

  2. Change port.yaml to:

  heat_template_version: 2015-10-15
  resources:
    my_port:
      type: OS::Neutron::Port
      properties:
        network: e08dd8fd-b484-4d3c-864a-b96a2dad61e3
        value_specs: {"binding:profile": {"spoof": "true"}}

  3. Run the stack update:

  openstack stack update -t ./port.yaml test-port-val-spec --wait

  4. Check the 'Request body' passed to Neutron:
  2025-06-19 11:36:33.216 4191656 DEBUG neutron.api.v2.base [req-e6dd17f9-b306-4add-b6fa-8cd81e364443 ...] Request body: {'port': {'device_id': '', 'device_owner': '', 'binding:host_id': None, 'binding:profile': {'spoof': 'true'}, 'dns_name': ''}} prepare_request_body /usr/lib/python3/dist-packages/neutron/api/v2/base.py:730

  Notice how the 'binding:profile' data is wiped and only contains the
  new value {'spoof': 'true'}, instead of {'trusted': 'off', 'spoof':
  'true'}

  5. Delete the stack and change port.yaml to how it looked in step 1
  6. Recreate the stack
  7. Update the port via openstack client:

  openstack port set --binding-profile spoof=true --debug <port-id>

  8. Check the 'Request body' passed to Neutron:

  2025-06-19 12:46:42.961 3785 DEBUG neutron.api.v2.base
  [req-2604229e-e09f-4e65-98a2-3cd682837d5f ...] Request body: {'port':
  {'binding:profile': {'trusted': 'off', 'spoof': 'true'}}}
  prepare_request_body /usr/lib/python3/dist-
  packages/neutron/api/v2/base.py:730

  Notice how the 'binding:profile' data is now updated with the old +
  new key/value(s): 'binding:profile': {'trusted': 'off', 'spoof':
  'true'}

  Now, I believe the issue is in merge_value_specs(). The call tree is:

  handle_update()
  \_ prepare_update_properties()
    \_ NeutronResource.merge_value_specs()

  and merge_value_specs() seems not to be merging the values as it
  should. See the example behavior below with the corresponding values
  in each step:

      def merge_value_specs(props, before_value_specs=None):

          props: {'value_specs': {'binding:profile': {'trusted':
  'off'}}}

          value_spec_props = props.pop('value_specs')

          props: {}
          before_value_specs: {'binding:profile': {'spoof': 'off'}}
          value_spec_props: {'binding:profile': {'trusted': 'off'}}

          if value_spec_props is not None:
              if before_value_specs:
                  for k in list(value_spec_props):
                      if value_spec_props[k] == before_value_specs.get(k, None):
                          value_spec_props.pop(k)
              props: {}
              props.update(value_spec_props)
              props: {'binding:profile': {'trusted': 'off'}}

  The problem seems to be that this function is incorrectly handling
  properties containing nested dicts such as 'binding:profile'.

  I will submit a patch shortly.

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




More information about the Ubuntu-openstack-bugs mailing list