]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Improve test coverage of dhcp agent scheduling
authorNuman Siddique <numan.siddique@enovance.com>
Thu, 11 Sep 2014 14:55:20 +0000 (20:25 +0530)
committerNuman Siddique <numan.siddique@enovance.com>
Tue, 20 Jan 2015 09:54:28 +0000 (15:24 +0530)
This patch adds few unit test cases and functional test cases
for schedule() and auto_schedule_networks() of DHCP agent
ChanceScheduler.

Change-Id: I6ed7c48bcf5fc43d805c7898c7d6f019b6792e18
Closes-bug: #1331516

neutron/tests/functional/scheduler/__init__.py [new file with mode: 0644]
neutron/tests/functional/scheduler/test_dhcp_agent_scheduler.py [new file with mode: 0644]
neutron/tests/unit/test_dhcp_scheduler.py

diff --git a/neutron/tests/functional/scheduler/__init__.py b/neutron/tests/functional/scheduler/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/neutron/tests/functional/scheduler/test_dhcp_agent_scheduler.py b/neutron/tests/functional/scheduler/test_dhcp_agent_scheduler.py
new file mode 100644 (file)
index 0000000..7322aff
--- /dev/null
@@ -0,0 +1,347 @@
+# Copyright (c) 2015 Red Hat, Inc.
+# 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 six
+import testscenarios
+
+from neutron import context
+from neutron.db import agents_db
+from neutron.db import agentschedulers_db
+from neutron.db import common_db_mixin
+from neutron.scheduler import dhcp_agent_scheduler
+from neutron.tests.unit import test_dhcp_scheduler as test_dhcp_sch
+
+# Required to generate tests from scenarios. Not compatible with nose.
+load_tests = testscenarios.load_tests_apply_scenarios
+
+
+class TestScheduleNetwork(test_dhcp_sch.TestDhcpSchedulerBaseTestCase,
+                          agentschedulers_db.DhcpAgentSchedulerDbMixin,
+                          agents_db.AgentDbMixin,
+                          common_db_mixin.CommonDbMixin):
+    """Test various scenarios for ChanceScheduler.schedule.
+
+        agent_count
+            Number of dhcp agents (also number of hosts).
+
+        max_agents_per_network
+            Maximum  DHCP Agents that can be scheduled for a network.
+
+        scheduled_agent_count
+            Number of agents the network has previously scheduled
+
+        down_agent_count
+            Number of dhcp agents which are down
+
+        expected_scheduled_agent_count
+            Number of scheduled agents the schedule() should return
+            or 'None' if the schedule() cannot schedule the network.
+    """
+
+    scenarios = [
+        ('No agents scheduled if no agents are present',
+         dict(agent_count=0,
+              max_agents_per_network=1,
+              scheduled_agent_count=0,
+              down_agent_count=0,
+              expected_scheduled_agent_count=None)),
+
+        ('No agents scheduled if network already hosted and'
+         ' max_agents_per_network reached',
+         dict(agent_count=1,
+              max_agents_per_network=1,
+              scheduled_agent_count=1,
+              down_agent_count=0,
+              expected_scheduled_agent_count=None)),
+
+        ('No agents scheduled if all agents are down',
+         dict(agent_count=2,
+              max_agents_per_network=1,
+              scheduled_agent_count=0,
+              down_agent_count=2,
+              expected_scheduled_agent_count=None)),
+
+        ('Agent scheduled to the network if network is not yet hosted',
+         dict(agent_count=1,
+              max_agents_per_network=1,
+              scheduled_agent_count=0,
+              down_agent_count=0,
+              expected_scheduled_agent_count=1)),
+
+        ('Additional Agents scheduled to the network if max_agents_per_network'
+         ' is not yet reached',
+         dict(agent_count=3,
+              max_agents_per_network=3,
+              scheduled_agent_count=1,
+              down_agent_count=0,
+              expected_scheduled_agent_count=2)),
+
+        ('No agent scheduled if agent is dead',
+         dict(agent_count=3,
+              max_agents_per_network=3,
+              scheduled_agent_count=1,
+              down_agent_count=1,
+              expected_scheduled_agent_count=1)),
+    ]
+
+    def test_schedule_network(self):
+        self.config(dhcp_agents_per_network=self.max_agents_per_network)
+        scheduler = dhcp_agent_scheduler.ChanceScheduler()
+
+        # create dhcp agents
+        hosts = ['host-%s' % i for i in range(self.agent_count)]
+        dhcp_agents = self._create_and_set_agents_down(
+            hosts, down_agent_count=self.down_agent_count)
+
+        active_agents = dhcp_agents[self.down_agent_count:]
+
+        # schedule some agents before calling schedule
+        if self.scheduled_agent_count:
+            # schedule the network
+            schedule_agents = active_agents[:self.scheduled_agent_count]
+            scheduler._schedule_bind_network(self.ctx, schedule_agents,
+                                             self.network_id)
+
+        actual_scheduled_agents = scheduler.schedule(self, self.ctx,
+                                                     self.network)
+        if self.expected_scheduled_agent_count:
+            self.assertEqual(self.expected_scheduled_agent_count,
+                             len(actual_scheduled_agents))
+            hosted_agents = self.list_dhcp_agents_hosting_network(
+                self.ctx, self.network_id)
+            self.assertEqual(self.scheduled_agent_count +
+                             len(actual_scheduled_agents),
+                             len(hosted_agents['agents']))
+        else:
+            self.assertIsNone(actual_scheduled_agents)
+
+
+class TestAutoSchedule(test_dhcp_sch.TestDhcpSchedulerBaseTestCase,
+                       agentschedulers_db.DhcpAgentSchedulerDbMixin,
+                       agents_db.AgentDbMixin,
+                       common_db_mixin.CommonDbMixin):
+    """Test various scenarios for ChanceScheduler.auto_schedule_networks.
+
+        Below is the brief description of the scenario variables
+        --------------------------------------------------------
+        agent_count
+            number of DHCP agents (also number of hosts).
+
+        max_agents_per_network
+            Maximum  DHCP Agents that can be scheduled for a network.
+
+        network_count
+            Number of networks.
+
+        networks_with_dhcp_disabled
+            List of networks with dhcp disabled
+
+        hosted_networks
+            A mapping of agent id to the ids of the networks that they
+            should be initially hosting.
+
+        expected_auto_schedule_return_value
+            Expected return value of 'auto_schedule_networks'.
+
+        expected_hosted_networks
+            This stores the expected networks that should have been scheduled
+            (or that could have already been scheduled) for each agent
+            after the 'auto_schedule_networks' function is called.
+    """
+
+    scenarios = [
+        ('Agent scheduled to the network if network is not yet hosted',
+         dict(agent_count=1,
+              max_agents_per_network=1,
+              network_count=1,
+              networks_with_dhcp_disabled=[],
+              hosted_networks={},
+              expected_auto_schedule_return_value=True,
+              expected_hosted_networks={'agent-0': ['network-0']})),
+
+        ('No agent scheduled if no networks are present',
+         dict(agent_count=1,
+              max_agents_per_network=1,
+              network_count=0,
+              networks_with_dhcp_disabled=[],
+              hosted_networks={},
+              expected_auto_schedule_return_value=False,
+              expected_hosted_networks={'agent-0': []})),
+
+        ('Agents scheduled to the networks if networks are not yet hosted',
+         dict(agent_count=2,
+              max_agents_per_network=3,
+              network_count=2,
+              networks_with_dhcp_disabled=[],
+              hosted_networks={},
+              expected_auto_schedule_return_value=True,
+              expected_hosted_networks={'agent-0': ['network-0',
+                                                    'network-1'],
+                                        'agent-1': ['network-0',
+                                                    'network-1']})),
+
+        ('No new agents scheduled if networks are already hosted',
+         dict(agent_count=2,
+              max_agents_per_network=3,
+              network_count=2,
+              networks_with_dhcp_disabled=[],
+              hosted_networks={'agent-0': ['network-0', 'network-1'],
+                               'agent-1': ['network-0', 'network-1']},
+              expected_auto_schedule_return_value=True,
+              expected_hosted_networks={'agent-0': ['network-0',
+                                                    'network-1'],
+                                        'agent-1': ['network-0',
+                                                    'network-1']})),
+
+        ('Additional agents scheduled to the networks if'
+         ' max_agents_per_network is not yet reached',
+         dict(agent_count=4,
+              max_agents_per_network=3,
+              network_count=4,
+              networks_with_dhcp_disabled=[],
+              hosted_networks={'agent-0': ['network-0', 'network-1'],
+                               'agent-1': ['network-0'],
+                               'agent-2': ['network-2'],
+                               'agent-3': ['network-0', 'network-2']},
+              expected_auto_schedule_return_value=True,
+              expected_hosted_networks={'agent-0': ['network-0',
+                                                    'network-1',
+                                                    'network-2',
+                                                    'network-3'],
+                                        'agent-1': ['network-0',
+                                                    'network-1',
+                                                    'network-2',
+                                                    'network-3'],
+                                        'agent-2': ['network-1',
+                                                    'network-2',
+                                                    'network-3'],
+                                        'agent-3': ['network-0',
+                                                    'network-1',
+                                                    'network-2',
+                                                    'network-3']})),
+
+        ('No agents scheduled if networks already hosted and'
+         ' max_agents_per_network reached',
+         dict(agent_count=4,
+              max_agents_per_network=1,
+              network_count=4,
+              networks_with_dhcp_disabled=[],
+              hosted_networks={'agent-0': ['network-0'],
+                               'agent-1': ['network-2'],
+                               'agent-2': ['network-1'],
+                               'agent-3': ['network-3']},
+              expected_auto_schedule_return_value=True,
+              expected_hosted_networks={'agent-0': ['network-0'],
+                                        'agent-1': ['network-2'],
+                                        'agent-2': ['network-1'],
+                                        'agent-3': ['network-3']})),
+
+        ('No agents scheduled to the network with dhcp disabled',
+         dict(agent_count=2,
+              max_agents_per_network=3,
+              network_count=2,
+              networks_with_dhcp_disabled=['network-1'],
+              hosted_networks={},
+              expected_auto_schedule_return_value=True,
+              expected_hosted_networks={'agent-0': ['network-0'],
+                                        'agent-1': ['network-0']})),
+
+        ('No agents scheduled if all networks have dhcp disabled',
+         dict(agent_count=2,
+              max_agents_per_network=3,
+              network_count=2,
+              networks_with_dhcp_disabled=['network-0', 'network-1'],
+              hosted_networks={},
+              expected_auto_schedule_return_value=False,
+              expected_hosted_networks={'agent-0': [],
+                                        'agent-1': []})),
+    ]
+
+    def _strip_host_index(self, name):
+        """Strips the host index.
+
+        Eg. if name = '2-agent-3', then 'agent-3' is returned.
+        """
+        return name[name.find('-') + 1:]
+
+    def _extract_index(self, name):
+        """Extracts the index number and returns.
+
+        Eg. if name = '2-agent-3', then 3 is returned
+        """
+        return int(name.split('-')[-1])
+
+    def get_subnets(self, context, fields=None):
+        subnets = []
+        for net_id in self._networks:
+            enable_dhcp = (not self._strip_host_index(net_id) in
+                           self.networks_with_dhcp_disabled)
+            subnets.append({'network_id': net_id,
+                            'enable_dhcp': enable_dhcp})
+        return subnets
+
+    def _get_hosted_networks_on_dhcp_agent(self, agent_id):
+        query = self.ctx.session.query(
+            agentschedulers_db.NetworkDhcpAgentBinding.network_id)
+        query = query.filter(
+            agentschedulers_db.NetworkDhcpAgentBinding.dhcp_agent_id ==
+            agent_id)
+
+        return [item[0] for item in query]
+
+    def _test_auto_schedule(self, host_index):
+        self.config(dhcp_agents_per_network=self.max_agents_per_network)
+        scheduler = dhcp_agent_scheduler.ChanceScheduler()
+        self.ctx = context.get_admin_context()
+        msg = 'host_index = %s' % host_index
+
+        # create dhcp agents
+        hosts = ['%s-agent-%s' % (host_index, i)
+                 for i in range(self.agent_count)]
+        dhcp_agents = self._create_and_set_agents_down(hosts)
+
+        # create networks
+        self._networks = ['%s-network-%s' % (host_index, i)
+                          for i in range(self.network_count)]
+        self._save_networks(self._networks)
+
+        # pre schedule the networks to the agents defined in
+        # self.hosted_networks before calling auto_schedule_network
+        for agent, networks in six.iteritems(self.hosted_networks):
+            agent_index = self._extract_index(agent)
+            for net in networks:
+                net_index = self._extract_index(net)
+                scheduler._schedule_bind_network(self.ctx,
+                                                 [dhcp_agents[agent_index]],
+                                                 self._networks[net_index])
+
+        retval = scheduler.auto_schedule_networks(self, self.ctx,
+                                                  hosts[host_index])
+        self.assertEqual(self.expected_auto_schedule_return_value, retval,
+                         message=msg)
+
+        agent_id = dhcp_agents[host_index].id
+        hosted_networks = self._get_hosted_networks_on_dhcp_agent(agent_id)
+        hosted_net_ids = [self._strip_host_index(net)
+                          for net in hosted_networks]
+        expected_hosted_networks = self.expected_hosted_networks['agent-%s' %
+                                                                 host_index]
+        for hosted_net_id in hosted_net_ids:
+            self.assertIn(hosted_net_id, expected_hosted_networks,
+                          message=msg + '[%s]' % hosted_net_id)
+
+    def test_auto_schedule(self):
+        for i in range(self.agent_count):
+            self._test_auto_schedule(i)
index cf4f130bf1fa702e672ef162b09129780400683a..c49afe973a2a49cf3899ece75dc99147abda1d26 100644 (file)
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import mock
+import datetime
 
