vmware-nsx/quantum/tests/unit/test_linux_dhcp.py
Aaron Rosen 314134bdcb Make dhcp agent configurable for namespace
Bug #1035769

Change-Id: I9f0d48a73f059a4985d629c7f0209675f5b01bec
2012-08-12 14:14:07 -07:00

410 lines
15 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 OpenStack LLC
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import tempfile
import unittest2 as unittest
import mock
from quantum.agent.linux import dhcp
from quantum.agent.common import config
from quantum.openstack.common import cfg
class FakeIPAllocation:
def __init__(self, address):
self.ip_address = address
class FakePort1:
id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
admin_state_up = True
fixed_ips = [FakeIPAllocation('192.168.0.2')]
mac_address = '00:00:80:aa:bb:cc'
class FakePort2:
id = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
admin_state_up = False
fixed_ips = [FakeIPAllocation('fdca:3ba5:a17a:4ba3::2')]
mac_address = '00:00:f3:aa:bb:cc'
class FakePort3:
id = '44444444-4444-4444-4444-444444444444'
admin_state_up = True
fixed_ips = [FakeIPAllocation('192.168.0.3'),
FakeIPAllocation('fdca:3ba5:a17a:4ba3::3')]
mac_address = '00:00:0f:aa:bb:cc'
class FakeV4Subnet:
id = 'dddddddd-dddd-dddd-dddd-dddddddddddd'
ip_version = 4
cidr = '192.168.0.0/24'
gateway_ip = '192.168.0.1'
enable_dhcp = True
class FakeV6Subnet:
id = 'ffffffff-ffff-ffff-ffff-ffffffffffff'
ip_version = 6
cidr = 'fdca:3ba5:a17a:4ba3::/64'
gateway_ip = 'fdca:3ba5:a17a:4ba3::1'
enable_dhcp = True
class FakeV4SubnetNoDHCP:
id = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
ip_version = 4
cidr = '192.168.1.0/24'
gateway_ip = '192.168.1.1'
enable_dhcp = False
class FakeV4Network:
id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
subnets = [FakeV4Subnet()]
ports = [FakePort1()]
class FakeV6Network:
id = 'bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb'
subnets = [FakeV6Subnet()]
ports = [FakePort2()]
class FakeDualNetwork:
id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'
subnets = [FakeV4Subnet(), FakeV6Subnet()]
ports = [FakePort1(), FakePort2(), FakePort3()]
class FakeDualNetworkSingleDHCP:
id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'
subnets = [FakeV4Subnet(), FakeV4SubnetNoDHCP()]
ports = [FakePort1(), FakePort2(), FakePort3()]
class TestDhcpBase(unittest.TestCase):
def test_base_abc_error(self):
self.assertRaises(TypeError, dhcp.DhcpBase, None)
def test_replace_file(self):
# make file to replace
with mock.patch('tempfile.NamedTemporaryFile') as ntf:
ntf.return_value.name = '/baz'
with mock.patch('os.chmod') as chmod:
with mock.patch('os.rename') as rename:
dhcp.replace_file('/foo', 'bar')
expected = [mock.call('w+', dir='/', delete=False),
mock.call().write('bar'),
mock.call().close()]
ntf.assert_has_calls(expected)
chmod.assert_called_once_with('/baz', 0644)
rename.assert_called_once_with('/baz', '/foo')
def test_restart(self):
class SubClass(dhcp.DhcpBase):
def __init__(self):
dhcp.DhcpBase.__init__(self, None, None)
self.called = []
def enable(self):
self.called.append('enable')
def disable(self):
self.called.append('disable')
def reload_allocations(self):
pass
@property
def active(self):
return True
c = SubClass()
c.restart()
self.assertEquals(c.called, ['disable', 'enable'])
class LocalChild(dhcp.DhcpLocalProcess):
PORTS = {4: [4], 6: [6]}
def __init__(self, *args, **kwargs):
super(LocalChild, self).__init__(*args, **kwargs)
self.called = []
def reload_allocations(self):
self.called.append('reload')
def spawn_process(self):
self.called.append('spawn')
class TestBase(unittest.TestCase):
def setUp(self):
root = os.path.dirname(os.path.dirname(__file__))
args = ['--config-file',
os.path.join(root, 'etc', 'quantum.conf.test')]
self.conf = config.setup_conf()
self.conf.register_opts(dhcp.OPTS)
self.conf(args=args)
self.conf.set_override('state_path', '')
self.conf.use_namespaces = True
self.replace_p = mock.patch('quantum.agent.linux.dhcp.replace_file')
self.execute_p = mock.patch('quantum.agent.linux.utils.execute')
self.safe = self.replace_p.start()
self.execute = self.execute_p.start()
def tearDown(self):
self.execute_p.stop()
self.replace_p.stop()
class TestDhcpLocalProcess(TestBase):
def test_active(self):
dummy_cmd_line = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
self.execute.return_value = (dummy_cmd_line, '')
with mock.patch.object(LocalChild, 'pid') as pid:
pid.__get__ = mock.Mock(return_value=4)
lp = LocalChild(self.conf, FakeV4Network())
self.assertTrue(lp.active)
self.execute.assert_called_once_with(['cat', '/proc/4/cmdline'],
'sudo')
def test_active_cmd_mismatch(self):
dummy_cmd_line = 'bbbbbbbb-bbbb-bbbb-aaaa-aaaaaaaaaaaa'
self.execute.return_value = (dummy_cmd_line, '')
with mock.patch.object(LocalChild, 'pid') as pid:
pid.__get__ = mock.Mock(return_value=4)
lp = LocalChild(self.conf, FakeV4Network())
self.assertFalse(lp.active)
self.execute.assert_called_once_with(['cat', '/proc/4/cmdline'],
'sudo')
def test_get_conf_file_name(self):
tpl = '/dhcp/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/dev'
with mock.patch('os.path.isdir') as isdir:
isdir.return_value = False
with mock.patch('os.makedirs') as makedirs:
lp = LocalChild(self.conf, FakeV4Network())
self.assertEqual(lp.get_conf_file_name('dev'), tpl)
self.assertFalse(makedirs.called)
def test_get_conf_file_name_ensure_dir(self):
tpl = '/dhcp/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/dev'
with mock.patch('os.path.isdir') as isdir:
isdir.return_value = False
with mock.patch('os.makedirs') as makedirs:
lp = LocalChild(self.conf, FakeV4Network())
self.assertEqual(lp.get_conf_file_name('dev', True), tpl)
self.assertTrue(makedirs.called)
def test_enable_already_active(self):
with mock.patch.object(LocalChild, 'active') as patched:
patched.__get__ = mock.Mock(return_value=True)
lp = LocalChild(self.conf, FakeV4Network())
lp.enable()
self.assertEqual(lp.called, ['reload'])
def test_enable(self):
delegate = mock.Mock(return_value='tap0')
attrs_to_mock = dict(
[(a, mock.DEFAULT) for a in
['active', 'get_conf_file_name']]
)
with mock.patch.multiple(LocalChild, **attrs_to_mock) as mocks:
mocks['active'].__get__ = mock.Mock(return_value=False)
mocks['get_conf_file_name'].return_value = '/dir'
lp = LocalChild(self.conf,
FakeDualNetwork(),
device_delegate=delegate)
lp.enable()
delegate.assert_has_calls(
[mock.call.setup(mock.ANY, reuse_existing=True)])
self.assertEqual(lp.called, ['spawn'])
def test_disable_not_active(self):
attrs_to_mock = dict([(a, mock.DEFAULT) for a in ['active', 'pid']])
with mock.patch.multiple(LocalChild, **attrs_to_mock) as mocks:
mocks['active'].__get__ = mock.Mock(return_value=False)
mocks['pid'].__get__ = mock.Mock(return_value=5)
with mock.patch.object(dhcp.LOG, 'debug') as log:
lp = LocalChild(self.conf, FakeDualNetwork())
lp.disable()
msg = log.call_args[0][0]
self.assertIn('stale', msg)
def test_disable_unknown_network(self):
attrs_to_mock = dict([(a, mock.DEFAULT) for a in ['active', 'pid']])
with mock.patch.multiple(LocalChild, **attrs_to_mock) as mocks:
mocks['active'].__get__ = mock.Mock(return_value=False)
mocks['pid'].__get__ = mock.Mock(return_value=None)
with mock.patch.object(dhcp.LOG, 'debug') as log:
lp = LocalChild(self.conf, FakeDualNetwork())
lp.disable()
msg = log.call_args[0][0]
self.assertIn('No DHCP', msg)
def test_disable(self):
attrs_to_mock = dict([(a, mock.DEFAULT) for a in
['active', 'pid']])
delegate = mock.Mock()
delegate.intreface_name = 'tap0'
network = FakeDualNetwork()
with mock.patch.multiple(LocalChild, **attrs_to_mock) as mocks:
mocks['active'].__get__ = mock.Mock(return_value=True)
mocks['pid'].__get__ = mock.Mock(return_value=5)
lp = LocalChild(self.conf, network, device_delegate=delegate)
lp.disable()
delegate.assert_has_calls([mock.call.destroy(network)])
exp_args = ['ip', 'netns', 'exec',
'cccccccc-cccc-cccc-cccc-cccccccccccc', 'kill', '-9', 5]
self.execute.assert_called_once_with(exp_args, root_helper='sudo')
def test_pid(self):
with mock.patch('__builtin__.open') as mock_open:
mock_open.return_value.__enter__ = lambda s: s
mock_open.return_value.__exit__ = mock.Mock()
mock_open.return_value.read.return_value = '5'
lp = LocalChild(self.conf, FakeDualNetwork())
self.assertEqual(lp.pid, 5)
def test_pid_no_an_int(self):
with mock.patch('__builtin__.open') as mock_open:
mock_open.return_value.__enter__ = lambda s: s
mock_open.return_value.__exit__ = mock.Mock()
mock_open.return_value.read.return_value = 'foo'
lp = LocalChild(self.conf, FakeDualNetwork())
self.assertIsNone(lp.pid)
def test_pid_invalid_file(self):
with mock.patch.object(LocalChild, 'get_conf_file_name') as conf_file:
conf_file.return_value = '.doesnotexist/pid'
lp = LocalChild(self.conf, FakeDualNetwork())
self.assertIsNone(lp.pid)
class TestDnsmasq(TestBase):
def _test_spawn(self, extra_options):
def mock_get_conf_file_name(kind, ensure_conf_dir=False):
return '/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/%s' % kind
expected = [
'ip',
'netns',
'exec',
'cccccccc-cccc-cccc-cccc-cccccccccccc',
'dnsmasq',
'--no-hosts',
'--no-resolv',
'--strict-order',
'--bind-interfaces',
'--interface=tap0',
'--except-interface=lo',
'--domain=openstacklocal',
'--pid-file=/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/pid',
'--dhcp-hostsfile=/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/host',
'--dhcp-optsfile=/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/opts',
'--leasefile-ro',
'--dhcp-range=set:tag0,192.168.0.0,static,120s',
'--dhcp-range=set:tag1,fdca:3ba5:a17a:4ba3::,static,120s'
]
expected.extend(extra_options)
self.execute.return_value = ('', '')
delegate = mock.Mock()
delegate.get_interface_name.return_value = 'tap0'
attrs_to_mock = dict(
[(a, mock.DEFAULT) for a in
['_output_opts_file', 'get_conf_file_name']]
)
with mock.patch.multiple(dhcp.Dnsmasq, **attrs_to_mock) as mocks:
mocks['get_conf_file_name'].side_effect = mock_get_conf_file_name
mocks['_output_opts_file'].return_value = (
'/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/opts'
)
dm = dhcp.Dnsmasq(self.conf, FakeDualNetwork(),
device_delegate=delegate)
dm.spawn_process()
self.assertTrue(mocks['_output_opts_file'].called)
self.execute.assert_called_once_with(expected, root_helper='sudo')
def test_spawn(self):
self._test_spawn([])
def test_spawn_cfg_config_file(self):
self.conf.set_override('dnsmasq_config_file', '/foo')
self._test_spawn(['--conf-file=/foo'])
def test_spawn_cfg_dns_server(self):
self.conf.set_override('dnsmasq_dns_server', '8.8.8.8')
self._test_spawn(['--server=8.8.8.8'])
def test_output_opts_file(self):
expected = 'tag:tag0,option:router,192.168.0.1'
with mock.patch.object(dhcp.Dnsmasq, 'get_conf_file_name') as conf_fn:
conf_fn.return_value = '/foo/opts'
dm = dhcp.Dnsmasq(self.conf, FakeDualNetwork())
dm._output_opts_file()
self.safe.assert_called_once_with('/foo/opts', expected)
def test_output_opts_file_single_dhcp(self):
expected = 'tag:tag0,option:router,192.168.0.1'
with mock.patch.object(dhcp.Dnsmasq, 'get_conf_file_name') as conf_fn:
conf_fn.return_value = '/foo/opts'
dm = dhcp.Dnsmasq(self.conf, FakeDualNetworkSingleDHCP())
dm._output_opts_file()
self.safe.assert_called_once_with('/foo/opts', expected)
def test_reload_allocations(self):
exp_host_name = '/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/host'
exp_host_data = """
00:00:80:aa:bb:cc,192-168-0-2.openstacklocal,192.168.0.2
00:00:f3:aa:bb:cc,fdca-3ba5-a17a-4ba3--2.openstacklocal,fdca:3ba5:a17a:4ba3::2
00:00:0f:aa:bb:cc,192-168-0-3.openstacklocal,192.168.0.3
00:00:0f:aa:bb:cc,fdca-3ba5-a17a-4ba3--3.openstacklocal,fdca:3ba5:a17a:4ba3::3
""".lstrip()
exp_opt_name = '/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/opts'
exp_opt_data = "tag:tag0,option:router,192.168.0.1"
exp_args = ['ip', 'netns', 'exec',
'cccccccc-cccc-cccc-cccc-cccccccccccc', 'kill', '-HUP', 5]
with mock.patch('os.path.isdir') as isdir:
isdir.return_value = True
with mock.patch.object(dhcp.Dnsmasq, 'pid') as pid:
pid.__get__ = mock.Mock(return_value=5)
dm = dhcp.Dnsmasq(self.conf, FakeDualNetwork())
dm.reload_allocations()
self.safe.assert_has_calls([mock.call(exp_host_name, exp_host_data),
mock.call(exp_opt_name, exp_opt_data)])
self.execute.assert_called_once_with(exp_args, root_helper='sudo')