add non-routed subnet metadata support
implements blueprint metadata-non-routed This patchset completes Quantum metadata support by adding metadata proxy support for isolated network segments. The support requires that the guest instance request host routes, so that the DHCP port can be used to proxy metadata requests. NOTE: The cirros image does not support host router, so the UEC or equivalent required for testing and usage. Change-Id: I962deef7c164ecb2a93b7af326ef8dca6e2b183a
This commit is contained in:
parent
af1b0d19fc
commit
cb6b9994e2
@ -29,3 +29,10 @@ dhcp_driver = quantum.agent.linux.dhcp.Dnsmasq
|
|||||||
# Allow overlapping IP (Must have kernel build with CONFIG_NET_NS=y and
|
# Allow overlapping IP (Must have kernel build with CONFIG_NET_NS=y and
|
||||||
# iproute2 package that supports namespaces).
|
# iproute2 package that supports namespaces).
|
||||||
# use_namespaces = True
|
# use_namespaces = True
|
||||||
|
|
||||||
|
# The DHCP server can assist with providing metadata support on isolated
|
||||||
|
# networks. Setting this value to True will cause the DHCP server to append
|
||||||
|
# specific host routes to the DHCP request. The metadata service will only
|
||||||
|
# be activated when the subnet gateway_ip is None. The guest instance must
|
||||||
|
# be configured to request host routes via DHCP (Option 121).
|
||||||
|
# enable_isolated_metadata = False
|
||||||
|
@ -24,6 +24,7 @@ import netaddr
|
|||||||
|
|
||||||
from quantum.agent.common import config
|
from quantum.agent.common import config
|
||||||
from quantum.agent.linux import dhcp
|
from quantum.agent.linux import dhcp
|
||||||
|
from quantum.agent.linux import external_process
|
||||||
from quantum.agent.linux import interface
|
from quantum.agent.linux import interface
|
||||||
from quantum.agent.linux import ip_lib
|
from quantum.agent.linux import ip_lib
|
||||||
from quantum.agent import rpc as agent_rpc
|
from quantum.agent import rpc as agent_rpc
|
||||||
@ -39,6 +40,8 @@ from quantum.openstack.common import uuidutils
|
|||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
NS_PREFIX = 'qdhcp-'
|
NS_PREFIX = 'qdhcp-'
|
||||||
|
METADATA_DEFAULT_IP = '169.254.169.254/16'
|
||||||
|
METADATA_PORT = 80
|
||||||
|
|
||||||
|
|
||||||
class DhcpAgent(object):
|
class DhcpAgent(object):
|
||||||
@ -49,7 +52,9 @@ class DhcpAgent(object):
|
|||||||
default='quantum.agent.linux.dhcp.Dnsmasq',
|
default='quantum.agent.linux.dhcp.Dnsmasq',
|
||||||
help=_("The driver used to manage the DHCP server.")),
|
help=_("The driver used to manage the DHCP server.")),
|
||||||
cfg.BoolOpt('use_namespaces', default=True,
|
cfg.BoolOpt('use_namespaces', default=True,
|
||||||
help=_("Allow overlapping IP."))
|
help=_("Allow overlapping IP.")),
|
||||||
|
cfg.BoolOpt('enable_isolated_metadata', default=False,
|
||||||
|
help=_("Support Metadata requests on isolated networks."))
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self, conf):
|
def __init__(self, conf):
|
||||||
@ -73,12 +78,12 @@ class DhcpAgent(object):
|
|||||||
self.lease_relay.start()
|
self.lease_relay.start()
|
||||||
self.notifications.run_dispatch(self)
|
self.notifications.run_dispatch(self)
|
||||||
|
|
||||||
|
def _ns_name(self, network):
|
||||||
|
if self.conf.use_namespaces:
|
||||||
|
return NS_PREFIX + network.id
|
||||||
|
|
||||||
def call_driver(self, action, network):
|
def call_driver(self, action, network):
|
||||||
"""Invoke an action on a DHCP driver instance."""
|
"""Invoke an action on a DHCP driver instance."""
|
||||||
if self.conf.use_namespaces:
|
|
||||||
namespace = NS_PREFIX + network.id
|
|
||||||
else:
|
|
||||||
namespace = None
|
|
||||||
try:
|
try:
|
||||||
# the Driver expects something that is duck typed similar to
|
# the Driver expects something that is duck typed similar to
|
||||||
# the base models.
|
# the base models.
|
||||||
@ -86,7 +91,7 @@ class DhcpAgent(object):
|
|||||||
network,
|
network,
|
||||||
self.root_helper,
|
self.root_helper,
|
||||||
self.device_manager,
|
self.device_manager,
|
||||||
namespace)
|
self._ns_name(network))
|
||||||
getattr(driver, action)()
|
getattr(driver, action)()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@ -145,6 +150,8 @@ class DhcpAgent(object):
|
|||||||
for subnet in network.subnets:
|
for subnet in network.subnets:
|
||||||
if subnet.enable_dhcp:
|
if subnet.enable_dhcp:
|
||||||
if self.call_driver('enable', network):
|
if self.call_driver('enable', network):
|
||||||
|
if self.conf.use_namespaces:
|
||||||
|
self.enable_isolated_metadata_proxy(network)
|
||||||
self.cache.put(network)
|
self.cache.put(network)
|
||||||
break
|
break
|
||||||
|
|
||||||
@ -152,6 +159,8 @@ class DhcpAgent(object):
|
|||||||
"""Disable DHCP for a network known to the agent."""
|
"""Disable DHCP for a network known to the agent."""
|
||||||
network = self.cache.get_network_by_id(network_id)
|
network = self.cache.get_network_by_id(network_id)
|
||||||
if network:
|
if network:
|
||||||
|
if self.conf.use_namespaces:
|
||||||
|
self.disable_isolated_metadata_proxy(network)
|
||||||
if self.call_driver('disable', network):
|
if self.call_driver('disable', network):
|
||||||
self.cache.remove(network)
|
self.cache.remove(network)
|
||||||
|
|
||||||
@ -235,6 +244,29 @@ class DhcpAgent(object):
|
|||||||
self.cache.remove_port(port)
|
self.cache.remove_port(port)
|
||||||
self.call_driver('reload_allocations', network)
|
self.call_driver('reload_allocations', network)
|
||||||
|
|
||||||
|
def enable_isolated_metadata_proxy(self, network):
|
||||||
|
def callback(pid_file):
|
||||||
|
return ['quantum-ns-metadata-proxy',
|
||||||
|
'--pid_file=%s' % pid_file,
|
||||||
|
'--network_id=%s' % network.id,
|
||||||
|
'--state_path=%s' % self.conf.state_path,
|
||||||
|
'--metadata_port=%d' % METADATA_PORT]
|
||||||
|
|
||||||
|
pm = external_process.ProcessManager(
|
||||||
|
self.conf,
|
||||||
|
network.id,
|
||||||
|
self.conf.root_helper,
|
||||||
|
self._ns_name(network))
|
||||||
|
pm.enable(callback)
|
||||||
|
|
||||||
|
def disable_isolated_metadata_proxy(self, network):
|
||||||
|
pm = external_process.ProcessManager(
|
||||||
|
self.conf,
|
||||||
|
network.id,
|
||||||
|
self.conf.root_helper,
|
||||||
|
self._ns_name(network))
|
||||||
|
pm.disable()
|
||||||
|
|
||||||
|
|
||||||
class DhcpPluginApi(proxy.RpcProxy):
|
class DhcpPluginApi(proxy.RpcProxy):
|
||||||
"""Agent side of the dhcp rpc API.
|
"""Agent side of the dhcp rpc API.
|
||||||
@ -447,6 +479,9 @@ class DeviceManager(object):
|
|||||||
ip_cidr = '%s/%s' % (fixed_ip.ip_address, net.prefixlen)
|
ip_cidr = '%s/%s' % (fixed_ip.ip_address, net.prefixlen)
|
||||||
ip_cidrs.append(ip_cidr)
|
ip_cidrs.append(ip_cidr)
|
||||||
|
|
||||||
|
if self.conf.enable_isolated_metadata and self.conf.use_namespaces:
|
||||||
|
ip_cidrs.append(METADATA_DEFAULT_IP)
|
||||||
|
|
||||||
self.driver.init_l3(interface_name, ip_cidrs,
|
self.driver.init_l3(interface_name, ip_cidrs,
|
||||||
namespace=namespace)
|
namespace=namespace)
|
||||||
|
|
||||||
|
@ -58,6 +58,7 @@ TCP = 'tcp'
|
|||||||
DNS_PORT = 53
|
DNS_PORT = 53
|
||||||
DHCPV4_PORT = 67
|
DHCPV4_PORT = 67
|
||||||
DHCPV6_PORT = 467
|
DHCPV6_PORT = 467
|
||||||
|
METADATA_DEFAULT_IP = '169.254.169.254'
|
||||||
|
|
||||||
|
|
||||||
class DhcpBase(object):
|
class DhcpBase(object):
|
||||||
@ -264,14 +265,15 @@ class Dnsmasq(DhcpLocalProcess):
|
|||||||
utils.execute(cmd, self.root_helper)
|
utils.execute(cmd, self.root_helper)
|
||||||
|
|
||||||
def reload_allocations(self):
|
def reload_allocations(self):
|
||||||
"""If all subnets turn off dhcp, kill the process."""
|
"""Rebuild the dnsmasq config and signal the dnsmasq to reload."""
|
||||||
|
|
||||||
|
# If all subnets turn off dhcp, kill the process.
|
||||||
if not self._enable_dhcp():
|
if not self._enable_dhcp():
|
||||||
self.disable()
|
self.disable()
|
||||||
LOG.debug(_('Killing dhcpmasq for network since all subnets have '
|
LOG.debug(_('Killing dhcpmasq for network since all subnets have '
|
||||||
'turned off DHCP: %s'), self.network.id)
|
'turned off DHCP: %s'), self.network.id)
|
||||||
return
|
return
|
||||||
|
|
||||||
"""Rebuilds the dnsmasq config and signal the dnsmasq to reload."""
|
|
||||||
self._output_hosts_file()
|
self._output_hosts_file()
|
||||||
self._output_opts_file()
|
self._output_opts_file()
|
||||||
cmd = ['kill', '-HUP', self.pid]
|
cmd = ['kill', '-HUP', self.pid]
|
||||||
@ -301,6 +303,10 @@ class Dnsmasq(DhcpLocalProcess):
|
|||||||
|
|
||||||
def _output_opts_file(self):
|
def _output_opts_file(self):
|
||||||
"""Write a dnsmasq compatible options file."""
|
"""Write a dnsmasq compatible options file."""
|
||||||
|
|
||||||
|
if self.conf.enable_isolated_metadata:
|
||||||
|
subnet_to_interface_ip = self._make_subnet_interface_ip_map()
|
||||||
|
|
||||||
options = []
|
options = []
|
||||||
for i, subnet in enumerate(self.network.subnets):
|
for i, subnet in enumerate(self.network.subnets):
|
||||||
if not subnet.enable_dhcp:
|
if not subnet.enable_dhcp:
|
||||||
@ -312,6 +318,19 @@ class Dnsmasq(DhcpLocalProcess):
|
|||||||
|
|
||||||
host_routes = ["%s,%s" % (hr.destination, hr.nexthop)
|
host_routes = ["%s,%s" % (hr.destination, hr.nexthop)
|
||||||
for hr in subnet.host_routes]
|
for hr in subnet.host_routes]
|
||||||
|
|
||||||
|
# Add host routes for isolated network segments
|
||||||
|
enable_metadata = (
|
||||||
|
self.conf.enable_isolated_metadata
|
||||||
|
and not subnet.gateway_ip
|
||||||
|
and subnet.ip_version == 4)
|
||||||
|
|
||||||
|
if enable_metadata:
|
||||||
|
subnet_dhcp_ip = subnet_to_interface_ip[subnet.id]
|
||||||
|
host_routes.append(
|
||||||
|
'%s/32,%s' % (METADATA_DEFAULT_IP, subnet_dhcp_ip)
|
||||||
|
)
|
||||||
|
|
||||||
if host_routes:
|
if host_routes:
|
||||||
options.append(
|
options.append(
|
||||||
self._format_option(i, 'classless-static-route',
|
self._format_option(i, 'classless-static-route',
|
||||||
@ -328,6 +347,28 @@ class Dnsmasq(DhcpLocalProcess):
|
|||||||
replace_file(name, '\n'.join(options))
|
replace_file(name, '\n'.join(options))
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
def _make_subnet_interface_ip_map(self):
|
||||||
|
ip_dev = ip_lib.IPDevice(
|
||||||
|
self.interface_name,
|
||||||
|
self.root_helper,
|
||||||
|
self.namespace
|
||||||
|
)
|
||||||
|
|
||||||
|
subnet_lookup = dict(
|
||||||
|
(netaddr.IPNetwork(subnet.cidr), subnet.id)
|
||||||
|
for subnet in self.network.subnets
|
||||||
|
)
|
||||||
|
|
||||||
|
retval = {}
|
||||||
|
|
||||||
|
for addr in ip_dev.addr.list():
|
||||||
|
ip_net = netaddr.IPNetwork(addr['cidr'])
|
||||||
|
|
||||||
|
if ip_net in subnet_lookup:
|
||||||
|
retval[subnet_lookup[ip_net]] = addr['cidr'].split('/')[0]
|
||||||
|
|
||||||
|
return retval
|
||||||
|
|
||||||
def _lease_relay_script_path(self):
|
def _lease_relay_script_path(self):
|
||||||
return os.path.join(os.path.dirname(sys.argv[0]),
|
return os.path.join(os.path.dirname(sys.argv[0]),
|
||||||
'quantum-dhcp-agent-dnsmasq-lease-update')
|
'quantum-dhcp-agent-dnsmasq-lease-update')
|
||||||
|
@ -100,6 +100,7 @@ class TestDhcpAgent(unittest.TestCase):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.notification_p.stop()
|
self.notification_p.stop()
|
||||||
self.driver_cls_p.stop()
|
self.driver_cls_p.stop()
|
||||||
|
cfg.CONF.reset()
|
||||||
|
|
||||||
def test_dhcp_agent_main(self):
|
def test_dhcp_agent_main(self):
|
||||||
logging_str = 'quantum.agent.common.config.setup_logging'
|
logging_str = 'quantum.agent.common.config.setup_logging'
|
||||||
@ -131,6 +132,19 @@ class TestDhcpAgent(unittest.TestCase):
|
|||||||
[mock.call.start()])
|
[mock.call.start()])
|
||||||
self.notification.assert_has_calls([mock.call.run_dispatch()])
|
self.notification.assert_has_calls([mock.call.run_dispatch()])
|
||||||
|
|
||||||
|
def test_ns_name(self):
|
||||||
|
with mock.patch('quantum.agent.dhcp_agent.DeviceManager') as dev_mgr:
|
||||||
|
mock_net = mock.Mock(id='foo')
|
||||||
|
dhcp = dhcp_agent.DhcpAgent(cfg.CONF)
|
||||||
|
self.assertTrue(dhcp._ns_name(mock_net), 'qdhcp-foo')
|
||||||
|
|
||||||
|
def test_ns_name_disabled_namespace(self):
|
||||||
|
with mock.patch('quantum.agent.dhcp_agent.DeviceManager') as dev_mgr:
|
||||||
|
cfg.CONF.set_override('use_namespaces', False)
|
||||||
|
mock_net = mock.Mock(id='foo')
|
||||||
|
dhcp = dhcp_agent.DhcpAgent(cfg.CONF)
|
||||||
|
self.assertIsNone(dhcp._ns_name(mock_net))
|
||||||
|
|
||||||
def test_call_driver(self):
|
def test_call_driver(self):
|
||||||
network = mock.Mock()
|
network = mock.Mock()
|
||||||
network.id = '1'
|
network.id = '1'
|
||||||
@ -275,8 +289,13 @@ class TestDhcpAgentEventHandler(unittest.TestCase):
|
|||||||
self.call_driver_p = mock.patch.object(self.dhcp, 'call_driver')
|
self.call_driver_p = mock.patch.object(self.dhcp, 'call_driver')
|
||||||
|
|
||||||
self.call_driver = self.call_driver_p.start()
|
self.call_driver = self.call_driver_p.start()
|
||||||
|
self.external_process_p = mock.patch(
|
||||||
|
'quantum.agent.linux.external_process.ProcessManager'
|
||||||
|
)
|
||||||
|
self.external_process = self.external_process_p.start()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
|
self.external_process_p.stop()
|
||||||
self.call_driver_p.stop()
|
self.call_driver_p.stop()
|
||||||
self.cache_p.stop()
|
self.cache_p.stop()
|
||||||
self.plugin_p.stop()
|
self.plugin_p.stop()
|
||||||
@ -289,6 +308,14 @@ class TestDhcpAgentEventHandler(unittest.TestCase):
|
|||||||
[mock.call.get_network_info(fake_network.id)])
|
[mock.call.get_network_info(fake_network.id)])
|
||||||
self.call_driver.assert_called_once_with('enable', fake_network)
|
self.call_driver.assert_called_once_with('enable', fake_network)
|
||||||
self.cache.assert_has_calls([mock.call.put(fake_network)])
|
self.cache.assert_has_calls([mock.call.put(fake_network)])
|
||||||
|
self.external_process.assert_has_calls([
|
||||||
|
mock.call(
|
||||||
|
cfg.CONF,
|
||||||
|
'12345678-1234-5678-1234567890ab',
|
||||||
|
'sudo',
|
||||||
|
'qdhcp-12345678-1234-5678-1234567890ab'),
|
||||||
|
mock.call().enable(mock.ANY)
|
||||||
|
])
|
||||||
|
|
||||||
def test_enable_dhcp_helper_down_network(self):
|
def test_enable_dhcp_helper_down_network(self):
|
||||||
self.plugin.get_network_info.return_value = fake_down_network
|
self.plugin.get_network_info.return_value = fake_down_network
|
||||||
@ -297,6 +324,7 @@ class TestDhcpAgentEventHandler(unittest.TestCase):
|
|||||||
[mock.call.get_network_info(fake_down_network.id)])
|
[mock.call.get_network_info(fake_down_network.id)])
|
||||||
self.assertFalse(self.call_driver.called)
|
self.assertFalse(self.call_driver.called)
|
||||||
self.assertFalse(self.cache.called)
|
self.assertFalse(self.cache.called)
|
||||||
|
self.assertFalse(self.external_process.called)
|
||||||
|
|
||||||
def test_enable_dhcp_helper_exception_during_rpc(self):
|
def test_enable_dhcp_helper_exception_during_rpc(self):
|
||||||
self.plugin.get_network_info.side_effect = Exception
|
self.plugin.get_network_info.side_effect = Exception
|
||||||
@ -308,15 +336,17 @@ class TestDhcpAgentEventHandler(unittest.TestCase):
|
|||||||
self.assertTrue(log.called)
|
self.assertTrue(log.called)
|
||||||
self.assertTrue(self.dhcp.needs_resync)
|
self.assertTrue(self.dhcp.needs_resync)
|
||||||
self.assertFalse(self.cache.called)
|
self.assertFalse(self.cache.called)
|
||||||
|
self.assertFalse(self.external_process.called)
|
||||||
|
|
||||||
def test_enable_dhcp_helper_driver_failure(self):
|
def test_enable_dhcp_helper_driver_failure(self):
|
||||||
self.plugin.get_network_info.return_value = fake_network
|
self.plugin.get_network_info.return_value = fake_network
|
||||||
|
self.call_driver.return_value = False
|
||||||
self.dhcp.enable_dhcp_helper(fake_network.id)
|
self.dhcp.enable_dhcp_helper(fake_network.id)
|
||||||
self.call_driver.enable.return_value = False
|
|
||||||
self.plugin.assert_has_calls(
|
self.plugin.assert_has_calls(
|
||||||
[mock.call.get_network_info(fake_network.id)])
|
[mock.call.get_network_info(fake_network.id)])
|
||||||
self.call_driver.assert_called_once_with('enable', fake_network)
|
self.call_driver.assert_called_once_with('enable', fake_network)
|
||||||
self.assertFalse(self.cache.called)
|
self.assertFalse(self.cache.called)
|
||||||
|
self.assertFalse(self.external_process.called)
|
||||||
|
|
||||||
def test_disable_dhcp_helper_known_network(self):
|
def test_disable_dhcp_helper_known_network(self):
|
||||||
self.cache.get_network_by_id.return_value = fake_network
|
self.cache.get_network_by_id.return_value = fake_network
|
||||||
@ -324,6 +354,14 @@ class TestDhcpAgentEventHandler(unittest.TestCase):
|
|||||||
self.cache.assert_has_calls(
|
self.cache.assert_has_calls(
|
||||||
[mock.call.get_network_by_id(fake_network.id)])
|
[mock.call.get_network_by_id(fake_network.id)])
|
||||||
self.call_driver.assert_called_once_with('disable', fake_network)
|
self.call_driver.assert_called_once_with('disable', fake_network)
|
||||||
|
self.external_process.assert_has_calls([
|
||||||
|
mock.call(
|
||||||
|
cfg.CONF,
|
||||||
|
'12345678-1234-5678-1234567890ab',
|
||||||
|
'sudo',
|
||||||
|
'qdhcp-12345678-1234-5678-1234567890ab'),
|
||||||
|
mock.call().disable()
|
||||||
|
])
|
||||||
|
|
||||||
def test_disable_dhcp_helper_unknown_network(self):
|
def test_disable_dhcp_helper_unknown_network(self):
|
||||||
self.cache.get_network_by_id.return_value = None
|
self.cache.get_network_by_id.return_value = None
|
||||||
@ -331,16 +369,51 @@ class TestDhcpAgentEventHandler(unittest.TestCase):
|
|||||||
self.cache.assert_has_calls(
|
self.cache.assert_has_calls(
|
||||||
[mock.call.get_network_by_id('abcdef')])
|
[mock.call.get_network_by_id('abcdef')])
|
||||||
self.assertEqual(self.call_driver.call_count, 0)
|
self.assertEqual(self.call_driver.call_count, 0)
|
||||||
|
self.assertFalse(self.external_process.called)
|
||||||
|
|
||||||
def test_disable_dhcp_helper_driver_failure(self):
|
def test_disable_dhcp_helper_driver_failure(self):
|
||||||
self.cache.get_network_by_id.return_value = fake_network
|
self.cache.get_network_by_id.return_value = fake_network
|
||||||
|
self.call_driver.return_value = False
|
||||||
self.dhcp.disable_dhcp_helper(fake_network.id)
|
self.dhcp.disable_dhcp_helper(fake_network.id)
|
||||||
self.call_driver.disable.return_value = False
|
|
||||||
self.cache.assert_has_calls(
|
self.cache.assert_has_calls(
|
||||||
[mock.call.get_network_by_id(fake_network.id)])
|
[mock.call.get_network_by_id(fake_network.id)])
|
||||||
self.call_driver.assert_called_once_with('disable', fake_network)
|
self.call_driver.assert_called_once_with('disable', fake_network)
|
||||||
self.cache.assert_has_calls(
|
self.cache.assert_has_calls(
|
||||||
[mock.call.get_network_by_id(fake_network.id)])
|
[mock.call.get_network_by_id(fake_network.id)])
|
||||||
|
self.external_process.assert_has_calls([
|
||||||
|
mock.call(
|
||||||
|
cfg.CONF,
|
||||||
|
'12345678-1234-5678-1234567890ab',
|
||||||
|
'sudo',
|
||||||
|
'qdhcp-12345678-1234-5678-1234567890ab'),
|
||||||
|
mock.call().disable()
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_enable_isolated_metadata_proxy(self):
|
||||||
|
class_path = 'quantum.agent.linux.external_process.ProcessManager'
|
||||||
|
with mock.patch(class_path) as ext_process:
|
||||||
|
self.dhcp.enable_isolated_metadata_proxy(fake_network)
|
||||||
|
ext_process.assert_has_calls([
|
||||||
|
mock.call(
|
||||||
|
cfg.CONF,
|
||||||
|
'12345678-1234-5678-1234567890ab',
|
||||||
|
'sudo',
|
||||||
|
'qdhcp-12345678-1234-5678-1234567890ab'),
|
||||||
|
mock.call().enable(mock.ANY)
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_disable_isolated_metadata_proxy(self):
|
||||||
|
class_path = 'quantum.agent.linux.external_process.ProcessManager'
|
||||||
|
with mock.patch(class_path) as ext_process:
|
||||||
|
self.dhcp.disable_isolated_metadata_proxy(fake_network)
|
||||||
|
ext_process.assert_has_calls([
|
||||||
|
mock.call(
|
||||||
|
cfg.CONF,
|
||||||
|
'12345678-1234-5678-1234567890ab',
|
||||||
|
'sudo',
|
||||||
|
'qdhcp-12345678-1234-5678-1234567890ab'),
|
||||||
|
mock.call().disable()
|
||||||
|
])
|
||||||
|
|
||||||
def test_network_create_end(self):
|
def test_network_create_end(self):
|
||||||
payload = dict(network=dict(id=fake_network.id))
|
payload = dict(network=dict(id=fake_network.id))
|
||||||
@ -668,6 +741,8 @@ class TestDeviceManager(unittest.TestCase):
|
|||||||
cfg.CONF.set_override('interface_driver',
|
cfg.CONF.set_override('interface_driver',
|
||||||
'quantum.agent.linux.interface.NullDriver')
|
'quantum.agent.linux.interface.NullDriver')
|
||||||
config.register_root_helper(cfg.CONF)
|
config.register_root_helper(cfg.CONF)
|
||||||
|
cfg.CONF.set_override('use_namespaces', True)
|
||||||
|
cfg.CONF.set_override('enable_isolated_metadata', True)
|
||||||
|
|
||||||
self.device_exists_p = mock.patch(
|
self.device_exists_p = mock.patch(
|
||||||
'quantum.agent.linux.ip_lib.device_exists')
|
'quantum.agent.linux.ip_lib.device_exists')
|
||||||
@ -683,6 +758,7 @@ class TestDeviceManager(unittest.TestCase):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.dvr_cls_p.stop()
|
self.dvr_cls_p.stop()
|
||||||
self.device_exists_p.stop()
|
self.device_exists_p.stop()
|
||||||
|
cfg.CONF.reset()
|
||||||
|
|
||||||
def _test_setup_helper(self, device_exists, reuse_existing=False):
|
def _test_setup_helper(self, device_exists, reuse_existing=False):
|
||||||
plugin = mock.Mock()
|
plugin = mock.Mock()
|
||||||
@ -691,7 +767,9 @@ class TestDeviceManager(unittest.TestCase):
|
|||||||
self.mock_driver.get_device_name.return_value = 'tap12345678-12'
|
self.mock_driver.get_device_name.return_value = 'tap12345678-12'
|
||||||
|
|
||||||
dh = dhcp_agent.DeviceManager(cfg.CONF, plugin)
|
dh = dhcp_agent.DeviceManager(cfg.CONF, plugin)
|
||||||
dh.setup(fake_network, reuse_existing)
|
interface_name = dh.setup(fake_network, reuse_existing)
|
||||||
|
|
||||||
|
self.assertEqual(interface_name, 'tap12345678-12')
|
||||||
|
|
||||||
plugin.assert_has_calls([
|
plugin.assert_has_calls([
|
||||||
mock.call.get_dhcp_port(fake_network.id, mock.ANY)])
|
mock.call.get_dhcp_port(fake_network.id, mock.ANY)])
|
||||||
@ -699,7 +777,7 @@ class TestDeviceManager(unittest.TestCase):
|
|||||||
namespace = dhcp_agent.NS_PREFIX + fake_network.id
|
namespace = dhcp_agent.NS_PREFIX + fake_network.id
|
||||||
|
|
||||||
expected = [mock.call.init_l3('tap12345678-12',
|
expected = [mock.call.init_l3('tap12345678-12',
|
||||||
['172.9.9.9/24'],
|
['172.9.9.9/24', '169.254.169.254/16'],
|
||||||
namespace=namespace)]
|
namespace=namespace)]
|
||||||
|
|
||||||
if not reuse_existing:
|
if not reuse_existing:
|
||||||
|
@ -202,8 +202,11 @@ class TestBase(unittest.TestCase):
|
|||||||
os.path.join(root, 'etc', 'quantum.conf.test')]
|
os.path.join(root, 'etc', 'quantum.conf.test')]
|
||||||
self.conf = config.setup_conf()
|
self.conf = config.setup_conf()
|
||||||
self.conf.register_opts(dhcp.OPTS)
|
self.conf.register_opts(dhcp.OPTS)
|
||||||
self.conf.register_opt(cfg.StrOpt('dhcp_lease_relay_socket',
|
self.conf.register_opt(
|
||||||
default='$state_path/dhcp/lease_relay'))
|
cfg.StrOpt('dhcp_lease_relay_socket',
|
||||||
|
default='$state_path/dhcp/lease_relay'))
|
||||||
|
self.conf.register_opt(cfg.BoolOpt('enable_isolated_metadata',
|
||||||
|
default=True))
|
||||||
self.conf(args=args)
|
self.conf(args=args)
|
||||||
self.conf.set_override('state_path', '')
|
self.conf.set_override('state_path', '')
|
||||||
self.conf.use_namespaces = True
|
self.conf.use_namespaces = True
|
||||||
@ -511,12 +514,18 @@ tag:tag0,option:router,192.168.0.1""".lstrip()
|
|||||||
self.safe.assert_called_once_with('/foo/opts', expected)
|
self.safe.assert_called_once_with('/foo/opts', expected)
|
||||||
|
|
||||||
def test_output_opts_file_no_gateway(self):
|
def test_output_opts_file_no_gateway(self):
|
||||||
expected = "tag:tag0,option:router"
|
expected = """
|
||||||
|
tag:tag0,option:classless-static-route,169.254.169.254/32,192.168.1.1
|
||||||
|
tag:tag0,option:router""".lstrip()
|
||||||
|
|
||||||
with mock.patch.object(dhcp.Dnsmasq, 'get_conf_file_name') as conf_fn:
|
with mock.patch.object(dhcp.Dnsmasq, 'get_conf_file_name') as conf_fn:
|
||||||
conf_fn.return_value = '/foo/opts'
|
conf_fn.return_value = '/foo/opts'
|
||||||
dm = dhcp.Dnsmasq(self.conf, FakeV4NoGatewayNetwork())
|
dm = dhcp.Dnsmasq(self.conf, FakeV4NoGatewayNetwork())
|
||||||
dm._output_opts_file()
|
with mock.patch.object(dm, '_make_subnet_interface_ip_map') as ipm:
|
||||||
|
ipm.return_value = {FakeV4SubnetNoGateway.id: '192.168.1.1'}
|
||||||
|
|
||||||
|
dm._output_opts_file()
|
||||||
|
self.assertTrue(ipm.called)
|
||||||
|
|
||||||
self.safe.assert_called_once_with('/foo/opts', expected)
|
self.safe.assert_called_once_with('/foo/opts', expected)
|
||||||
|
|
||||||
@ -549,13 +558,33 @@ tag:tag1,option:classless-static-route,%s,%s""".lstrip() % (fake_v6,
|
|||||||
pid.__get__ = mock.Mock(return_value=5)
|
pid.__get__ = mock.Mock(return_value=5)
|
||||||
dm = dhcp.Dnsmasq(self.conf, FakeDualNetwork(),
|
dm = dhcp.Dnsmasq(self.conf, FakeDualNetwork(),
|
||||||
namespace='qdhcp-ns')
|
namespace='qdhcp-ns')
|
||||||
dm.reload_allocations()
|
|
||||||
|
method_name = '_make_subnet_interface_ip_map'
|
||||||
|
with mock.patch.object(dhcp.Dnsmasq, method_name) as ip_map:
|
||||||
|
ip_map.return_value = {}
|
||||||
|
dm.reload_allocations()
|
||||||
|
self.assertTrue(ip_map.called)
|
||||||
|
|
||||||
self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data),
|
self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data),
|
||||||
mock.call(exp_opt_name, exp_opt_data)])
|
mock.call(exp_opt_name, exp_opt_data)])
|
||||||
self.execute.assert_called_once_with(exp_args, root_helper='sudo',
|
self.execute.assert_called_once_with(exp_args, root_helper='sudo',
|
||||||
check_exit_code=True)
|
check_exit_code=True)
|
||||||
|
|
||||||
|
def test_make_subnet_interface_ip_map(self):
|
||||||
|
with mock.patch('quantum.agent.linux.ip_lib.IPDevice') as ip_dev:
|
||||||
|
ip_dev.return_value.addr.list.return_value = [
|
||||||
|
{'cidr': '192.168.0.1/24'}
|
||||||
|
]
|
||||||
|
|
||||||
|
dm = dhcp.Dnsmasq(self.conf,
|
||||||
|
FakeDualNetwork(),
|
||||||
|
namespace='qdhcp-ns')
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
dm._make_subnet_interface_ip_map(),
|
||||||
|
{FakeV4Subnet.id: '192.168.0.1'}
|
||||||
|
)
|
||||||
|
|
||||||
def _test_lease_relay_script_helper(self, action, lease_remaining,
|
def _test_lease_relay_script_helper(self, action, lease_remaining,
|
||||||
path_exists=True):
|
path_exists=True):
|
||||||
relay_path = '/dhcp/relay_socket'
|
relay_path = '/dhcp/relay_socket'
|
||||||
|
Loading…
Reference in New Issue
Block a user