+import mock
 from oslo.utils import timeutils
+import testscenarios
+
 
 from neutron.common import constants
 from neutron.common import topics
@@ -26,12 +29,16 @@ from neutron.db import models_v2
 from neutron.scheduler import dhcp_agent_scheduler
 from neutron.tests.unit import testlib_api
 
+# Required to generate tests from scenarios. Not compatible with nose.
+load_tests = testscenarios.load_tests_apply_scenarios
+
 
-class DhcpSchedulerTestCase(testlib_api.SqlTestCase):
+class TestDhcpSchedulerBaseTestCase(testlib_api.SqlTestCase):
 
     def setUp(self):
-        super(DhcpSchedulerTestCase, self).setUp()
+        super(TestDhcpSchedulerBaseTestCase, self).setUp()
         self.ctx = context.get_admin_context()
+        self.network = {'id': 'foo_network_id'}
         self.network_id = 'foo_network_id'
         self._save_networks([self.network_id])
 
@@ -54,6 +61,16 @@ class DhcpSchedulerTestCase(testlib_api.SqlTestCase):
             with self.ctx.session.begin(subtransactions=True):
                 self.ctx.session.add(agent)
 
+    def _create_and_set_agents_down(self, hosts, down_agent_count=0):
+        dhcp_agents = self._get_agents(hosts)
+        # bring down the specified agents
+        for agent in dhcp_agents[:down_agent_count]:
+            old_time = agent['heartbeat_timestamp']
+            hour_old = old_time - datetime.timedelta(hours=1)
+            agent['heartbeat_timestamp'] = hour_old
+        self._save_agents(dhcp_agents)
+        return dhcp_agents
+
     def _save_networks(self, networks):
         for network_id in networks:
             with self.ctx.session.begin(subtransactions=True):
