Implementation of bp per-net-dhcp-enable

Change-Id: I81c2e6adb02921e8b80f8181a730b1cba9ffa649
This commit is contained in:
xchenum 2012-07-25 20:58:52 -04:00
parent ecaa605e4e
commit e5307ebaea
9 changed files with 246 additions and 26 deletions

View File

@ -104,12 +104,21 @@ class DhcpAgent(object):
subnets = {} subnets = {}
subnet_hashes = set() subnet_hashes = set()
network_admin_up = {}
for network in self.db.networks.all():
network_admin_up[network.id] = network.admin_state_up
for subnet in self.db.subnets.all(): for subnet in self.db.subnets.all():
if (not subnet.enable_dhcp or
not network_admin_up[subnet.network_id]):
continue
subnet_hashes.add((hash(str(subnet)), subnet.network_id)) subnet_hashes.add((hash(str(subnet)), subnet.network_id))
subnets[subnet.id] = subnet.network_id subnets[subnet.id] = subnet.network_id
ipalloc_hashes = set([(hash(str(a)), subnets[a.subnet_id]) ipalloc_hashes = set([(hash(str(a)), subnets[a.subnet_id])
for a in self.db.ipallocations.all()]) for a in self.db.ipallocations.all()
if a.subnet_id in subnets])
networks = set(subnets.itervalues()) networks = set(subnets.itervalues())

View File

@ -90,14 +90,20 @@ class DhcpBase(object):
class DhcpLocalProcess(DhcpBase): class DhcpLocalProcess(DhcpBase):
PORTS = [] PORTS = []
def _enable_dhcp(self):
"""check if there is a subnet within the network with dhcp enabled."""
for subnet in self.network.subnets:
if subnet.enable_dhcp:
return True
return False
def enable(self): def enable(self):
"""Enables DHCP for this network by spawning a local process.""" """Enables DHCP for this network by spawning a local process."""
if self.active: if self.active:
self.reload_allocations() self.reload_allocations()
return elif self._enable_dhcp():
self.device_delegate.setup(self.network, reuse_existing=True)
self.device_delegate.setup(self.network, reuse_existing=True) self.spawn_process()
self.spawn_process()
def disable(self): def disable(self):
"""Disable DHCP for this network by killing the local process.""" """Disable DHCP for this network by killing the local process."""
@ -193,6 +199,9 @@ class Dnsmasq(DhcpLocalProcess):
] ]
for i, subnet in enumerate(self.network.subnets): for i, subnet in enumerate(self.network.subnets):
# if a subnet is specified to have dhcp disabled
if not subnet.enable_dhcp:
continue
if subnet.ip_version == 4: if subnet.ip_version == 4:
mode = 'static' mode = 'static'
else: else:
@ -213,6 +222,13 @@ 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."""
if not self._enable_dhcp():
self.disable()
LOG.debug(_('Killing dhcpmasq for network since all subnets have \
turned off DHCP: %s') % self.network.id)
return
"""Rebuilds the dnsmasq config and signal the dnsmasq to reload.""" """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()
@ -240,9 +256,12 @@ class Dnsmasq(DhcpLocalProcess):
# TODO (mark): add support for nameservers # TODO (mark): add support for nameservers
options = [] options = []
for i, subnet in enumerate(self.network.subnets): for i, subnet in enumerate(self.network.subnets):
if subnet.ip_version == 6: if not subnet.enable_dhcp:
continue
elif subnet.ip_version == 6:
continue continue
else: else:
#NOTE(xchenum) need to handle no gw case
options.append((self._TAG_PREFIX % i, options.append((self._TAG_PREFIX % i,
'option', 'option',
'router', 'router',

View File

@ -228,5 +228,10 @@ RESOURCE_ATTRIBUTE_MAP = {
'tenant_id': {'allow_post': True, 'allow_put': False, 'tenant_id': {'allow_post': True, 'allow_put': False,
'required_by_policy': True, 'required_by_policy': True,
'is_visible': True}, 'is_visible': True},
'enable_dhcp': {'allow_post': True, 'allow_put': True,
'default': True,
'convert_to': convert_to_boolean,
'validate': {'type:boolean': None},
'is_visible': True},
} }
} }

View File

@ -625,7 +625,8 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
'allocation_pools': [{'start': pool['first_ip'], 'allocation_pools': [{'start': pool['first_ip'],
'end': pool['last_ip']} 'end': pool['last_ip']}
for pool in subnet['allocation_pools']], for pool in subnet['allocation_pools']],
'gateway_ip': subnet['gateway_ip']} 'gateway_ip': subnet['gateway_ip'],
'enable_dhcp': subnet['enable_dhcp']}
return self._fields(res, fields) return self._fields(res, fields)
def _make_port_dict(self, port, fields=None): def _make_port_dict(self, port, fields=None):
@ -702,7 +703,8 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
network_id=s['network_id'], network_id=s['network_id'],
ip_version=s['ip_version'], ip_version=s['ip_version'],
cidr=s['cidr'], cidr=s['cidr'],
gateway_ip=s['gateway_ip']) gateway_ip=s['gateway_ip'],
enable_dhcp=s['enable_dhcp'])
context.session.add(subnet) context.session.add(subnet)
pools = self._allocate_pools_for_subnet(context, s) pools = self._allocate_pools_for_subnet(context, s)
for pool in pools: for pool in pools:

