diff --git a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py index be1aeac497c..e08742937a8 100644 --- a/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py +++ b/neutron/plugins/ml2/drivers/ovn/mech_driver/ovsdb/ovn_client.py @@ -1951,7 +1951,8 @@ class OVNClient(object): def create_subnet(self, context, subnet, network): if subnet['enable_dhcp']: if subnet['ip_version'] == 4: - self.update_metadata_port(context, network['id']) + self.update_metadata_port(context, network['id'], + subnet_id=subnet['id']) self._add_subnet_dhcp_options(subnet, network) db_rev.bump_revision(context, subnet, ovn_const.TYPE_SUBNETS) @@ -1968,7 +1969,8 @@ class OVNClient(object): subnet['id'])['subnet'] if subnet['enable_dhcp'] or ovn_subnet: - self.update_metadata_port(context, network['id']) + self.update_metadata_port(context, network['id'], + subnet_id=subnet['id']) check_rev_cmd = self._nb_idl.check_revision_number( subnet['id'], subnet, ovn_const.TYPE_SUBNETS) @@ -2076,12 +2078,24 @@ class OVNClient(object): # TODO(boden): rehome create_port into neutron-lib p_utils.create_port(self._plugin, context, port) - def update_metadata_port(self, context, network_id): + def update_metadata_port(self, context, network_id, subnet_id=None): """Update metadata port. This function will allocate an IP address for the metadata port of - the given network in all its IPv4 subnets. + the given network in all its IPv4 subnets or the given subnet. """ + def update_metadata_port_fixed_ips(metadata_port, subnet_ids): + wanted_fixed_ips = [ + {'subnet_id': fixed_ip['subnet_id'], + 'ip_address': fixed_ip['ip_address']} for fixed_ip in + metadata_port['fixed_ips']] + wanted_fixed_ips.extend({'subnet_id': s_id} for s_id in subnet_ids) + port = {'id': metadata_port['id'], + 'port': {'network_id': network_id, + 'fixed_ips': wanted_fixed_ips}} + self._plugin.update_port(n_context.get_admin_context(), + metadata_port['id'], port) + if not ovn_conf.is_ovn_metadata_enabled(): return @@ -2092,31 +2106,28 @@ class OVNClient(object): network_id) return + port_subnet_ids = set(ip['subnet_id'] for ip in + metadata_port['fixed_ips']) + + # If this method is called from "create_subnet" or "update_subnet", + # only the fixed IP address from this subnet should be updated in the + # metadata port. + if subnet_id: + if subnet_id not in port_subnet_ids: + update_metadata_port_fixed_ips(metadata_port, [subnet_id]) + return + # Retrieve all subnets in this network subnets = self._plugin.get_subnets(context, filters=dict( network_id=[network_id], ip_version=[4])) subnet_ids = set(s['id'] for s in subnets) - port_subnet_ids = set(ip['subnet_id'] for ip in - metadata_port['fixed_ips']) # Find all subnets where metadata port doesn't have an IP in and # allocate one. if subnet_ids != port_subnet_ids: - wanted_fixed_ips = [] - for fixed_ip in metadata_port['fixed_ips']: - wanted_fixed_ips.append( - {'subnet_id': fixed_ip['subnet_id'], - 'ip_address': fixed_ip['ip_address']}) - wanted_fixed_ips.extend( - dict(subnet_id=s) - for s in subnet_ids - port_subnet_ids) - - port = {'id': metadata_port['id'], - 'port': {'network_id': network_id, - 'fixed_ips': wanted_fixed_ips}} - self._plugin.update_port(n_context.get_admin_context(), - metadata_port['id'], port) + update_metadata_port_fixed_ips(metadata_port, + subnet_ids - port_subnet_ids) def get_parent_port(self, port_id): return self._nb_idl.get_parent_port(port_id) 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 cef9e14b89d..27db955b9b1 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 @@ -1504,13 +1504,13 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase): self.mech_driver.update_subnet_postcommit(context) esd.assert_called_once_with( context.current, context.network.current, mock.ANY) - umd.assert_called_once_with(mock.ANY, 'id') + umd.assert_called_once_with(mock.ANY, 'id', subnet_id='subnet_id') def test_update_subnet_postcommit_disable_dhcp(self): self.mech_driver._nb_ovn.get_subnet_dhcp_options.return_value = { 'subnet': mock.sentinel.subnet, 'ports': []} context = fakes.FakeSubnetContext( - subnet={'enable_dhcp': False, 'id': 'fake_id', 'ip_version': 4, + subnet={'enable_dhcp': False, 'id': 'subnet_id', 'ip_version': 4, 'network_id': 'id'}, network={'id': 'id'}) with mock.patch.object( @@ -1521,7 +1521,7 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase): 'update_metadata_port') as umd: self.mech_driver.update_subnet_postcommit(context) dsd.assert_called_once_with(context.current['id'], mock.ANY) - umd.assert_called_once_with(mock.ANY, 'id') + umd.assert_called_once_with(mock.ANY, 'id', subnet_id='subnet_id') def test_update_subnet_postcommit_update_dhcp(self): self.mech_driver._nb_ovn.get_subnet_dhcp_options.return_value = { @@ -1539,7 +1539,63 @@ class TestOVNMechanismDriver(test_plugin.Ml2PluginV2TestCase): self.mech_driver.update_subnet_postcommit(context) usd.assert_called_once_with( context.current, context.network.current, mock.ANY) - umd.assert_called_once_with(mock.ANY, 'id') + umd.assert_called_once_with(mock.ANY, 'id', subnet_id='subnet_id') + + def test_update_metadata_port_with_subnet_present_in_port(self): + ovn_conf.cfg.CONF.set_override('ovn_metadata_enabled', True, + group='ovn') + fixed_ips = [{'subnet_id': 'subnet1', 'ip_address': 'ip_add1'}] + with mock.patch.object( + self.mech_driver._ovn_client, '_find_metadata_port', + return_value={'fixed_ips': fixed_ips, 'id': 'metadata_id'}), \ + mock.patch.object(self.mech_driver._plugin, 'get_subnets', + return_value=[{'id': 'subnet1'}, + {'id': 'subnet2'}]), \ + mock.patch.object(self.mech_driver._plugin, 'update_port') as \ + mock_update_port: + self.mech_driver._ovn_client.update_metadata_port( + self.context, 'net_id', subnet_id='subnet1') + mock_update_port.assert_not_called() + + def test_update_metadata_port_with_subnet_not_present_in_port(self): + ovn_conf.cfg.CONF.set_override('ovn_metadata_enabled', True, + group='ovn') + fixed_ips = [{'subnet_id': 'subnet1', 'ip_address': 'ip_add1'}] + with mock.patch.object( + self.mech_driver._ovn_client, '_find_metadata_port', + return_value={'fixed_ips': fixed_ips, 'id': 'metadata_id'}), \ + mock.patch.object(self.mech_driver._plugin, 'get_subnets', + return_value=[{'id': 'subnet1'}, + {'id': 'subnet2'}]), \ + mock.patch.object(self.mech_driver._plugin, 'update_port') as \ + mock_update_port: + self.mech_driver._ovn_client.update_metadata_port( + self.context, 'net_id', subnet_id='subnet3') + fixed_ips.append({'subnet_id': 'subnet3'}) + port = {'id': 'metadata_id', 'port': { + 'network_id': 'net_id', 'fixed_ips': fixed_ips}} + mock_update_port.assert_called_once_with( + mock.ANY, 'metadata_id', port) + + def test_update_metadata_port_no_subnet(self): + ovn_conf.cfg.CONF.set_override('ovn_metadata_enabled', True, + group='ovn') + fixed_ips = [{'subnet_id': 'subnet1', 'ip_address': 'ip_add1'}] + with mock.patch.object( + self.mech_driver._ovn_client, '_find_metadata_port', + return_value={'fixed_ips': fixed_ips, 'id': 'metadata_id'}), \ + mock.patch.object(self.mech_driver._plugin, 'get_subnets', + return_value=[{'id': 'subnet1'}, + {'id': 'subnet2'}]), \ + mock.patch.object(self.mech_driver._plugin, 'update_port') as \ + mock_update_port: + self.mech_driver._ovn_client.update_metadata_port(self.context, + 'net_id') + fixed_ips.append({'subnet_id': 'subnet2'}) + port = {'id': 'metadata_id', 'port': { + 'network_id': 'net_id', 'fixed_ips': fixed_ips}} + mock_update_port.assert_called_once_with( + mock.ANY, 'metadata_id', port) @mock.patch.object(provisioning_blocks, 'is_object_blocked') @mock.patch.object(provisioning_blocks, 'provisioning_complete')