@@ -62,64 +79,115 @@ class DhcpSchedulerTestCase(testlib_api.SqlTestCase):
     def _test_schedule_bind_network(self, agents, network_id):
         scheduler = dhcp_agent_scheduler.ChanceScheduler()
         scheduler._schedule_bind_network(self.ctx, agents, network_id)
-        results = (
-            self.ctx.session.query(agentschedulers_db.NetworkDhcpAgentBinding).
-            filter_by(network_id=network_id).all())
+        results = self.ctx.session.query(
+            agentschedulers_db.NetworkDhcpAgentBinding).filter_by(
+            network_id=network_id).all()
         self.assertEqual(len(agents), len(results))
         for result in results:
             self.assertEqual(network_id, result.network_id)
 
+
+class TestDhcpScheduler(TestDhcpSchedulerBaseTestCase):
+
     def test_schedule_bind_network_single_agent(self):
-        agents = self._get_agents(['host-a'])
-        self._save_agents(agents)
+        agents = self._create_and_set_agents_down(['host-a'])
         self._test_schedule_bind_network(agents, self.network_id)
 
     def test_schedule_bind_network_multi_agents(self):
-        agents = self._get_agents(['host-a', 'host-b'])
-        self._save_agents(agents)
+        agents = self._create_and_set_agents_down(['host-a', 'host-b'])
         self._test_schedule_bind_network(agents, self.network_id)
 
     def test_schedule_bind_network_multi_agent_fail_one(self):