View File

@ -112,6 +112,8 @@ class Subnet(model_base.BASEV2, HasId, HasTenant):
allocation_pools = orm.relationship(IPAllocationPool, allocation_pools = orm.relationship(IPAllocationPool,
backref='subnet', backref='subnet',
lazy="dynamic") lazy="dynamic")
enable_dhcp = sa.Column(sa.Boolean())
#TODO(danwent): #TODO(danwent):
# - dns_namservers # - dns_namservers
# - additional_routes # - additional_routes

View File

@ -752,7 +752,7 @@ class V2Views(unittest.TestCase):
def test_subnet(self): def test_subnet(self):
keys = ('id', 'network_id', 'tenant_id', 'gateway_ip', keys = ('id', 'network_id', 'tenant_id', 'gateway_ip',
'ip_version', 'cidr') 'ip_version', 'cidr', 'enable_dhcp')
self._view(keys, 'subnets', 'subnet') self._view(keys, 'subnets', 'subnet')

View File

@ -137,12 +137,13 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
return network_req.get_response(self.api) return network_req.get_response(self.api)
def _create_subnet(self, fmt, tenant_id, net_id, gateway_ip, cidr, def _create_subnet(self, fmt, tenant_id, net_id, gateway_ip, cidr,
allocation_pools=None, ip_version=4): allocation_pools=None, ip_version=4, enable_dhcp=True):
data = {'subnet': {'tenant_id': tenant_id, data = {'subnet': {'tenant_id': tenant_id,
'network_id': net_id, 'network_id': net_id,
'cidr': cidr, 'cidr': cidr,
'ip_version': ip_version, 'ip_version': ip_version,
'tenant_id': self._tenant_id}} 'tenant_id': self._tenant_id,
'enable_dhcp': enable_dhcp}}
if gateway_ip: if gateway_ip:
data['subnet']['gateway_ip'] = gateway_ip data['subnet']['gateway_ip'] = gateway_ip
if allocation_pools: if allocation_pools:
@ -165,14 +166,15 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
return port_req.get_response(self.api) return port_req.get_response(self.api)
def _make_subnet(self, fmt, network, gateway, cidr, def _make_subnet(self, fmt, network, gateway, cidr,
allocation_pools=None, ip_version=4): allocation_pools=None, ip_version=4, enable_dhcp=True):
res = self._create_subnet(fmt, res = self._create_subnet(fmt,
network['network']['tenant_id'], network['network']['tenant_id'],
network['network']['id'], network['network']['id'],
gateway, gateway,
cidr, cidr,
allocation_pools=allocation_pools, allocation_pools=allocation_pools,
ip_version=ip_version) ip_version=ip_version,
enable_dhcp=enable_dhcp)
# Things can go wrong - raise HTTP exc with res code only # Things can go wrong - raise HTTP exc with res code only
# so it can be caught by unit tests # so it can be caught by unit tests
if res.status_int >= 400: if res.status_int >= 400:
@ -200,7 +202,8 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
cidr='10.0.0.0/24', cidr='10.0.0.0/24',
fmt='json', fmt='json',
ip_version=4, ip_version=4,
allocation_pools=None): allocation_pools=None,
enable_dhcp=True):
# TODO(anyone) DRY this # TODO(anyone) DRY this
# NOTE(salvatore-orlando): we can pass the network object # NOTE(salvatore-orlando): we can pass the network object
# to gen function anyway, and then avoid the repetition # to gen function anyway, and then avoid the repetition
@ -211,7 +214,8 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
gateway_ip, gateway_ip,
cidr, cidr,
allocation_pools, allocation_pools,
ip_version) ip_version,
enable_dhcp)
yield subnet yield subnet
self._delete('subnets', subnet['subnet']['id']) self._delete('subnets', subnet['subnet']['id'])
else: else:
@ -220,7 +224,8 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
gateway_ip, gateway_ip,
cidr, cidr,
allocation_pools, allocation_pools,
ip_version) ip_version,
enable_dhcp)
yield subnet yield subnet
self._delete('subnets', subnet['subnet']['id']) self._delete('subnets', subnet['subnet']['id'])
@ -876,6 +881,7 @@ class TestSubnetsV2(QuantumDbPluginV2TestCase):
keys = kwargs.copy() keys = kwargs.copy()
keys.setdefault('cidr', '10.0.0.0/24') keys.setdefault('cidr', '10.0.0.0/24')
keys.setdefault('ip_version', 4) keys.setdefault('ip_version', 4)
keys.setdefault('enable_dhcp', True)
with self.subnet(network=network, **keys) as subnet: with self.subnet(network=network, **keys) as subnet:
# verify the response has each key with the correct value # verify the response has each key with the correct value
for k in keys: for k in keys:
@ -974,10 +980,12 @@ class TestSubnetsV2(QuantumDbPluginV2TestCase):
cidr = '10.0.0.0/24' cidr = '10.0.0.0/24'
allocation_pools = [{'start': '10.0.0.2', allocation_pools = [{'start': '10.0.0.2',
'end': '10.0.0.254'}] 'end': '10.0.0.254'}]
enable_dhcp = True
subnet = self._test_create_subnet() subnet = self._test_create_subnet()
# verify cidr & gw have been correctly generated # verify cidr & gw have been correctly generated
self.assertEquals(subnet['subnet']['cidr'], cidr) self.assertEquals(subnet['subnet']['cidr'], cidr)
self.assertEquals(subnet['subnet']['gateway_ip'], gateway) self.assertEquals(subnet['subnet']['gateway_ip'], gateway)
self.assertEquals(subnet['subnet']['enable_dhcp'], enable_dhcp)
self.assertEquals(subnet['subnet']['allocation_pools'], self.assertEquals(subnet['subnet']['allocation_pools'],
allocation_pools) allocation_pools)
@ -1021,6 +1029,10 @@ class TestSubnetsV2(QuantumDbPluginV2TestCase):
cidr=cidr, cidr=cidr,
allocation_pools=allocation_pools) allocation_pools=allocation_pools)
def test_create_subnet_with_dhcp_disabled(self):
enable_dhcp = False
self._test_create_subnet(enable_dhcp=enable_dhcp)
def test_create_subnet_gateway_in_allocation_pool_returns_409(self): def test_create_subnet_gateway_in_allocation_pool_returns_409(self):
gateway_ip = '10.0.0.50' gateway_ip = '10.0.0.50'
cidr = '10.0.0.0/24' cidr = '10.0.0.0/24'

