diff --git a/neutron/common/ovn/utils.py b/neutron/common/ovn/utils.py index 902f8d22e6c..1367351a491 100644 --- a/neutron/common/ovn/utils.py +++ b/neutron/common/ovn/utils.py @@ -263,6 +263,11 @@ def validate_and_get_data_from_binding_profile(port): ) % constants.PORT_CAP_PARAM raise n_exc.InvalidInput(error_message=msg) + # Note that only the keys mentioned in each parameter set, as defined in + # constants.OVN_PORT_BINDING_PROFILE_PARAMS, will be evaluated. + # + # Any surplus keys provided by Nova will be ignored and pruned from the + # Dict returned by this function. for pbp_param_set in constants.OVN_PORT_BINDING_PROFILE_PARAMS: if pbp_param_set.vnic_type: if pbp_param_set.vnic_type != vnic_type: @@ -278,13 +283,10 @@ def validate_and_get_data_from_binding_profile(port): pass if len(param_dict) == 0: continue - if len(param_dict) != len(param_keys): + if param_keys - binding_profile.keys(): msg = _('Invalid binding:profile. %s are all ' 'required.') % param_keys raise n_exc.InvalidInput(error_message=msg) - if (len(binding_profile) != len(param_keys)): - msg = _('Invalid binding:profile. too many parameters') - raise n_exc.InvalidInput(error_message=msg) break if not param_dict: diff --git a/neutron/tests/unit/common/ovn/test_utils.py b/neutron/tests/unit/common/ovn/test_utils.py index cee8e250fba..c7606283f22 100644 --- a/neutron/tests/unit/common/ovn/test_utils.py +++ b/neutron/tests/unit/common/ovn/test_utils.py @@ -531,7 +531,7 @@ class TestValidateAndGetDataFromBindingProfile(base.BaseTestCase): constants.OVNPortBindingProfileParamSet( { 'key': [str], - 'other_key': [str], + 'other_key': [int], 'third_key': [str] }, self.VNIC_FAKE_OTHER, constants.PORT_CAP_SWITCHDEV), @@ -647,6 +647,24 @@ class TestValidateAndGetDataFromBindingProfile(base.BaseTestCase): constants.OVN_PORT_BINDING_PROFILE: expect}), expect) + def test_valid_input_surplus_keys(self): + # Confirm that extra keys are allowed + binding_profile = { + constants.PORT_CAP_PARAM: [constants.PORT_CAP_SWITCHDEV], + 'pci_vendor_info': 'dead:beef', + 'pci_slot': '0000:ca:fe.42', + 'physical_network': 'physnet1', + 'optional_information_provided_by_nova': 'not_consumed_by_neutron', + } + expect = binding_profile.copy() + del(expect[constants.PORT_CAP_PARAM]) + del(expect['optional_information_provided_by_nova']) + self.assertDictEqual( + expect, + utils.validate_and_get_data_from_binding_profile( + {portbindings.VNIC_TYPE: portbindings.VNIC_DIRECT, + constants.OVN_PORT_BINDING_PROFILE: binding_profile})) + def test_unknown_profile_items_pruned(self): # Confirm that unknown profile items are pruned self.assertEqual( @@ -684,36 +702,41 @@ class TestValidateAndGetDataFromBindingProfile(base.BaseTestCase): def test_overlapping_param_set_different_vnic_type(self): # Confirm overlapping param sets discerned by vnic_type - expect = { + binding_profile = { 'key': 'value', 'other_key': 'value', } - # This param set is not valid for VNIC_FAKE_NORMAL - self.assertRaises( - neutron_lib.exceptions.InvalidInput, - utils.validate_and_get_data_from_binding_profile, - {portbindings.VNIC_TYPE: self.VNIC_FAKE_NORMAL, - constants.OVN_PORT_BINDING_PROFILE: expect}) + # This param set is valid for VNIC_FAKE_NORMAL with 'other_key' pruned. + expect = binding_profile.copy() + del(expect['other_key']) + self.assertDictEqual( + expect, + utils.validate_and_get_data_from_binding_profile( + {portbindings.VNIC_TYPE: self.VNIC_FAKE_NORMAL, + constants.OVN_PORT_BINDING_PROFILE: binding_profile})) # It is valid for VNIC_FAKE_OTHER + expect = binding_profile.copy() self.assertDictEqual( expect, utils.validate_and_get_data_from_binding_profile( {portbindings.VNIC_TYPE: self.VNIC_FAKE_OTHER, - constants.OVN_PORT_BINDING_PROFILE: expect})) + constants.OVN_PORT_BINDING_PROFILE: binding_profile})) def test_overlapping_param_set_different_vnic_type_and_capability(self): # Confirm overlapping param sets discerned by vnic_type and capability - expect = { + binding_profile = { 'key': 'value', - 'other_key': 'value', + 'other_key': 42, 'third_key': 'value', } # This param set is not valid for VNIC_FAKE_OTHER without capability + expect = binding_profile.copy() + del(expect['third_key']) self.assertRaises( neutron_lib.exceptions.InvalidInput, utils.validate_and_get_data_from_binding_profile, {portbindings.VNIC_TYPE: self.VNIC_FAKE_OTHER, - constants.OVN_PORT_BINDING_PROFILE: expect}) + constants.OVN_PORT_BINDING_PROFILE: binding_profile}) # This param set is also not valid as the capabilities do not match binding_profile = { constants.PORT_CAP_PARAM: ['fake-capability'], @@ -730,7 +753,7 @@ class TestValidateAndGetDataFromBindingProfile(base.BaseTestCase): binding_profile = { constants.PORT_CAP_PARAM: [constants.PORT_CAP_SWITCHDEV], 'key': 'value', - 'other_key': 'value', + 'other_key': 42, 'third_key': 'value', } expect = binding_profile.copy() diff --git a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py index 811b58c10bf..8fe321c5040 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py +++ b/neutron/tests/unit/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py @@ -3984,7 +3984,7 @@ class TestOVNVtepPortBinding(OVNMechanismDriverTestCase): with self.subnet(n): self._create_port(self.fmt, n['network']['id'], arg_list=(OVN_PROFILE,), - expected_res_status=400, + expected_res_status=404, **binding) def test_create_port_with_vtep_options_and_check_vtep_keys(self):