]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Implementation of bp per-net-dhcp-enable
authorxchenum <xchenum@gmail.com>
Thu, 26 Jul 2012 00:58:52 +0000 (20:58 -0400)
committerxchenum <xchenum@gmail.com>
Tue, 7 Aug 2012 17:26:34 +0000 (13:26 -0400)
Change-Id: I81c2e6adb02921e8b80f8181a730b1cba9ffa649

quantum/agent/dhcp_agent.py
quantum/agent/linux/dhcp.py
quantum/api/v2/attributes.py
quantum/db/db_base_plugin_v2.py
quantum/db/models_v2.py
quantum/tests/unit/test_api_v2.py
quantum/tests/unit/test_db_plugin.py
quantum/tests/unit/test_dhcp_agent.py
quantum/tests/unit/test_linux_dhcp.py

index 3e4c4ed564adc2926f7ddefeb215f27e8e4ff40e..8ab0c925404d791fe191057e1a2007bd0456e9f7 100644 (file)
@@ -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())
 
index ba56563704655c8c98be09e18798554531cc97fe..5ed015867b9d4d2fa06c982054cb418aa8bc000a 100644 (file)
@@ -90,14 +90,20 @@ 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
-
-        self.device_delegate.setup(self.network, reuse_existing=True)
-        self.spawn_process()
+        elif self._enable_dhcp():
+            self.device_delegate.setup(self.network, reuse_existing=True)
+            self.spawn_process()
 
     def disable(self):
         """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):
+            # 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',
index 05fe2925038d207168bac7f6f9f42847e253dd8c..980231879d1cffe4e543d83551ba161b412cc4e6 100644 (file)
@@ -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},
     }
 }
index 7b4d10e4cf07df0f983a30a620782fcea6628b2e..d07e5d0d0a690b2ce00a8940327f3d913a2a035c 100644 (file)
@@ -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:
index ec7a55b8e76a418ec4de092efabe578ba808aa7a..a076a956f41329ad129be434418fff32a81da5d5 100644 (file)
@@ -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
index 0f6105eeba7ab58a76e24daae793fe39462bbf9d..5aae422df10e9880d4b4b50467e5fe6e38c80c9e 100644 (file)
@@ -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')
 
 
index 45f9fc243a32576b221c9ef768efd313bbf9ab7e..35e46f42dd96b941d0c4ff5e18d2cf6ab61a25aa 100644 (file)
@@ -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'
index cac9c6510867b40ab49ae848c43aa951270d0553..0eeb7eb75cc5cf44802bbe768f9803aec68ea9b2 100644 (file)
@@ -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(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_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(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_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()
@@ -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,
index 37b0dbb06ed9a9f7387b1331f0a38eee5cbc506f..e20e3a392cfb2db254b1f7d9d7a70544d7c53faa 100644 (file)
@@ -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 = """