diff --git a/nova/network/neutronv2/api.py b/nova/network/neutronv2/api.py index fb97871d6f35..288cbf4ad0bf 100644 --- a/nova/network/neutronv2/api.py +++ b/nova/network/neutronv2/api.py @@ -3103,6 +3103,23 @@ class API(base_api.NetworkAPI): subnet_dict['dhcp_server'] = ip_pair['ip_address'] break + # NOTE(arnaudmorin): If enable_dhcp is set on subnet, but, for + # some reason neutron did not have any DHCP port yet, we still + # want the network_info to be populated with a valid dhcp_server + # value. This is mostly useful for the metadata API (which is + # relying on this value to give network_data to the instance). + # + # This will also help some providers which are using external + # DHCP servers not handled by neutron. + # In this case, neutron will never create any DHCP port in the + # subnet. + # + # Also note that we cannot set the value to None because then the + # value would be discarded by the metadata API. + # So the subnet gateway will be used as fallback. + if subnet.get('enable_dhcp') and 'dhcp_server' not in subnet_dict: + subnet_dict['dhcp_server'] = subnet['gateway_ip'] + subnet_object = network_model.Subnet(**subnet_dict) for dns in subnet.get('dns_nameservers', []): subnet_object.add_dns( diff --git a/nova/tests/unit/network/test_neutronv2.py b/nova/tests/unit/network/test_neutronv2.py index 01a6cdfa2842..ead936f75258 100644 --- a/nova/tests/unit/network/test_neutronv2.py +++ b/nova/tests/unit/network/test_neutronv2.py @@ -2507,6 +2507,49 @@ class TestNeutronv2(TestNeutronv2Base): self.assertEqual(subnet_data1[0]['host_routes'][0]['nexthop'], subnets[0]['routes'][0]['gateway']['address']) + @mock.patch.object(neutronapi, 'get_client') + def test_get_subnets_from_port_enabled_dhcp(self, mock_get_client): + api = neutronapi.API() + mocked_client = mock.create_autospec(client.Client) + mock_get_client.return_value = mocked_client + + port_data = copy.copy(self.port_data1[0]) + # add another IP on the same subnet and verify the subnet is deduped + port_data['fixed_ips'].append({'ip_address': '10.0.1.3', + 'subnet_id': 'my_subid1'}) + subnet_data1 = copy.copy(self.subnet_data1) + subnet_data1[0]['enable_dhcp'] = True + + mocked_client.list_subnets.return_value = {'subnets': subnet_data1} + mocked_client.list_ports.return_value = {'ports': self.dhcp_port_data1} + + subnets = api._get_subnets_from_port(self.context, port_data) + + self.assertEqual(self.dhcp_port_data1[0]['fixed_ips'][0]['ip_address'], + subnets[0]['meta']['dhcp_server']) + + @mock.patch.object(neutronapi, 'get_client') + def test_get_subnets_from_port_enabled_dhcp_no_dhcp_ports(self, + mock_get_client): + api = neutronapi.API() + mocked_client = mock.create_autospec(client.Client) + mock_get_client.return_value = mocked_client + + port_data = copy.copy(self.port_data1[0]) + # add another IP on the same subnet and verify the subnet is deduped + port_data['fixed_ips'].append({'ip_address': '10.0.1.3', + 'subnet_id': 'my_subid1'}) + subnet_data1 = copy.copy(self.subnet_data1) + subnet_data1[0]['enable_dhcp'] = True + + mocked_client.list_subnets.return_value = {'subnets': subnet_data1} + mocked_client.list_ports.return_value = {'ports': []} + + subnets = api._get_subnets_from_port(self.context, port_data) + + self.assertEqual(subnet_data1[0]['gateway_ip'], + subnets[0]['meta']['dhcp_server']) + def test_get_all_empty_list_networks(self): api = neutronapi.API() neutronapi.get_client(mox.IgnoreArg()).AndReturn(self.moxed_client) diff --git a/releasenotes/notes/always-set-dhcp-server-if-enable-dhcp-b96bf720af235902.yaml b/releasenotes/notes/always-set-dhcp-server-if-enable-dhcp-b96bf720af235902.yaml new file mode 100644 index 000000000000..f683cec6fde9 --- /dev/null +++ b/releasenotes/notes/always-set-dhcp-server-if-enable-dhcp-b96bf720af235902.yaml @@ -0,0 +1,5 @@ +--- +features: + - When ``enable_dhcp`` is set on a subnet but there is no DHCP port on neutron + then the ``dhcp_server`` value in meta hash will contain the subnet + gateway IP instead of being absent.