Update DHCP port information during setup

When setting up the DHCP agent of a network, the DHCP namespace external
port is configured. If this port already exists and the fixed IP
addresses are correctly configured (in the DHCP subnets range), the port
is used as is.

Sometimes, because of 1627480 or 1841636, the port information is not
correctly retrieved. This patch does not solve it but mitigates the
process of resynchronizing the network DHCP. If the stored DHCP port
does not have the correct information, the agent calls the RPC plugin to
retrieve from the server the DHCP port updated information, including
the fixed IP address and the subnets.

Change-Id: Iff40e7bba645ee12c2001d7ce735a36e0ddc81e9
Related-Bug: #1627480
Related-Bug: #1841636
This commit is contained in:
Rodolfo Alonso Hernandez 2019-08-27 09:43:26 +00:00
parent f5bcca87d1
commit b0a93df476
4 changed files with 66 additions and 16 deletions

View File

@ -718,7 +718,7 @@ class DhcpPluginApi(object):
and update_dhcp_port methods.
1.5 - Added dhcp_ready_on_ports
1.7 - Added get_networks
1.8 - Added get_dhcp_port
"""
def __init__(self, topic, host):
@ -776,6 +776,13 @@ class DhcpPluginApi(object):
network_id=network_id, device_id=device_id,
host=self.host)
def get_dhcp_port(self, port_id):
"""Make a remote process call to retrieve the dhcp port."""
cctxt = self.client.prepare(version='1.8')
port = cctxt.call(self.context, 'get_dhcp_port', port_id=port_id)
if port:
return dhcp.DictModel(port)
def dhcp_ready_on_ports(self, port_ids):
"""Notify the server that DHCP is configured for the port."""
cctxt = self.client.prepare(version='1.5')

View File

@ -1391,6 +1391,37 @@ class DeviceManager(object):
fixed_ips=unique_ip_subnets)
return self.plugin.create_dhcp_port({'port': port_dict})
def _check_dhcp_port_subnet(self, dhcp_port, dhcp_subnets, network):
"""Check if DHCP port IPs are in the range of the DHCP subnets
FIXME(kevinbenton): ensure we have the IPs we actually need.
can be removed once bug/1627480 is fixed
"""
if self.driver.use_gateway_ips:
return
expected = set(dhcp_subnets)
actual = {fip.subnet_id for fip in dhcp_port.fixed_ips}
missing = expected - actual
if not missing:
return
LOG.debug('Requested DHCP port with IPs on subnets %(expected)s '
'but only got IPs on subnets %(actual)s.',
{'expected': expected, 'actual': actual})
updated_dhcp_port = self.plugin.get_dhcp_port(dhcp_port.id)
actual = {fip.subnet_id for fip in updated_dhcp_port.fixed_ips}
missing = expected - actual
if missing:
raise exceptions.SubnetMismatchForPort(
port_id=updated_dhcp_port.id, subnet_id=list(missing)[0])
self._update_dhcp_port(network, updated_dhcp_port)
LOG.debug('Previous DHCP port information: %(dhcp_port)s. Updated '
'DHCP port information: %(updated_dhcp_port)s.',
{'dhcp_port': dhcp_port,
'updated_dhcp_port': updated_dhcp_port})
def setup_dhcp_port(self, network):
"""Create/update DHCP port for the host if needed and return port."""
@ -1416,19 +1447,8 @@ class DeviceManager(object):
else:
raise exceptions.Conflict()
# FIXME(kevinbenton): ensure we have the IPs we actually need.
# can be removed once bug/1627480 is fixed
if not self.driver.use_gateway_ips:
expected = set(dhcp_subnets)
actual = {fip.subnet_id for fip in dhcp_port.fixed_ips}
missing = expected - actual
if missing:
LOG.debug("Requested DHCP port with IPs on subnets "
"%(expected)s but only got IPs on subnets "
"%(actual)s.", {'expected': expected,
'actual': actual})
raise exceptions.SubnetMismatchForPort(
port_id=dhcp_port.id, subnet_id=list(missing)[0])
self._check_dhcp_port_subnet(dhcp_port, dhcp_subnets, network)
# Convert subnet_id to subnet dict
fixed_ips = [dict(subnet_id=fixed_ip.subnet_id,
ip_address=fixed_ip.ip_address,

View File

@ -71,10 +71,11 @@ class DhcpRpcCallback(object):
# DHCP agent since Havana, so similar rationale for not bumping
# the major version as above applies here too.
# 1.7 - Add get_networks
# 1.8 - Add get_dhcp_port
target = oslo_messaging.Target(
namespace=constants.RPC_NAMESPACE_DHCP_PLUGIN,
version='1.7')
version='1.8')
def _get_active_networks(self, context, **kwargs):
"""Retrieve and return a list of the active networks."""
@ -313,6 +314,13 @@ class DhcpRpcCallback(object):
'%(port_id)s which no longer exists.',
{'host': host, 'port_id': port['id']})
@db_api.retry_db_errors
def get_dhcp_port(self, context, **kwargs):
"""Retrieve the DHCP port"""
port_id = kwargs.get('port_id')
plugin = directory.get_plugin()
return plugin.get_port(context, port_id)
@db_api.retry_db_errors
def dhcp_ready_on_ports(self, context, port_ids):
for port_id in port_ids:

View File

@ -1906,7 +1906,21 @@ class TestDeviceManager(base.BaseTestCase):
[{'subnet_id': fake_fixed_ip1.subnet_id}],
'device_id': mock.ANY}})])
def test_create_dhcp_port_update_add_subnet_bug_1627480(self):
def test__check_dhcp_port_subnet(self):
# this can go away once bug/1627480 is fixed
plugin = mock.Mock()
fake_port_copy = copy.deepcopy(fake_port1)
fake_port_copy.fixed_ips = [fake_fixed_ip1, fake_fixed_ip_subnet2]
plugin.get_dhcp_port.return_value = fake_port_copy
dh = dhcp.DeviceManager(cfg.CONF, plugin)
fake_network_copy = copy.deepcopy(fake_network)
fake_network_copy.ports[0].device_id = dh.get_device_id(fake_network)
fake_network_copy.subnets[1].enable_dhcp = True
plugin.update_dhcp_port.return_value = fake_network.ports[0]
dh.setup_dhcp_port(fake_network_copy)
self.assertEqual(fake_port_copy, fake_network_copy.ports[0])
def test__check_dhcp_port_subnet_port_missing_subnet(self):
# this can go away once bug/1627480 is fixed
plugin = mock.Mock()
dh = dhcp.DeviceManager(cfg.CONF, plugin)
@ -1914,6 +1928,7 @@ class TestDeviceManager(base.BaseTestCase):
fake_network_copy.ports[0].device_id = dh.get_device_id(fake_network)
fake_network_copy.subnets[1].enable_dhcp = True
plugin.update_dhcp_port.return_value = fake_network.ports[0]
plugin.get_dhcp_port.return_value = fake_network_copy.ports[0]
with testtools.ExpectedException(exceptions.SubnetMismatchForPort):
dh.setup_dhcp_port(fake_network_copy)