View File

@ -70,6 +70,7 @@ class TestDhcpAgent(unittest.TestCase):
self.dhcp.daemon_loop() self.dhcp.daemon_loop()
def test_daemon_loop_completes_single_pass(self): def test_daemon_loop_completes_single_pass(self):
self.dhcp._network_dhcp_enable = mock.Mock(return_value=True)
with mock.patch.object(self.dhcp, 'get_network_state_delta') as state: with mock.patch.object(self.dhcp, 'get_network_state_delta') as state:
with mock.patch.object(self.dhcp, 'call_driver') as call_driver: with mock.patch.object(self.dhcp, 'call_driver') as call_driver:
with mock.patch('quantum.agent.dhcp_agent.time') as time: with mock.patch('quantum.agent.dhcp_agent.time') as time:
@ -84,18 +85,75 @@ class TestDhcpAgent(unittest.TestCase):
mock.call('reload_allocations', 'updated_net'), mock.call('reload_allocations', 'updated_net'),
mock.call('disable', 'deleted_net')]) mock.call('disable', 'deleted_net')])
def test_state_builder(self): def test_state_builder_network_admin_down(self):
fake_subnet = [ fake_network1 = FakeModel(1, admin_state_up=True)
FakeModel(1, network_id=1), fake_network2 = FakeModel(2, admin_state_up=False)
FakeModel(2, network_id=2),
] fake_subnet1 = FakeModel(1, network_id=1, enable_dhcp=True)
fake_subnet2 = FakeModel(2, network_id=2, enable_dhcp=True)
fake_subnet3 = FakeModel(3, network_id=2, enable_dhcp=True)
fake_network1.subnets = [fake_subnet1]
fake_network2.subnets = [fake_subnet2, fake_subnet3]
fake_subnet1.network = fake_network1
fake_subnet2.network = fake_network2
fake_subnet3.network = fake_network2
fake_allocation = [ fake_allocation = [
FakeModel(2, subnet_id=1) FakeModel(2, subnet_id=1),
FakeModel(3, subnet_id=2)
] ]
fake_subnets = [fake_subnet1, fake_subnet2, fake_subnet3]
fake_networks = [fake_network1, fake_network2]
db = mock.Mock() db = mock.Mock()
db.subnets.all = mock.Mock(return_value=fake_subnet) db.subnets.all = mock.Mock(return_value=fake_subnets)
db.networks.all = mock.Mock(return_value=fake_networks)
db.ipallocations.all = mock.Mock(return_value=fake_allocation)
self.dhcp.db = db
state = self.dhcp._state_builder()
self.assertEquals(state.networks, set([1]))
expected_subnets = set([
(hash(str(fake_subnets[0])), 1),
])
self.assertEquals(state.subnet_hashes, expected_subnets)
expected_ipalloc = set([
(hash(str(fake_allocation[0])), 1),
])
self.assertEquals(state.ipalloc_hashes, expected_ipalloc)
def test_state_builder_network_dhcp_partial_disable(self):
fake_network1 = FakeModel(1, admin_state_up=True)
fake_network2 = FakeModel(2, admin_state_up=True)
fake_subnet1 = FakeModel(1, network_id=1, enable_dhcp=True)
fake_subnet2 = FakeModel(2, network_id=2, enable_dhcp=False)
fake_subnet3 = FakeModel(3, network_id=2, enable_dhcp=True)
fake_network1.subnets = [fake_subnet1]
fake_network2.subnets = [fake_subnet2, fake_subnet3]
fake_subnet1.network = fake_network1
fake_subnet2.network = fake_network2
fake_subnet3.network = fake_network2
fake_allocation = [
FakeModel(2, subnet_id=1),
FakeModel(3, subnet_id=2),
FakeModel(4, subnet_id=3),
]
fake_subnets = [fake_subnet1, fake_subnet2, fake_subnet3]
fake_networks = [fake_network1, fake_network2]
db = mock.Mock()
db.subnets.all = mock.Mock(return_value=fake_subnets)
db.networks.all = mock.Mock(return_value=fake_networks)
db.ipallocations.all = mock.Mock(return_value=fake_allocation) db.ipallocations.all = mock.Mock(return_value=fake_allocation)
self.dhcp.db = db self.dhcp.db = db
state = self.dhcp._state_builder() state = self.dhcp._state_builder()
@ -103,8 +161,95 @@ class TestDhcpAgent(unittest.TestCase):
self.assertEquals(state.networks, set([1, 2])) self.assertEquals(state.networks, set([1, 2]))
expected_subnets = set([ expected_subnets = set([
(hash(str(fake_subnet[0])), 1), (hash(str(fake_subnets[0])), 1),
(hash(str(fake_subnet[1])), 2) (hash(str(fake_subnets[2])), 2),
])
self.assertEquals(state.subnet_hashes, expected_subnets)
expected_ipalloc = set([
(hash(str(fake_allocation[0])), 1),
(hash(str(fake_allocation[2])), 2),
])
self.assertEquals(state.ipalloc_hashes, expected_ipalloc)
def test_state_builder_network_dhcp_all_disable(self):
fake_network1 = FakeModel(1, admin_state_up=True)
fake_network2 = FakeModel(2, admin_state_up=True)
fake_subnet1 = FakeModel(1, network_id=1, enable_dhcp=True)
fake_subnet2 = FakeModel(2, network_id=2, enable_dhcp=False)
fake_subnet3 = FakeModel(3, network_id=2, enable_dhcp=False)
fake_network1.subnets = [fake_subnet1]
fake_network2.subnets = [fake_subnet2, fake_subnet3]
fake_subnet1.network = fake_network1
fake_subnet2.network = fake_network2
fake_subnet3.network = fake_network2
fake_allocation = [
FakeModel(2, subnet_id=1),
FakeModel(3, subnet_id=2),
FakeModel(4, subnet_id=3),
]
fake_subnets = [fake_subnet1, fake_subnet2, fake_subnet3]
fake_networks = [fake_network1, fake_network2]
db = mock.Mock()
db.subnets.all = mock.Mock(return_value=fake_subnets)
db.networks.all = mock.Mock(return_value=fake_networks)
db.ipallocations.all = mock.Mock(return_value=fake_allocation)
self.dhcp.db = db
state = self.dhcp._state_builder()
self.assertEquals(state.networks, set([1]))
expected_subnets = set([
(hash(str(fake_subnets[0])), 1)
])
self.assertEquals(state.subnet_hashes, expected_subnets)
expected_ipalloc = set([
(hash(str(fake_allocation[0])), 1)
])
self.assertEquals(state.ipalloc_hashes, expected_ipalloc)
def test_state_builder_mixed(self):
fake_network1 = FakeModel(1, admin_state_up=True)
fake_network2 = FakeModel(2, admin_state_up=True)
fake_network3 = FakeModel(3, admin_state_up=False)
fake_subnet1 = FakeModel(1, network_id=1, enable_dhcp=True)
fake_subnet2 = FakeModel(2, network_id=2, enable_dhcp=False)
fake_subnet3 = FakeModel(3, network_id=3, enable_dhcp=True)
fake_network1.subnets = [fake_subnet1]
fake_network2.subnets = [fake_subnet2]
fake_network3.subnets = [fake_subnet3]
fake_subnet1.network = fake_network1
fake_subnet2.network = fake_network2
fake_subnet3.network = fake_network3
fake_allocation = [
FakeModel(2, subnet_id=1)
]
fake_subnets = [fake_subnet1, fake_subnet2, fake_subnet3]
fake_networks = [fake_network1, fake_network2, fake_network3]
db = mock.Mock()
db.subnets.all = mock.Mock(return_value=fake_subnets)
db.networks.all = mock.Mock(return_value=fake_networks)
db.ipallocations.all = mock.Mock(return_value=fake_allocation)
self.dhcp.db = db
state = self.dhcp._state_builder()
self.assertEquals(state.networks, set([1]))
expected_subnets = set([
(hash(str(fake_subnets[0])), 1),
]) ])
self.assertEquals(state.subnet_hashes, expected_subnets) self.assertEquals(state.subnet_hashes, expected_subnets)
@ -120,7 +265,8 @@ class TestDhcpAgent(unittest.TestCase):
return self.dhcp.get_network_state_delta() return self.dhcp.get_network_state_delta()
def test_get_network_state_fresh(self): def test_get_network_state_fresh(self):
new_state = dhcp_agent.State(set([1]), set([(3, 1)]), set([(11, 1)])) new_state = dhcp_agent.State(set([1]), set([(3, 1)]),
set([(11, 1)]))
delta = self._network_state_helper(self.dhcp.prev_state, new_state) delta = self._network_state_helper(self.dhcp.prev_state, new_state)
self.assertEqual(delta, self.assertEqual(delta,

View File

@ -58,6 +58,7 @@ class FakeV4Subnet:
ip_version = 4 ip_version = 4
cidr = '192.168.0.0/24' cidr = '192.168.0.0/24'
gateway_ip = '192.168.0.1' gateway_ip = '192.168.0.1'
enable_dhcp = True
class FakeV6Subnet: class FakeV6Subnet:
@ -65,6 +66,15 @@ class FakeV6Subnet:
ip_version = 6 ip_version = 6
cidr = 'fdca:3ba5:a17a:4ba3::/64' cidr = 'fdca:3ba5:a17a:4ba3::/64'
gateway_ip = 'fdca:3ba5:a17a:4ba3::1' 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: class FakeV4Network:
@ -85,6 +95,12 @@ class FakeDualNetwork:
ports = [FakePort1(), FakePort2(), FakePort3()] ports = [FakePort1(), FakePort2(), FakePort3()]
class FakeDualNetworkSingleDHCP:
id = 'cccccccc-cccc-cccc-cccc-cccccccccccc'
subnets = [FakeV4Subnet(), FakeV4SubnetNoDHCP()]
ports = [FakePort1(), FakePort2(), FakePort3()]
class TestDhcpBase(unittest.TestCase): class TestDhcpBase(unittest.TestCase):
def test_base_abc_error(self): def test_base_abc_error(self):
self.assertRaises(TypeError, dhcp.DhcpBase, None) self.assertRaises(TypeError, dhcp.DhcpBase, None)
@ -353,6 +369,15 @@ class TestDnsmasq(TestBase):
self.safe.assert_called_once_with('/foo/opts', expected) 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): def test_reload_allocations(self):
exp_host_name = '/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/host' exp_host_name = '/dhcp/cccccccc-cccc-cccc-cccc-cccccccccccc/host'
exp_host_data = """ exp_host_data = """