-        agents = self._get_agents(['host-a'])
-        self._save_agents(agents)
+        agents = self._create_and_set_agents_down(['host-a'])
         self._test_schedule_bind_network(agents, self.network_id)
         with mock.patch.object(dhcp_agent_scheduler.LOG, 'info') as fake_log:
             self._test_schedule_bind_network(agents, self.network_id)
             self.assertEqual(1, fake_log.call_count)
 
-    def test_auto_schedule_networks_no_networks(self):
-        plugin = mock.MagicMock()
-        plugin.get_networks.return_value = []
-        scheduler = dhcp_agent_scheduler.ChanceScheduler()
-        self.assertFalse(scheduler.auto_schedule_networks(plugin,
-                                                          self.ctx, "host-a"))
 
-    def test_auto_schedule_networks(self):
+class TestAutoScheduleNetworks(TestDhcpSchedulerBaseTestCase):
+    """Unit test scenarios for ChanceScheduler.auto_schedule_networks.
+
+    network_present
+        Network is present or not
+
+    enable_dhcp
+        Dhcp is enabled or disabled in the subnet of the network
+
+    scheduled_already
+        Network is already scheduled to the agent or not
+
+    agent_down
+        Dhcp agent is down or alive
+
+    valid_host
+        If true, then an valid host is passed to schedule the network,
+        else an invalid host is passed.
+    """
+    scenarios = [
+        ('Network present',
+         dict(network_present=True,
+              enable_dhcp=True,
+              scheduled_already=False,
+              agent_down=False,
+              valid_host=True)),
+
+        ('No network',
+         dict(network_present=False,
+              enable_dhcp=False,
+              scheduled_already=False,
+              agent_down=False,
+              valid_host=True)),
+
+        ('Network already scheduled',
+         dict(network_present=True,
+              enable_dhcp=True,
+              scheduled_already=True,
+              agent_down=False,
+              valid_host=True)),
+
+        ('Agent down',
+         dict(network_present=True,
+              enable_dhcp=True,
+              scheduled_already=False,
+              agent_down=False,
+              valid_host=True)),
+
+        ('dhcp disabled',
+         dict(network_present=True,
+              enable_dhcp=False,
+              scheduled_already=False,
+              agent_down=False,
+              valid_host=False)),
+
+        ('Invalid host',
+         dict(network_present=True,
+              enable_dhcp=True,
+              scheduled_already=False,
+              agent_down=False,
+              valid_host=False)),
+    ]
+
+    def test_auto_schedule_network(self):
         plugin = mock.MagicMock()
