Implementation of bp per-net-dhcp-enable
Change-Id: I81c2e6adb02921e8b80f8181a730b1cba9ffa649
This commit is contained in:
parent
ecaa605e4e
commit
e5307ebaea
@ -104,12 +104,21 @@ class DhcpAgent(object):
|
||||
|
||||
subnets = {}
|
||||
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():
|
||||
if (not subnet.enable_dhcp or
|
||||
not network_admin_up[subnet.network_id]):
|
||||
continue
|
||||
subnet_hashes.add((hash(str(subnet)), subnet.network_id))
|
||||
subnets[subnet.id] = subnet.network_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())
|
||||
|
||||
|
@ -90,12 +90,18 @@ class DhcpBase(object):
|
||||
class DhcpLocalProcess(DhcpBase):
|
||||
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):
|
||||
"""Enables DHCP for this network by spawning a local process."""
|
||||
if self.active:
|
||||
self.reload_allocations()
|
||||
return
|
||||
|
||||
elif self._enable_dhcp():
|
||||
self.device_delegate.setup(self.network, reuse_existing=True)
|
||||
self.spawn_process()
|
||||
|
||||
@ -193,6 +199,9 @@ class Dnsmasq(DhcpLocalProcess):
|
||||
]
|
||||
|
||||
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:
|
||||
mode = 'static'
|
||||
else:
|
||||
@ -213,6 +222,13 @@ class Dnsmasq(DhcpLocalProcess):
|
||||
utils.execute(cmd, self.root_helper)
|
||||
|
||||
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."""
|
||||
self._output_hosts_file()
|
||||
self._output_opts_file()
|
||||
@ -240,9 +256,12 @@ class Dnsmasq(DhcpLocalProcess):
|
||||
# TODO (mark): add support for nameservers
|
||||
options = []
|
||||
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
|
||||
else:
|
||||
#NOTE(xchenum) need to handle no gw case
|
||||
options.append((self._TAG_PREFIX % i,
|
||||
'option',
|
||||
'router',
|
||||
|
@ -228,5 +228,10 @@ RESOURCE_ATTRIBUTE_MAP = {
|
||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
||||
'required_by_policy': 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},
|
||||
}
|
||||
}
|
||||
|
@ -625,7 +625,8 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
'allocation_pools': [{'start': pool['first_ip'],
|
||||
'end': pool['last_ip']}
|
||||
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)
|
||||
|
||||
def _make_port_dict(self, port, fields=None):
|
||||
@ -702,7 +703,8 @@ class QuantumDbPluginV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
network_id=s['network_id'],
|
||||
ip_version=s['ip_version'],
|
||||
cidr=s['cidr'],
|
||||
gateway_ip=s['gateway_ip'])
|
||||
gateway_ip=s['gateway_ip'],
|
||||
enable_dhcp=s['enable_dhcp'])
|
||||
context.session.add(subnet)
|
||||
pools = self._allocate_pools_for_subnet(context, s)
|
||||
for pool in pools:
|
||||
|
@ -112,6 +112,8 @@ class Subnet(model_base.BASEV2, HasId, HasTenant):
|
||||
allocation_pools = orm.relationship(IPAllocationPool,
|
||||
backref='subnet',
|
||||
lazy="dynamic")
|
||||
enable_dhcp = sa.Column(sa.Boolean())
|
||||
|
||||
#TODO(danwent):
|
||||
# - dns_namservers
|
||||
# - additional_routes
|
||||
|
@ -752,7 +752,7 @@ class V2Views(unittest.TestCase):
|
||||
|
||||
def test_subnet(self):
|
||||
keys = ('id', 'network_id', 'tenant_id', 'gateway_ip',
|
||||
'ip_version', 'cidr')
|
||||
'ip_version', 'cidr', 'enable_dhcp')
|
||||
self._view(keys, 'subnets', 'subnet')
|
||||
|
||||
|
||||
|
@ -137,12 +137,13 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
|
||||
return network_req.get_response(self.api)
|
||||
|
||||
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,
|
||||
'network_id': net_id,
|
||||
'cidr': cidr,
|
||||
'ip_version': ip_version,
|
||||
'tenant_id': self._tenant_id}}
|
||||
'tenant_id': self._tenant_id,
|
||||
'enable_dhcp': enable_dhcp}}
|
||||
if gateway_ip:
|
||||
data['subnet']['gateway_ip'] = gateway_ip
|
||||
if allocation_pools:
|
||||
@ -165,14 +166,15 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
|
||||
return port_req.get_response(self.api)
|
||||
|
||||
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,
|
||||
network['network']['tenant_id'],
|
||||
network['network']['id'],
|
||||
gateway,
|
||||
cidr,
|
||||
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
|
||||
# so it can be caught by unit tests
|
||||
if res.status_int >= 400:
|
||||
@ -200,7 +202,8 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
|
||||
cidr='10.0.0.0/24',
|
||||
fmt='json',
|
||||
ip_version=4,
|
||||
allocation_pools=None):
|
||||
allocation_pools=None,
|
||||
enable_dhcp=True):
|
||||
# TODO(anyone) DRY this
|
||||
# NOTE(salvatore-orlando): we can pass the network object
|
||||
# to gen function anyway, and then avoid the repetition
|
||||
@ -211,7 +214,8 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
|
||||
gateway_ip,
|
||||
cidr,
|
||||
allocation_pools,
|
||||
ip_version)
|
||||
ip_version,
|
||||
enable_dhcp)
|
||||
yield subnet
|
||||
self._delete('subnets', subnet['subnet']['id'])
|
||||
else:
|
||||
@ -220,7 +224,8 @@ class QuantumDbPluginV2TestCase(unittest2.TestCase):
|
||||
gateway_ip,
|
||||
cidr,
|
||||
allocation_pools,
|
||||
ip_version)
|
||||
ip_version,
|
||||
enable_dhcp)
|
||||
yield subnet
|
||||
self._delete('subnets', subnet['subnet']['id'])
|
||||
|
||||
@ -876,6 +881,7 @@ class TestSubnetsV2(QuantumDbPluginV2TestCase):
|
||||
keys = kwargs.copy()
|
||||
keys.setdefault('cidr', '10.0.0.0/24')
|
||||
keys.setdefault('ip_version', 4)
|
||||
keys.setdefault('enable_dhcp', True)
|
||||
with self.subnet(network=network, **keys) as subnet:
|
||||
# verify the response has each key with the correct value
|
||||
for k in keys:
|
||||
@ -974,10 +980,12 @@ class TestSubnetsV2(QuantumDbPluginV2TestCase):
|
||||
cidr = '10.0.0.0/24'
|
||||
allocation_pools = [{'start': '10.0.0.2',
|
||||
'end': '10.0.0.254'}]
|
||||
enable_dhcp = True
|
||||
subnet = self._test_create_subnet()
|
||||
# verify cidr & gw have been correctly generated
|
||||
self.assertEquals(subnet['subnet']['cidr'], cidr)
|
||||
self.assertEquals(subnet['subnet']['gateway_ip'], gateway)
|
||||
self.assertEquals(subnet['subnet']['enable_dhcp'], enable_dhcp)
|
||||
self.assertEquals(subnet['subnet']['allocation_pools'],
|
||||
allocation_pools)
|
||||
|
||||
@ -1021,6 +1029,10 @@ class TestSubnetsV2(QuantumDbPluginV2TestCase):
|
||||
cidr=cidr,
|
||||
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):
|
||||
gateway_ip = '10.0.0.50'
|
||||
cidr = '10.0.0.0/24'
|
||||
|
@ -70,6 +70,7 @@ class TestDhcpAgent(unittest.TestCase):
|
||||
self.dhcp.daemon_loop()
|
||||
|
||||
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, 'call_driver') as call_driver:
|
||||
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('disable', 'deleted_net')])
|
||||
|
||||
def test_state_builder(self):
|
||||
fake_subnet = [
|
||||
FakeModel(1, network_id=1),
|
||||
FakeModel(2, network_id=2),
|
||||
]
|
||||
def test_state_builder_network_admin_down(self):
|
||||
fake_network1 = FakeModel(1, admin_state_up=True)
|
||||
fake_network2 = FakeModel(2, admin_state_up=False)
|
||||
|
||||
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 = [
|
||||
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.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)
|
||||
self.dhcp.db = db
|
||||
state = self.dhcp._state_builder()
|
||||
@ -103,8 +161,95 @@ class TestDhcpAgent(unittest.TestCase):
|
||||
self.assertEquals(state.networks, set([1, 2]))
|
||||
|
||||
expected_subnets = set([
|
||||
(hash(str(fake_subnet[0])), 1),
|
||||
(hash(str(fake_subnet[1])), 2)
|
||||
(hash(str(fake_subnets[0])), 1),
|
||||
(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)
|
||||
|
||||
@ -120,7 +265,8 @@ class TestDhcpAgent(unittest.TestCase):
|
||||
return self.dhcp.get_network_state_delta()
|
||||
|
||||
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)
|
||||
self.assertEqual(delta,
|
||||
|
@ -58,6 +58,7 @@ class FakeV4Subnet:
|
||||
ip_version = 4
|
||||
cidr = '192.168.0.0/24'
|
||||
gateway_ip = '192.168.0.1'
|
||||
enable_dhcp = True
|
||||
|
||||
|
||||
class FakeV6Subnet:
|
||||
@ -65,6 +66,15 @@ class FakeV6Subnet:
|
||||
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:
|
||||
@ -85,6 +95,12 @@ class FakeDualNetwork:
|
||||
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)
|
||||
@ -353,6 +369,15 @@ class TestDnsmasq(TestBase):
|
||||
|
||||
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 = """
|
||||
|
Loading…
Reference in New Issue
Block a user