--- /dev/null
+# 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)
# 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
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])
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):
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))