-        plugin.get_subnets.return_value = [{"network_id": self.network_id,
-                                            "enable_dhcp": True}]
-        agents = self._get_agents(['host-a'])
-        self._save_agents(agents)
+        plugin.get_subnets.return_value = (
+            [{"network_id": self.network_id, "enable_dhcp": self.enable_dhcp}]
+            if self.network_present else [])
         scheduler = dhcp_agent_scheduler.ChanceScheduler()
-
-        self.assertTrue(scheduler.auto_schedule_networks(plugin,
-                                                         self.ctx, "host-a"))
-        results = (
-            self.ctx.session.query(agentschedulers_db.NetworkDhcpAgentBinding)
-            .all())
-        self.assertEqual(1, len(results))
-
-    def test_auto_schedule_networks_network_already_scheduled(self):
-        plugin = mock.MagicMock()
-        plugin.get_subnets.return_value = [{"network_id": self.network_id,
-                                            "enable_dhcp": True}]
-        agents = self._get_agents(['host-a'])
-        self._save_agents(agents)
-        scheduler = dhcp_agent_scheduler.ChanceScheduler()
-        self._test_schedule_bind_network(agents, self.network_id)
-        self.assertTrue(scheduler.auto_schedule_networks(plugin,
-                                                         self.ctx, "host-a"))
-        results = (
-            self.ctx.session.query(agentschedulers_db.NetworkDhcpAgentBinding)
-            .all())
-        self.assertEqual(1, len(results))
+        if self.network_present:
+            down_agent_count = 1 if self.agent_down else 0
+            agents = self._create_and_set_agents_down(
+                ['host-a'], down_agent_count=down_agent_count)
+            if self.scheduled_already:
+                self._test_schedule_bind_network(agents, self.network_id)
+
+        expected_result = (self.network_present and self.enable_dhcp)
+        expected_hosted_agents = (1 if expected_result and
+                                  self.valid_host else 0)
+        host = "host-a" if self.valid_host else "host-b"
+        observed_ret_value = scheduler.auto_schedule_networks(
+            plugin, self.ctx, host)
+        self.assertEqual(expected_result, observed_ret_value)
+        hosted_agents = self.ctx.session.query(
+            agentschedulers_db.NetworkDhcpAgentBinding).all()
+        self.assertEqual(expected_hosted_agents, len(hosted_agents))