1 # Copyright 2014 OpenStack Foundation
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
17 from oslo_config import cfg
18 from oslo_utils import importutils
21 from neutron.common import constants
22 from neutron import context
23 from neutron.db import agentschedulers_db as sched_db
24 from neutron.db import common_db_mixin
25 from neutron.db import models_v2
26 from neutron.extensions import dhcpagentscheduler
27 from neutron.scheduler import dhcp_agent_scheduler
28 from neutron.tests.common import helpers
29 from neutron.tests.unit import testlib_api
31 # Required to generate tests from scenarios. Not compatible with nose.
32 load_tests = testscenarios.load_tests_apply_scenarios
38 class TestDhcpSchedulerBaseTestCase(testlib_api.SqlTestCase):
41 super(TestDhcpSchedulerBaseTestCase, self).setUp()
42 self.ctx = context.get_admin_context()
43 self.network = {'id': 'foo_network_id'}
44 self.network_id = 'foo_network_id'
45 self._save_networks([self.network_id])
47 def _create_and_set_agents_down(self, hosts, down_agent_count=0,
49 az=helpers.DEFAULT_AZ):
51 for i, host in enumerate(hosts):
52 is_alive = i >= down_agent_count
53 agents.append(helpers.register_dhcp_agent(
55 admin_state_up=admin_state_up,
60 def _save_networks(self, networks):
61 for network_id in networks:
62 with self.ctx.session.begin(subtransactions=True):
63 self.ctx.session.add(models_v2.Network(id=network_id))
65 def _test_schedule_bind_network(self, agents, network_id):
66 scheduler = dhcp_agent_scheduler.ChanceScheduler()
67 scheduler.resource_filter.bind(self.ctx, agents, network_id)
68 results = self.ctx.session.query(
69 sched_db.NetworkDhcpAgentBinding).filter_by(
70 network_id=network_id).all()
71 self.assertEqual(len(agents), len(results))
72 for result in results:
73 self.assertEqual(network_id, result.network_id)
76 class TestDhcpScheduler(TestDhcpSchedulerBaseTestCase):
78 def test_schedule_bind_network_single_agent(self):
79 agents = self._create_and_set_agents_down(['host-a'])
80 self._test_schedule_bind_network(agents, self.network_id)
82 def test_schedule_bind_network_multi_agents(self):
83 agents = self._create_and_set_agents_down(['host-a', 'host-b'])
84 self._test_schedule_bind_network(agents, self.network_id)
86 def test_schedule_bind_network_multi_agent_fail_one(self):
87 agents = self._create_and_set_agents_down(['host-a'])
88 self._test_schedule_bind_network(agents, self.network_id)
89 with mock.patch.object(dhcp_agent_scheduler.LOG, 'info') as fake_log:
90 self._test_schedule_bind_network(agents, self.network_id)
91 self.assertEqual(1, fake_log.call_count)
93 def _test_get_agents_and_scheduler_for_dead_agent(self):
94 agents = self._create_and_set_agents_down(['dead_host', 'alive_host'],
96 dead_agent = [agents[0]]
97 alive_agent = [agents[1]]
98 self._test_schedule_bind_network(dead_agent, self.network_id)
99 scheduler = dhcp_agent_scheduler.ChanceScheduler()
100 return dead_agent, alive_agent, scheduler
102 def _test_reschedule_vs_network_on_dead_agent(self,
104 dead_agent, alive_agent, scheduler = (
105 self._test_get_agents_and_scheduler_for_dead_agent())
106 network = {'id': self.network_id}
108 plugin.get_subnets.return_value = [{"network_id": self.network_id,
109 "enable_dhcp": True}]
110 plugin.get_agents_db.return_value = dead_agent + alive_agent
111 if active_hosts_only:
112 plugin.get_dhcp_agents_hosting_networks.return_value = []
115 plugin, self.ctx, network))
117 plugin.get_dhcp_agents_hosting_networks.return_value = dead_agent
120 plugin, self.ctx, network))
122 def test_network_rescheduled_when_db_returns_active_hosts(self):
123 self._test_reschedule_vs_network_on_dead_agent(True)
125 def test_network_not_rescheduled_when_db_returns_all_hosts(self):
126 self._test_reschedule_vs_network_on_dead_agent(False)
128 def _get_agent_binding_from_db(self, agent):
129 return self.ctx.session.query(
130 sched_db.NetworkDhcpAgentBinding
131 ).filter_by(dhcp_agent_id=agent[0].id).all()
133 def _test_auto_reschedule_vs_network_on_dead_agent(self,
135 dead_agent, alive_agent, scheduler = (
136 self._test_get_agents_and_scheduler_for_dead_agent())
138 plugin.get_subnets.return_value = [{"network_id": self.network_id,
139 "enable_dhcp": True}]
140 plugin.get_network.return_value = self.network
141 if active_hosts_only:
142 plugin.get_dhcp_agents_hosting_networks.return_value = []
144 plugin.get_dhcp_agents_hosting_networks.return_value = dead_agent
145 network_assigned_to_dead_agent = (
146 self._get_agent_binding_from_db(dead_agent))
147 self.assertEqual(1, len(network_assigned_to_dead_agent))
149 scheduler.auto_schedule_networks(
150 plugin, self.ctx, "alive_host"))
151 network_assigned_to_dead_agent = (
152 self._get_agent_binding_from_db(dead_agent))
153 network_assigned_to_alive_agent = (
154 self._get_agent_binding_from_db(alive_agent))
155 self.assertEqual(1, len(network_assigned_to_dead_agent))
156 if active_hosts_only:
157 self.assertEqual(1, len(network_assigned_to_alive_agent))
159 self.assertEqual(0, len(network_assigned_to_alive_agent))
161 def test_network_auto_rescheduled_when_db_returns_active_hosts(self):
162 self._test_auto_reschedule_vs_network_on_dead_agent(True)
164 def test_network_not_auto_rescheduled_when_db_returns_all_hosts(self):
165 self._test_auto_reschedule_vs_network_on_dead_agent(False)
168 class TestAutoScheduleNetworks(TestDhcpSchedulerBaseTestCase):
169 """Unit test scenarios for ChanceScheduler.auto_schedule_networks.
172 Network is present or not
175 Dhcp is enabled or disabled in the subnet of the network
178 Network is already scheduled to the agent or not
181 Dhcp agent is down or alive
184 If true, then an valid host is passed to schedule the network,
185 else an invalid host is passed.
188 'availability_zone_hints' of the network.
189 note that default 'availability_zone' of an agent is 'nova'.
193 dict(network_present=True,
195 scheduled_already=False,
201 dict(network_present=False,
203 scheduled_already=False,
208 ('Network already scheduled',
209 dict(network_present=True,
211 scheduled_already=True,
217 dict(network_present=True,
219 scheduled_already=False,
225 dict(network_present=True,
227 scheduled_already=False,
233 dict(network_present=True,
235 scheduled_already=False,
241 dict(network_present=True,
243 scheduled_already=False,
249 dict(network_present=True,
251 scheduled_already=False,
254 az_hints=['not-match'])),
257 def test_auto_schedule_network(self):
258 plugin = mock.MagicMock()
259 plugin.get_subnets.return_value = (
260 [{"network_id": self.network_id, "enable_dhcp": self.enable_dhcp}]
261 if self.network_present else [])
262 plugin.get_network.return_value = {'availability_zone_hints':
264 scheduler = dhcp_agent_scheduler.ChanceScheduler()
265 if self.network_present:
266 down_agent_count = 1 if self.agent_down else 0
267 agents = self._create_and_set_agents_down(
268 ['host-a'], down_agent_count=down_agent_count)
269 if self.scheduled_already:
270 self._test_schedule_bind_network(agents, self.network_id)
272 expected_result = (self.network_present and self.enable_dhcp)
273 expected_hosted_agents = (1 if expected_result and
274 self.valid_host else 0)
275 if (self.az_hints and
276 agents[0]['availability_zone'] not in self.az_hints):
277 expected_hosted_agents = 0
278 host = "host-a" if self.valid_host else "host-b"
279 observed_ret_value = scheduler.auto_schedule_networks(
280 plugin, self.ctx, host)
281 self.assertEqual(expected_result, observed_ret_value)
282 hosted_agents = self.ctx.session.query(
283 sched_db.NetworkDhcpAgentBinding).all()
284 self.assertEqual(expected_hosted_agents, len(hosted_agents))
287 class TestNetworksFailover(TestDhcpSchedulerBaseTestCase,
288 sched_db.DhcpAgentSchedulerDbMixin,
289 common_db_mixin.CommonDbMixin):
290 def test_reschedule_network_from_down_agent(self):
291 agents = self._create_and_set_agents_down(['host-a', 'host-b'], 1)
292 self._test_schedule_bind_network([agents[0]], self.network_id)
293 self._save_networks(["foo-network-2"])
294 self._test_schedule_bind_network([agents[1]], "foo-network-2")
295 with mock.patch.object(self, 'remove_network_from_dhcp_agent') as rn,\
296 mock.patch.object(self,
298 return_value=[agents[1]]) as sch,\
299 mock.patch.object(self,
302 return_value={'id': self.network_id}):
303 notifier = mock.MagicMock()
304 self.agent_notifiers[constants.AGENT_TYPE_DHCP] = notifier
305 self.remove_networks_from_down_agents()
306 rn.assert_called_with(mock.ANY, agents[0].id, self.network_id,
308 sch.assert_called_with(mock.ANY, {'id': self.network_id})
309 notifier.network_added_to_agent.assert_called_with(
310 mock.ANY, self.network_id, agents[1].host)
312 def _test_failed_rescheduling(self, rn_side_effect=None):
313 agents = self._create_and_set_agents_down(['host-a', 'host-b'], 1)
314 self._test_schedule_bind_network([agents[0]], self.network_id)
315 with mock.patch.object(self,
316 'remove_network_from_dhcp_agent',
317 side_effect=rn_side_effect) as rn,\
318 mock.patch.object(self,
320 return_value=None) as sch,\
321 mock.patch.object(self,
324 return_value={'id': self.network_id}):
325 notifier = mock.MagicMock()
326 self.agent_notifiers[constants.AGENT_TYPE_DHCP] = notifier
327 self.remove_networks_from_down_agents()
328 rn.assert_called_with(mock.ANY, agents[0].id, self.network_id,
330 sch.assert_called_with(mock.ANY, {'id': self.network_id})
331 self.assertFalse(notifier.network_added_to_agent.called)
333 def test_reschedule_network_from_down_agent_failed(self):
334 self._test_failed_rescheduling()
336 def test_reschedule_network_from_down_agent_concurrent_removal(self):
337 self._test_failed_rescheduling(
338 rn_side_effect=dhcpagentscheduler.NetworkNotHostedByDhcpAgent(
339 network_id='foo', agent_id='bar'))
341 def test_filter_bindings(self):
343 sched_db.NetworkDhcpAgentBinding(network_id='foo1',
344 dhcp_agent={'id': 'id1'}),
345 sched_db.NetworkDhcpAgentBinding(network_id='foo2',
346 dhcp_agent={'id': 'id1'}),
347 sched_db.NetworkDhcpAgentBinding(network_id='foo3',
348 dhcp_agent={'id': 'id2'}),
349 sched_db.NetworkDhcpAgentBinding(network_id='foo4',
350 dhcp_agent={'id': 'id2'})]
351 with mock.patch.object(self, 'agent_starting_up',
352 side_effect=[True, False]):
353 res = [b for b in self._filter_bindings(None, bindings)]
354 # once per each agent id1 and id2
355 self.assertEqual(2, len(res))
356 res_ids = [b.network_id for b in res]
357 self.assertIn('foo3', res_ids)
358 self.assertIn('foo4', res_ids)
360 def test_reschedule_network_from_down_agent_failed_on_unexpected(self):
361 agents = self._create_and_set_agents_down(['host-a'], 1)
362 self._test_schedule_bind_network([agents[0]], self.network_id)
363 with mock.patch.object(
364 self, '_filter_bindings',
365 side_effect=Exception()):
366 # just make sure that no exception is raised
367 self.remove_networks_from_down_agents()
369 def test_reschedule_doesnt_occur_if_no_agents(self):
370 agents = self._create_and_set_agents_down(['host-a', 'host-b'], 2)
371 self._test_schedule_bind_network([agents[0]], self.network_id)
372 with mock.patch.object(
373 self, 'remove_network_from_dhcp_agent') as rn:
374 self.remove_networks_from_down_agents()
375 self.assertFalse(rn.called)
378 class DHCPAgentWeightSchedulerTestCase(TestDhcpSchedulerBaseTestCase):
379 """Unit test scenarios for WeightScheduler.schedule."""
382 super(DHCPAgentWeightSchedulerTestCase, self).setUp()
383 DB_PLUGIN_KLASS = 'neutron.plugins.ml2.plugin.Ml2Plugin'
384 self.setup_coreplugin(DB_PLUGIN_KLASS)
385 cfg.CONF.set_override("network_scheduler_driver",
386 'neutron.scheduler.dhcp_agent_scheduler.WeightScheduler')
387 self.plugin = importutils.import_object('neutron.plugins.ml2.plugin.'
389 self.assertEqual(1, self.patched_dhcp_periodic.call_count)
390 self.plugin.network_scheduler = importutils.import_object(
391 'neutron.scheduler.dhcp_agent_scheduler.WeightScheduler'
393 cfg.CONF.set_override('dhcp_agents_per_network', 1)
394 cfg.CONF.set_override("dhcp_load_type", "networks")
396 def test_scheduler_one_agents_per_network(self):
397 self._save_networks(['1111'])
398 helpers.register_dhcp_agent(HOST_C)
399 self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
401 agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
403 self.assertEqual(1, len(agents))
405 def test_scheduler_two_agents_per_network(self):
406 cfg.CONF.set_override('dhcp_agents_per_network', 2)
407 self._save_networks(['1111'])
408 helpers.register_dhcp_agent(HOST_C)
409 helpers.register_dhcp_agent(HOST_D)
410 self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
412 agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
414 self.assertEqual(2, len(agents))
416 def test_scheduler_no_active_agents(self):
417 self._save_networks(['1111'])
418 self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
420 agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
422 self.assertEqual(0, len(agents))
424 def test_scheduler_equal_distribution(self):
425 self._save_networks(['1111', '2222', '3333'])
426 helpers.register_dhcp_agent(HOST_C)
427 helpers.register_dhcp_agent(HOST_D, networks=1)
428 self.plugin.network_scheduler.schedule(
429 self.plugin, context.get_admin_context(), {'id': '1111'})
430 helpers.register_dhcp_agent(HOST_D, networks=2)
431 self.plugin.network_scheduler.schedule(
432 self.plugin, context.get_admin_context(), {'id': '2222'})
433 helpers.register_dhcp_agent(HOST_C, networks=4)
434 self.plugin.network_scheduler.schedule(
435 self.plugin, context.get_admin_context(), {'id': '3333'})
436 agent1 = self.plugin.get_dhcp_agents_hosting_networks(
438 agent2 = self.plugin.get_dhcp_agents_hosting_networks(
440 agent3 = self.plugin.get_dhcp_agents_hosting_networks(
442 self.assertEqual('host-c', agent1[0]['host'])
443 self.assertEqual('host-c', agent2[0]['host'])
444 self.assertEqual('host-d', agent3[0]['host'])
447 class TestDhcpSchedulerFilter(TestDhcpSchedulerBaseTestCase,
448 sched_db.DhcpAgentSchedulerDbMixin):
449 def _test_get_dhcp_agents_hosting_networks(self, expected, **kwargs):
450 agents = self._create_and_set_agents_down(['host-a', 'host-b'], 1)
451 agents += self._create_and_set_agents_down(['host-c', 'host-d'], 1,
452 admin_state_up=False)
453 self._test_schedule_bind_network(agents, self.network_id)
454 agents = self.get_dhcp_agents_hosting_networks(self.ctx,
457 host_ids = set(a['host'] for a in agents)
458 self.assertEqual(expected, host_ids)
460 def test_get_dhcp_agents_hosting_networks_default(self):
461 self._test_get_dhcp_agents_hosting_networks({'host-a', 'host-b',
464 def test_get_dhcp_agents_hosting_networks_active(self):
465 self._test_get_dhcp_agents_hosting_networks({'host-b', 'host-d'},
468 def test_get_dhcp_agents_hosting_networks_admin_up(self):
469 self._test_get_dhcp_agents_hosting_networks({'host-a', 'host-b'},
472 def test_get_dhcp_agents_hosting_networks_active_admin_up(self):
473 self._test_get_dhcp_agents_hosting_networks({'host-b'},
477 def test_get_dhcp_agents_hosting_networks_admin_down(self):
478 self._test_get_dhcp_agents_hosting_networks({'host-c', 'host-d'},
479 admin_state_up=False)
481 def test_get_dhcp_agents_hosting_networks_active_admin_down(self):
482 self._test_get_dhcp_agents_hosting_networks({'host-d'},
484 admin_state_up=False)
487 class DHCPAgentAZAwareWeightSchedulerTestCase(TestDhcpSchedulerBaseTestCase):
490 super(DHCPAgentAZAwareWeightSchedulerTestCase, self).setUp()
491 DB_PLUGIN_KLASS = 'neutron.plugins.ml2.plugin.Ml2Plugin'
492 self.setup_coreplugin(DB_PLUGIN_KLASS)
493 cfg.CONF.set_override("network_scheduler_driver",
494 'neutron.scheduler.dhcp_agent_scheduler.AZAwareWeightScheduler')
495 self.plugin = importutils.import_object('neutron.plugins.ml2.plugin.'
497 cfg.CONF.set_override('dhcp_agents_per_network', 1)
498 cfg.CONF.set_override("dhcp_load_type", "networks")
500 def test_az_scheduler_one_az_hints(self):
501 self._save_networks(['1111'])
502 helpers.register_dhcp_agent('az1-host1', networks=1, az='az1')
503 helpers.register_dhcp_agent('az1-host2', networks=2, az='az1')
504 helpers.register_dhcp_agent('az2-host1', networks=3, az='az2')
505 helpers.register_dhcp_agent('az2-host2', networks=4, az='az2')
506 self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
507 {'id': '1111', 'availability_zone_hints': ['az2']})
508 agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
510 self.assertEqual(1, len(agents))
511 self.assertEqual('az2-host1', agents[0]['host'])
513 def test_az_scheduler_default_az_hints(self):
514 cfg.CONF.set_override('default_availability_zones', ['az1'])
515 self._save_networks(['1111'])
516 helpers.register_dhcp_agent('az1-host1', networks=1, az='az1')
517 helpers.register_dhcp_agent('az1-host2', networks=2, az='az1')
518 helpers.register_dhcp_agent('az2-host1', networks=3, az='az2')
519 helpers.register_dhcp_agent('az2-host2', networks=4, az='az2')
520 self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
521 {'id': '1111', 'availability_zone_hints': []})
522 agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
524 self.assertEqual(1, len(agents))
525 self.assertEqual('az1-host1', agents[0]['host'])
527 def test_az_scheduler_two_az_hints(self):
528 cfg.CONF.set_override('dhcp_agents_per_network', 2)
529 self._save_networks(['1111'])
530 helpers.register_dhcp_agent('az1-host1', networks=1, az='az1')
531 helpers.register_dhcp_agent('az1-host2', networks=2, az='az1')
532 helpers.register_dhcp_agent('az2-host1', networks=3, az='az2')
533 helpers.register_dhcp_agent('az2-host2', networks=4, az='az2')
534 helpers.register_dhcp_agent('az3-host1', networks=5, az='az3')
535 helpers.register_dhcp_agent('az3-host2', networks=6, az='az3')
536 self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
537 {'id': '1111', 'availability_zone_hints': ['az1', 'az3']})
538 agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
540 self.assertEqual(2, len(agents))
541 expected_hosts = set(['az1-host1', 'az3-host1'])
542 hosts = set([a['host'] for a in agents])
543 self.assertEqual(expected_hosts, hosts)
545 def test_az_scheduler_two_az_hints_one_available_az(self):
546 cfg.CONF.set_override('dhcp_agents_per_network', 2)
547 self._save_networks(['1111'])
548 helpers.register_dhcp_agent('az1-host1', networks=1, az='az1')
549 helpers.register_dhcp_agent('az1-host2', networks=2, az='az1')
550 helpers.register_dhcp_agent('az2-host1', networks=3, alive=False,
552 helpers.register_dhcp_agent('az2-host2', networks=4,
553 admin_state_up=False, az='az2')
554 self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
555 {'id': '1111', 'availability_zone_hints': ['az1', 'az2']})
556 agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
558 self.assertEqual(2, len(agents))
559 expected_hosts = set(['az1-host1', 'az1-host2'])
560 hosts = set([a['host'] for a in agents])
561 self.assertEqual(expected_hosts, hosts)
563 def test_az_scheduler_no_az_hints(self):
564 cfg.CONF.set_override('dhcp_agents_per_network', 2)
565 self._save_networks(['1111'])
566 helpers.register_dhcp_agent('az1-host1', networks=2, az='az1')
567 helpers.register_dhcp_agent('az1-host2', networks=3, az='az1')
568 helpers.register_dhcp_agent('az2-host1', networks=2, az='az2')
569 helpers.register_dhcp_agent('az2-host2', networks=1, az='az2')
570 self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
571 {'id': '1111', 'availability_zone_hints': []})
572 agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
574 self.assertEqual(2, len(agents))
575 expected_hosts = set(['az1-host1', 'az2-host2'])
576 hosts = {a['host'] for a in agents}
577 self.assertEqual(expected_hosts, hosts)