From: Numan Siddique Date: Thu, 11 Sep 2014 14:55:20 +0000 (+0530) Subject: Improve test coverage of dhcp agent scheduling X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=690f87977826f670aeea1f8165339e6c5af4f920;p=openstack-build%2Fneutron-build.git Improve test coverage of dhcp agent scheduling 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 --- diff --git a/neutron/tests/functional/scheduler/__init__.py b/neutron/tests/functional/scheduler/__init__.py new file mode 100644 index 000000000..e69de29bb 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 index 000000000..7322aff02 --- /dev/null +++ b/neutron/tests/functional/scheduler/test_dhcp_agent_scheduler.py @@ -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) diff --git a/neutron/tests/unit/test_dhcp_scheduler.py b/neutron/tests/unit/test_dhcp_scheduler.py index cf4f130bf..c49afe973 100644 --- a/neutron/tests/unit/test_dhcp_scheduler.py +++ b/neutron/tests/unit/test_dhcp_scheduler.py @@ -13,9 +13,12 @@ # 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))