Merge "add non-routed subnet metadata support"
This commit is contained in:
commit
c2886fce30
@ -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(
|
||||||
|
cfg.StrOpt('dhcp_lease_relay_socket',
|
||||||
default='$state_path/dhcp/lease_relay'))
|
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())
|
||||||
|
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()
|
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')
|
||||||
|
|
||||||
|
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()
|
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