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
This commit is contained in:
Numan Siddique 2014-09-11 20:25:20 +05:30
parent d145fee017
commit 690f879778
3 changed files with 458 additions and 43 deletions

View File

@ -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)

View File

@ -13,9 +13,12 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import mock import datetime
import mock
from oslo.utils import timeutils from oslo.utils import timeutils
import testscenarios
from neutron.common import constants from neutron.common import constants
from neutron.common import topics from neutron.common import topics
@ -26,12 +29,16 @@ from neutron.db import models_v2
from neutron.scheduler import dhcp_agent_scheduler from neutron.scheduler import dhcp_agent_scheduler
from neutron.tests.unit import testlib_api 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): def setUp(self):
super(DhcpSchedulerTestCase, self).setUp() super(TestDhcpSchedulerBaseTestCase, self).setUp()
self.ctx = context.get_admin_context() self.ctx = context.get_admin_context()
self.network = {'id': 'foo_network_id'}
self.network_id = 'foo_network_id' self.network_id = 'foo_network_id'
self._save_networks([self.network_id]) self._save_networks([self.network_id])
@ -54,6 +61,16 @@ class DhcpSchedulerTestCase(testlib_api.SqlTestCase):
with self.ctx.session.begin(subtransactions=True): with self.ctx.session.begin(subtransactions=True):
self.ctx.session.add(agent) 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): def _save_networks(self, networks):
for network_id in networks: for network_id in networks:
with self.ctx.session.begin(subtransactions=True): 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): def _test_schedule_bind_network(self, agents, network_id):
scheduler = dhcp_agent_scheduler.ChanceScheduler() scheduler = dhcp_agent_scheduler.ChanceScheduler()
scheduler._schedule_bind_network(self.ctx, agents, network_id) scheduler._schedule_bind_network(self.ctx, agents, network_id)
results = ( results = self.ctx.session.query(
self.ctx.session.query(agentschedulers_db.NetworkDhcpAgentBinding). agentschedulers_db.NetworkDhcpAgentBinding).filter_by(
filter_by(network_id=network_id).all()) network_id=network_id).all()
self.assertEqual(len(agents), len(results)) self.assertEqual(len(agents), len(results))
for result in results: for result in results:
self.assertEqual(network_id, result.network_id) self.assertEqual(network_id, result.network_id)
class TestDhcpScheduler(TestDhcpSchedulerBaseTestCase):
def test_schedule_bind_network_single_agent(self): def test_schedule_bind_network_single_agent(self):
agents = self._get_agents(['host-a']) agents = self._create_and_set_agents_down(['host-a'])
self._save_agents(agents)
self._test_schedule_bind_network(agents, self.network_id) self._test_schedule_bind_network(agents, self.network_id)
def test_schedule_bind_network_multi_agents(self): def test_schedule_bind_network_multi_agents(self):
agents = self._get_agents(['host-a', 'host-b']) agents = self._create_and_set_agents_down(['host-a', 'host-b'])
self._save_agents(agents)
self._test_schedule_bind_network(agents, self.network_id) self._test_schedule_bind_network(agents, self.network_id)
def test_schedule_bind_network_multi_agent_fail_one(self): def test_schedule_bind_network_multi_agent_fail_one(self):
agents = self._get_agents(['host-a']) agents = self._create_and_set_agents_down(['host-a'])
self._save_agents(agents)
self._test_schedule_bind_network(agents, self.network_id) self._test_schedule_bind_network(agents, self.network_id)
with mock.patch.object(dhcp_agent_scheduler.LOG, 'info') as fake_log: with mock.patch.object(dhcp_agent_scheduler.LOG, 'info') as fake_log:
self._test_schedule_bind_network(agents, self.network_id) self._test_schedule_bind_network(agents, self.network_id)
self.assertEqual(1, fake_log.call_count) 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):
plugin = mock.MagicMock() """Unit test scenarios for ChanceScheduler.auto_schedule_networks.
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.assertTrue(scheduler.auto_schedule_networks(plugin, network_present
self.ctx, "host-a")) Network is present or not
results = (
self.ctx.session.query(agentschedulers_db.NetworkDhcpAgentBinding)
.all())
self.assertEqual(1, len(results))
def test_auto_schedule_networks_network_already_scheduled(self): 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 = mock.MagicMock()
plugin.get_subnets.return_value = [{"network_id": self.network_id, plugin.get_subnets.return_value = (
"enable_dhcp": True}] [{"network_id": self.network_id, "enable_dhcp": self.enable_dhcp}]
agents = self._get_agents(['host-a']) if self.network_present else [])
self._save_agents(agents)
scheduler = dhcp_agent_scheduler.ChanceScheduler() scheduler = dhcp_agent_scheduler.ChanceScheduler()
self._test_schedule_bind_network(agents, self.network_id) if self.network_present:
self.assertTrue(scheduler.auto_schedule_networks(plugin, down_agent_count = 1 if self.agent_down else 0
self.ctx, "host-a")) agents = self._create_and_set_agents_down(
results = ( ['host-a'], down_agent_count=down_agent_count)
self.ctx.session.query(agentschedulers_db.NetworkDhcpAgentBinding) if self.scheduled_already:
.all()) self._test_schedule_bind_network(agents, self.network_id)
self.assertEqual(1, len(results))
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))