text: # Copyright 2014 OpenStack Foundation
  #
  # 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 datetime
  from operator import attrgetter
  import random
  
  import mock
  from neutron_lib import constants
  from neutron_lib import context
  from neutron_lib.exceptions import dhcpagentscheduler as das_exc
  from oslo_config import cfg
  from oslo_utils import importutils
  from oslo_utils import uuidutils
  import testscenarios
  
  from neutron.db import agentschedulers_db as sched_db
  from neutron.db import common_db_mixin
  from neutron.objects import agent
  from neutron.objects import network as network_obj
  from neutron.scheduler import dhcp_agent_scheduler
  from neutron.services.segments import db as segments_service_db
  from neutron.tests.common import helpers
  from neutron.tests.unit.plugins.ml2 import test_plugin
  from neutron.tests.unit import testlib_api
  
  # Required to generate tests from scenarios. Not compatible with nose.
  load_tests = testscenarios.load_tests_apply_scenarios
  
  HOST_C = 'host-c'
  HOST_D = 'host-d'
  
  
  class TestDhcpSchedulerBaseTestCase(testlib_api.SqlTestCase):
      CORE_PLUGIN = 'neutron.db.db_base_plugin_v2.NeutronDbPluginV2'
  
      def setUp(self):
          super(TestDhcpSchedulerBaseTestCase, self).setUp()
          self.setup_coreplugin(self.CORE_PLUGIN)
          self.ctx = context.get_admin_context()
          self.network = {'id': uuidutils.generate_uuid()}
          self.network_id = self.network['id']
          self._save_networks([self.network_id])
  
      def _create_and_set_agents_down(self, hosts, down_agent_count=0,
                                      admin_state_up=True,
                                      az=helpers.DEFAULT_AZ):
          agents = []
          for i, host in enumerate(hosts):
              is_alive = i >= down_agent_count
              agents.append(helpers.register_dhcp_agent(
                  host,
                  admin_state_up=admin_state_up,
                  alive=is_alive,
                  az=az))
          return agents
  
      def _save_networks(self, networks):
          for network_id in networks:
              network_obj.Network(self.ctx, id=network_id).create()
  
      def _test_schedule_bind_network(self, agents, network_id):
          scheduler = dhcp_agent_scheduler.ChanceScheduler()
          scheduler.resource_filter.bind(self.ctx, agents, network_id)
          binding_objs = network_obj.NetworkDhcpAgentBinding.get_objects(
              self.ctx, network_id=network_id)
          self.assertEqual(len(agents), len(binding_objs))
          for result in binding_objs:
              self.assertEqual(network_id, result.network_id)
  
  
  class TestDhcpScheduler(TestDhcpSchedulerBaseTestCase):
  
      def test_schedule_bind_network_single_agent(self):
          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._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._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_get_agents_and_scheduler_for_dead_agent(self):
          agents = self._create_and_set_agents_down(['dead_host', 'alive_host'],
                                                    1)
          dead_agent = [agents[0]]
          alive_agent = [agents[1]]
          self._test_schedule_bind_network(dead_agent, self.network_id)
          scheduler = dhcp_agent_scheduler.ChanceScheduler()
          return dead_agent, alive_agent, scheduler
  
      def _test_reschedule_vs_network_on_dead_agent(self,
                                                    active_hosts_only):
          dead_agent, alive_agent, scheduler = (
              self._test_get_agents_and_scheduler_for_dead_agent())
          network = {'id': self.network_id}
          plugin = mock.Mock()
          plugin.get_subnets.return_value = [{"network_id": self.network_id,
                                              "enable_dhcp": True}]
          plugin.get_agent_objects.return_value = dead_agent + alive_agent
          plugin.filter_hosts_with_network_access.side_effect = (
              lambda context, network_id, hosts: hosts)
          if active_hosts_only:
              plugin.get_dhcp_agents_hosting_networks.return_value = []
              self.assertTrue(
                  scheduler.schedule(
                      plugin, self.ctx, network))
          else:
              plugin.get_dhcp_agents_hosting_networks.return_value = dead_agent
              self.assertFalse(
                  scheduler.schedule(
                      plugin, self.ctx, network))
  
      def test_network_rescheduled_when_db_returns_active_hosts(self):
          self._test_reschedule_vs_network_on_dead_agent(True)
  
      def test_network_not_rescheduled_when_db_returns_all_hosts(self):
          self._test_reschedule_vs_network_on_dead_agent(False)
  
      def _get_agent_binding_from_db(self, agent):
          return network_obj.NetworkDhcpAgentBinding.get_objects(
              self.ctx, dhcp_agent_id=agent[0].id)
  
      def _test_auto_reschedule_vs_network_on_dead_agent(self,
                                                         active_hosts_only):
          dead_agent, alive_agent, scheduler = (
              self._test_get_agents_and_scheduler_for_dead_agent())
          plugin = mock.Mock()
          plugin.get_subnets.return_value = [{"network_id": self.network_id,
                                              "enable_dhcp": True,
                                              "segment_id": None}]
          plugin.get_network.return_value = self.network
          if active_hosts_only:
              plugin.get_dhcp_agents_hosting_networks.return_value = []
          else:
              plugin.get_dhcp_agents_hosting_networks.return_value = dead_agent
          network_assigned_to_dead_agent = (
              self._get_agent_binding_from_db(dead_agent))
          self.assertEqual(1, len(network_assigned_to_dead_agent))
          self.assertTrue(
              scheduler.auto_schedule_networks(
                  plugin, self.ctx, "alive_host"))
          network_assigned_to_dead_agent = (
              self._get_agent_binding_from_db(dead_agent))
          network_assigned_to_alive_agent = (
              self._get_agent_binding_from_db(alive_agent))
          self.assertEqual(1, len(network_assigned_to_dead_agent))
          if active_hosts_only:
              self.assertEqual(1, len(network_assigned_to_alive_agent))
          else:
              self.assertEqual(0, len(network_assigned_to_alive_agent))
  
      def test_network_auto_rescheduled_when_db_returns_active_hosts(self):
          self._test_auto_reschedule_vs_network_on_dead_agent(True)
  
      def test_network_not_auto_rescheduled_when_db_returns_all_hosts(self):
          self._test_auto_reschedule_vs_network_on_dead_agent(False)
  
  
  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.
  
      az_hints
          'availability_zone_hints' of the network.
          note that default 'availability_zone' of an agent is 'nova'.
      """
      scenarios = [
          ('Network present',
           dict(network_present=True,
                enable_dhcp=True,
                scheduled_already=False,
                agent_down=False,
                valid_host=True,
                az_hints=[])),
  
          ('No network',
           dict(network_present=False,
                enable_dhcp=False,
                scheduled_already=False,
                agent_down=False,
                valid_host=True,
                az_hints=[])),
  
          ('Network already scheduled',
           dict(network_present=True,
                enable_dhcp=True,
                scheduled_already=True,
                agent_down=False,
                valid_host=True,
                az_hints=[])),
  
          ('Agent down',
           dict(network_present=True,
                enable_dhcp=True,
                scheduled_already=False,
                agent_down=False,
                valid_host=True,
                az_hints=[])),
  
          ('dhcp disabled',
           dict(network_present=True,
                enable_dhcp=False,
                scheduled_already=False,
                agent_down=False,
                valid_host=False,
                az_hints=[])),
  
          ('Invalid host',
           dict(network_present=True,
                enable_dhcp=True,
                scheduled_already=False,
                agent_down=False,
                valid_host=False,
                az_hints=[])),
  
          ('Match AZ',
           dict(network_present=True,
                enable_dhcp=True,
                scheduled_already=False,
                agent_down=False,
                valid_host=True,
                az_hints=['nova'])),
  
          ('Not match AZ',
           dict(network_present=True,
                enable_dhcp=True,
                scheduled_already=False,
                agent_down=False,
                valid_host=True,
                az_hints=['not-match'])),
      ]
  
      def test_auto_schedule_network(self):
          plugin = mock.MagicMock()
          plugin.get_subnets.return_value = (
              [{"network_id": self.network_id, "enable_dhcp": self.enable_dhcp,
              "segment_id": None}] if self.network_present else [])
          plugin.get_network.return_value = {'availability_zone_hints':
                                             self.az_hints}
          scheduler = dhcp_agent_scheduler.ChanceScheduler()
          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)
          if (self.az_hints and
                  agents[0]['availability_zone'] not in self.az_hints):
              expected_hosted_agents = 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)
          count_hosted_agents = network_obj.NetworkDhcpAgentBinding.count(
              self.ctx)
          self.assertEqual(expected_hosted_agents, count_hosted_agents)
  
  
  class TestAutoScheduleSegments(test_plugin.Ml2PluginV2TestCase,
                                 TestDhcpSchedulerBaseTestCase):
      """Unit test scenarios for ChanceScheduler"""
      CORE_PLUGIN = 'neutron.plugins.ml2.plugin.Ml2Plugin'
  
      def setUp(self):
          super(TestAutoScheduleSegments, self).setUp()
          self.plugin = self.driver
          self.segments_plugin = importutils.import_object(
              'neutron.services.segments.plugin.Plugin')
          self.ctx = context.get_admin_context()
          # Remove MissingAuthPlugin exception from logs
          mock.patch(
              'neutron.notifiers.batch_notifier.BatchNotifier._notify').start()
  
      def _create_network(self):
          net = self.plugin.create_network(
              self.ctx,
              {'network': {'name': 'name',
                           'tenant_id': 'tenant_one',
                           'admin_state_up': True,
                           'shared': True}})
          return net['id']
  
      def _create_segment(self, network_id):
          seg = self.segments_plugin.create_segment(
              self.ctx,
              {'segment': {'network_id': network_id,
                           'name': None, 'description': None,
                           'physical_network': 'physnet1',
                           'network_type': 'vlan',
                           'segmentation_id': constants.ATTR_NOT_SPECIFIED}})
          return seg['id']
  
      def _create_subnet(self, segment_id, network_id, cidr='192.168.10.0/24'):
          subnet = self.plugin.create_subnet(
              self.ctx,
              {'subnet': {'name': 'name',
                          'ip_version': 4,
                          'network_id': network_id,
                          'cidr': cidr,
                          'gateway_ip': constants.ATTR_NOT_SPECIFIED,
                          'allocation_pools': constants.ATTR_NOT_SPECIFIED,
                          'dns_nameservers': constants.ATTR_NOT_SPECIFIED,
                          'host_routes': constants.ATTR_NOT_SPECIFIED,
                          'tenant_id': 'tenant_one',
                          'enable_dhcp': True,
                          'segment_id': segment_id}})
          return subnet['id']
  
      def test_auto_schedule_one_network_one_segment_one_subnet(self):
          net_id = self._create_network()
          seg_id = self._create_segment(net_id)
          self._create_subnet(seg_id, net_id)
          helpers.register_dhcp_agent(HOST_C)
          segments_service_db.update_segment_host_mapping(
              self.ctx, HOST_C, {seg_id})
          scheduler = dhcp_agent_scheduler.ChanceScheduler()
          observed_return_val = scheduler.auto_schedule_networks(
              self.plugin, self.ctx, HOST_C)
          self.assertTrue(observed_return_val)
          agent1 = self.plugin.get_dhcp_agents_hosting_networks(
              self.ctx, [net_id])
          self.assertEqual(1, len(agent1))
          self.assertEqual('host-c', agent1[0]['host'])
  
      def test_auto_schedule_one_network_one_segment_two_subnet(self):
          net_id = self._create_network()
          seg_id = self._create_segment(net_id)
          self._create_subnet(seg_id, net_id)
          self._create_subnet(seg_id, net_id, '192.168.11.0/24')
          helpers.register_dhcp_agent(HOST_C)
          segments_service_db.update_segment_host_mapping(
              self.ctx, HOST_C, {seg_id})
          scheduler = dhcp_agent_scheduler.ChanceScheduler()
          observed_return_val = scheduler.auto_schedule_networks(
              self.plugin, self.ctx, HOST_C)
          self.assertTrue(observed_return_val)
          agent1 = self.plugin.get_dhcp_agents_hosting_networks(
              self.ctx, [net_id])
          self.assertEqual(1, len(agent1))
          self.assertEqual('host-c', agent1[0]['host'])
  
      def test_auto_schedule_one_network_two_segments_with_one_subnet_each(self):
          net_id = self._create_network()
          seg1_id = self._create_segment(net_id)
          self._create_subnet(seg1_id, net_id)
          helpers.register_dhcp_agent(HOST_D)
          segments_service_db.update_segment_host_mapping(
              self.ctx, HOST_D, {seg1_id})
          scheduler = dhcp_agent_scheduler.ChanceScheduler()
          observed_val_first_segment = scheduler.auto_schedule_networks(
              self.plugin, self.ctx, HOST_D)
          self.assertTrue(observed_val_first_segment)
          agents = self.plugin.get_dhcp_agents_hosting_networks(
              self.ctx, [net_id])
          self.assertEqual(1, len(agents))
  
          seg2_id = self._create_segment(net_id)
          self._create_subnet(seg2_id, net_id, '192.168.11.0/24')
          helpers.register_dhcp_agent(HOST_C)
          segments_service_db.update_segment_host_mapping(
              self.ctx, HOST_C, {seg2_id})
          observed_val_second_segment = scheduler.auto_schedule_networks(
              self.plugin, self.ctx, HOST_C)
          self.assertTrue(observed_val_second_segment)
          agents = self.plugin.get_dhcp_agents_hosting_networks(
              self.ctx, [net_id])
          self.assertEqual(2, len(agents))
  
  
  class TestNetworksFailover(TestDhcpSchedulerBaseTestCase,
                             sched_db.DhcpAgentSchedulerDbMixin,
                             common_db_mixin.CommonDbMixin):
      def test_reschedule_network_from_down_agent(self):
          net_id = uuidutils.generate_uuid()
          agents = self._create_and_set_agents_down(['host-a', 'host-b'], 1)
          self._test_schedule_bind_network([agents[0]], self.network_id)
          self._save_networks([net_id])
          self._test_schedule_bind_network([agents[1]], net_id)
          with mock.patch.object(self, 'remove_network_from_dhcp_agent') as rn,\
                  mock.patch.object(self,
                                    'schedule_network',
                                    return_value=[agents[1]]) as sch,\
                  mock.patch.object(self,
                                    'get_network',
                                    create=True,
                                    return_value={'id': self.network_id}):
              notifier = mock.MagicMock()
              self.agent_notifiers[constants.AGENT_TYPE_DHCP] = notifier
              self.remove_networks_from_down_agents()
              rn.assert_called_with(mock.ANY, agents[0].id, self.network_id,
                                    notify=False)
              sch.assert_called_with(mock.ANY, {'id': self.network_id})
              notifier.network_added_to_agent.assert_called_with(
                  mock.ANY, self.network_id, agents[1].host)
  
      def _test_failed_rescheduling(self, rn_side_effect=None):
          agents = self._create_and_set_agents_down(['host-a', 'host-b'], 1)
          self._test_schedule_bind_network([agents[0]], self.network_id)
          with mock.patch.object(self,
                                 'remove_network_from_dhcp_agent',
                                 side_effect=rn_side_effect) as rn,\
                  mock.patch.object(self,
                                    'schedule_network',
                                    return_value=None) as sch,\
                  mock.patch.object(self,
                                    'get_network',
                                    create=True,
                                    return_value={'id': self.network_id}):
              notifier = mock.MagicMock()
              self.agent_notifiers[constants.AGENT_TYPE_DHCP] = notifier
              self.remove_networks_from_down_agents()
              rn.assert_called_with(mock.ANY, agents[0].id, self.network_id,
                                    notify=False)
              sch.assert_called_with(mock.ANY, {'id': self.network_id})
              self.assertFalse(notifier.network_added_to_agent.called)
  
      def test_reschedule_network_from_down_agent_failed(self):
          self._test_failed_rescheduling()
  
      def test_reschedule_network_from_down_agent_concurrent_removal(self):
          self._test_failed_rescheduling(
              rn_side_effect=das_exc.NetworkNotHostedByDhcpAgent(
                  network_id='foo', agent_id='bar'))
  
      def _create_test_networks(self, num_net=0):
          networks = [network_obj.Network(
                         self.ctx,
                         id=uuidutils.generate_uuid(),
                         name='network-%s' % (i))
                      for i in range(num_net)]
          for net in networks:
              net.create()
          return [net.id for net in networks]
  
      def _create_dhcp_agents(self):
          timestamp = datetime.datetime.now()
          dhcp_agent_ids = [uuidutils.generate_uuid() for x in range(2)]
          dhcp_agent_1 = agent.Agent(self.ctx, id=dhcp_agent_ids[0],
                                     agent_type='DHCP Agent',
                                     topic='fake_topic',
                                     host='fake_host',
                                     binary='fake_binary',
                                     created_at=timestamp,
                                     started_at=timestamp,
                                     heartbeat_timestamp=timestamp,
                                     configurations={},
                                     load=0)
          dhcp_agent_1.create()
  
          dhcp_agent_2 = agent.Agent(self.ctx, id=dhcp_agent_ids[1],
                                     agent_type='DHCP Agent',
                                     topic='fake_topic',
                                     host='fake_host_1',
                                     binary='fake_binary',
                                     created_at=timestamp,
                                     started_at=timestamp,
                                     heartbeat_timestamp=timestamp,
                                     configurations={},
                                     load=0)
          dhcp_agent_2.create()
          return [dhcp_agent_1.id, dhcp_agent_2.id]
  
      def test_filter_bindings(self):
          self.ctx = context.get_admin_context()
          dhcp_agt_ids = self._create_dhcp_agents()
          network_ids = sorted(self._create_test_networks(num_net=4))
          ndab_obj1 = network_obj.NetworkDhcpAgentBinding(self.ctx,
              network_id=network_ids[0], dhcp_agent_id=dhcp_agt_ids[0])
          ndab_obj1.create()
          ndab_obj2 = network_obj.NetworkDhcpAgentBinding(self.ctx,
              network_id=network_ids[1], dhcp_agent_id=dhcp_agt_ids[0])
          ndab_obj2.create()
          ndab_obj3 = network_obj.NetworkDhcpAgentBinding(self.ctx,
              network_id=network_ids[2], dhcp_agent_id=dhcp_agt_ids[1])
          ndab_obj3.create()
          ndab_obj4 = network_obj.NetworkDhcpAgentBinding(self.ctx,
              network_id=network_ids[3], dhcp_agent_id=dhcp_agt_ids[1])
          ndab_obj4.create()
          bindings_objs = sorted(network_obj.NetworkDhcpAgentBinding.get_objects(
              self.ctx), key=attrgetter('network_id'))
  
          with mock.patch.object(self, 'agent_starting_up',
                                 side_effect=[True, False]):
              res = [b for b in self._filter_bindings(None, bindings_objs)]
              # once per each agent id1 and id2
              self.assertEqual(2, len(res))
              res_ids = [b.network_id for b in res]
              self.assertIn(network_ids[2], res_ids)
              self.assertIn(network_ids[3], res_ids)
  
      def test_reschedule_network_from_down_agent_failed_on_unexpected(self):
          agents = self._create_and_set_agents_down(['host-a'], 1)
          self._test_schedule_bind_network([agents[0]], self.network_id)
          with mock.patch.object(
              self, '_filter_bindings',
              side_effect=Exception()):
              # just make sure that no exception is raised
              self.remove_networks_from_down_agents()
  
      def test_reschedule_network_catches_exceptions_on_fetching_bindings(self):
          with mock.patch('neutron_lib.context.get_admin_context') as get_ctx:
              mock_ctx = mock.Mock()
              get_ctx.return_value = mock_ctx
              mock_ctx.session.query.side_effect = Exception()
              # just make sure that no exception is raised
              self.remove_networks_from_down_agents()
  
      def test_reschedule_doesnt_occur_if_no_agents(self):
          agents = self._create_and_set_agents_down(['host-a', 'host-b'], 2)
          self._test_schedule_bind_network([agents[0]], self.network_id)
          with mock.patch.object(
              self, 'remove_network_from_dhcp_agent') as rn:
              self.remove_networks_from_down_agents()
              self.assertFalse(rn.called)
  
  
  class DHCPAgentWeightSchedulerTestCase(test_plugin.Ml2PluginV2TestCase):
      """Unit test scenarios for WeightScheduler.schedule."""
  
      def setUp(self):
          super(DHCPAgentWeightSchedulerTestCase, self).setUp()
          weight_scheduler = (
              'neutron.scheduler.dhcp_agent_scheduler.WeightScheduler')
          cfg.CONF.set_override('network_scheduler_driver', weight_scheduler)
          self.plugin = self.driver
          mock.patch.object(
              self.plugin, 'filter_hosts_with_network_access',
              side_effect=lambda context, network_id, hosts: hosts).start()
          self.plugin.network_scheduler = importutils.import_object(
              weight_scheduler)
          cfg.CONF.set_override("dhcp_load_type", "networks")
          self.segments_plugin = importutils.import_object(
              'neutron.services.segments.plugin.Plugin')
          self.ctx = context.get_admin_context()
  
      def _create_network(self):
          net = self.plugin.create_network(
              self.ctx,
              {'network': {'name': 'name',
                           'tenant_id': 'tenant_one',
                           'admin_state_up': True,
                           'shared': True}})
          return net['id']
  
      def _create_segment(self, network_id):
          seg = self.segments_plugin.create_segment(
              self.ctx,
              {'segment': {'network_id': network_id,
                           'name': None, 'description': None,
                           'physical_network': 'physnet1',
                           'network_type': 'vlan',
                           'segmentation_id': constants.ATTR_NOT_SPECIFIED}})
          return seg['id']
  
      def test_scheduler_one_agents_per_network(self):
          net_id = self._create_network()
          helpers.register_dhcp_agent(HOST_C)
          self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
                                                 {'id': net_id})
          agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
                                                                [net_id])
          self.assertEqual(1, len(agents))
  
      def test_scheduler_two_agents_per_network(self):
          cfg.CONF.set_override('dhcp_agents_per_network', 2)
          net_id = self._create_network()
          helpers.register_dhcp_agent(HOST_C)
          helpers.register_dhcp_agent(HOST_D)
          self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
                                                 {'id': net_id})
          agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
                                                                [net_id])
          self.assertEqual(2, len(agents))
  
      def test_scheduler_no_active_agents(self):
          net_id = self._create_network()
          self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
                                                 {'id': net_id})
          agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
                                                                [net_id])
          self.assertEqual(0, len(agents))
  
      def test_scheduler_equal_distribution(self):
          net_id_1 = self._create_network()
          net_id_2 = self._create_network()
          net_id_3 = self._create_network()
          helpers.register_dhcp_agent(HOST_C)
          helpers.register_dhcp_agent(HOST_D, networks=1)
          self.plugin.network_scheduler.schedule(
              self.plugin, context.get_admin_context(), {'id': net_id_1})
          helpers.register_dhcp_agent(HOST_D, networks=2)
          self.plugin.network_scheduler.schedule(
              self.plugin, context.get_admin_context(), {'id': net_id_2})
          helpers.register_dhcp_agent(HOST_C, networks=4)
          self.plugin.network_scheduler.schedule(
              self.plugin, context.get_admin_context(), {'id': net_id_3})
          agent1 = self.plugin.get_dhcp_agents_hosting_networks(
              self.ctx, [net_id_1])
          agent2 = self.plugin.get_dhcp_agents_hosting_networks(
              self.ctx, [net_id_2])
          agent3 = self.plugin.get_dhcp_agents_hosting_networks(
              self.ctx, [net_id_3])
          self.assertEqual('host-c', agent1[0]['host'])
          self.assertEqual('host-c', agent2[0]['host'])
          self.assertEqual('host-d', agent3[0]['host'])
  
      def _get_network_with_candidate_hosts(self, net_id, seg_id):
          # expire the session so that the segment is fully reloaded on fetch,
          # including its new host mapping
          self.ctx.session.expire_all()
          net = self.plugin.get_network(self.ctx, net_id)
          seg = self.segments_plugin.get_segment(self.ctx, seg_id)
          net['candidate_hosts'] = seg['hosts']
          return net
  
      def test_schedule_segment_one_hostable_agent(self):
          net_id = self._create_network()
          seg_id = self._create_segment(net_id)
          helpers.register_dhcp_agent(HOST_C)
          helpers.register_dhcp_agent(HOST_D)
          segments_service_db.update_segment_host_mapping(
              self.ctx, HOST_C, {seg_id})
          net = self._get_network_with_candidate_hosts(net_id, seg_id)
          agents = self.plugin.network_scheduler.schedule(
              self.plugin, self.ctx, net)
          self.assertEqual(1, len(agents))
          self.assertEqual(HOST_C, agents[0].host)
  
      def test_schedule_segment_many_hostable_agents(self):
          net_id = self._create_network()
          seg_id = self._create_segment(net_id)
          helpers.register_dhcp_agent(HOST_C)
          helpers.register_dhcp_agent(HOST_D)
          segments_service_db.update_segment_host_mapping(
              self.ctx, HOST_C, {seg_id})
          segments_service_db.update_segment_host_mapping(
              self.ctx, HOST_D, {seg_id})
          net = self._get_network_with_candidate_hosts(net_id, seg_id)
          agents = self.plugin.network_scheduler.schedule(
              self.plugin, self.ctx, net)
          self.assertEqual(1, len(agents))
          self.assertIn(agents[0].host, [HOST_C, HOST_D])
  
      def test_schedule_segment_no_host_mapping(self):
          net_id = self._create_network()
          seg_id = self._create_segment(net_id)
          helpers.register_dhcp_agent(HOST_C)
          helpers.register_dhcp_agent(HOST_D)
          net = self.plugin.get_network(self.ctx, net_id)
          seg = self.segments_plugin.get_segment(self.ctx, seg_id)
          net['candidate_hosts'] = seg['hosts']
          agents = self.plugin.network_scheduler.schedule(
              self.plugin, self.ctx, net)
          self.assertEqual(0, len(agents))
  
      def test_schedule_segment_two_agents_per_segment(self):
          cfg.CONF.set_override('dhcp_agents_per_network', 2)
          net_id = self._create_network()
          seg_id = self._create_segment(net_id)
          helpers.register_dhcp_agent(HOST_C)
          helpers.register_dhcp_agent(HOST_D)
          segments_service_db.update_segment_host_mapping(
              self.ctx, HOST_C, {seg_id})
          segments_service_db.update_segment_host_mapping(
              self.ctx, HOST_D, {seg_id})
          net = self._get_network_with_candidate_hosts(net_id, seg_id)
          agents = self.plugin.network_scheduler.schedule(
              self.plugin, self.ctx, net)
          self.assertEqual(2, len(agents))
          self.assertIn(agents[0].host, [HOST_C, HOST_D])
          self.assertIn(agents[1].host, [HOST_C, HOST_D])
  
      def test_schedule_segment_two_agents_per_segment_one_hostable_agent(self):
          cfg.CONF.set_override('dhcp_agents_per_network', 2)
          net_id = self._create_network()
          seg_id = self._create_segment(net_id)
          helpers.register_dhcp_agent(HOST_C)
          helpers.register_dhcp_agent(HOST_D)
          segments_service_db.update_segment_host_mapping(
              self.ctx, HOST_C, {seg_id})
          net = self._get_network_with_candidate_hosts(net_id, seg_id)
          agents = self.plugin.network_scheduler.schedule(
              self.plugin, self.ctx, net)
          self.assertEqual(1, len(agents))
          self.assertEqual(HOST_C, agents[0].host)
  
  
  class TestDhcpSchedulerFilter(TestDhcpSchedulerBaseTestCase,
                                sched_db.DhcpAgentSchedulerDbMixin):
      def _test_get_dhcp_agents_hosting_networks(self, expected, **kwargs):
          agents = self._create_and_set_agents_down(['host-a', 'host-b'], 1)
          agents += self._create_and_set_agents_down(['host-c', 'host-d'], 1,
                                                     admin_state_up=False)
          networks = kwargs.pop('networks', [self.network_id])
          for network in networks:
              self._test_schedule_bind_network(agents, network)
          agents = self.get_dhcp_agents_hosting_networks(self.ctx,
                                                         networks,
                                                         **kwargs)
          host_ids = set(a['host'] for a in agents)
          self.assertEqual(expected, host_ids)
  
      def test_get_dhcp_agents_hosting_networks_default(self):
          self._test_get_dhcp_agents_hosting_networks({'host-a', 'host-b',
                                                       'host-c', 'host-d'})
  
      def test_get_dhcp_agents_hosting_networks_active(self):
          self._test_get_dhcp_agents_hosting_networks({'host-b', 'host-d'},
                                                      active=True)
  
      def test_get_dhcp_agents_hosting_networks_admin_up(self):
          self._test_get_dhcp_agents_hosting_networks({'host-a', 'host-b'},
                                                      admin_state_up=True)
  
      def test_get_dhcp_agents_hosting_networks_active_admin_up(self):
          self._test_get_dhcp_agents_hosting_networks({'host-b'},
                                                      active=True,
                                                      admin_state_up=True)
  
      def test_get_dhcp_agents_hosting_networks_admin_down(self):
          self._test_get_dhcp_agents_hosting_networks({'host-c', 'host-d'},
                                                      admin_state_up=False)
  
      def test_get_dhcp_agents_hosting_networks_active_admin_down(self):
          self._test_get_dhcp_agents_hosting_networks({'host-d'},
                                                      active=True,
                                                      admin_state_up=False)
  
      def test_get_dhcp_agents_hosting_many_networks(self):
          net_id = uuidutils.generate_uuid()
          self._save_networks([net_id])
          networks = [net_id, self.network_id]
          self._test_get_dhcp_agents_hosting_networks({'host-a', 'host-b',
                                                       'host-c', 'host-d'},
                                                      networks=networks)
  
      def test_get_dhcp_agents_host_network_filter_by_hosts(self):
          self._test_get_dhcp_agents_hosting_networks({'host-a'},
                                                      hosts=['host-a'])
  
  
  class DHCPAgentAZAwareWeightSchedulerTestCase(TestDhcpSchedulerBaseTestCase):
  
      def setUp(self):
          super(DHCPAgentAZAwareWeightSchedulerTestCase, self).setUp()
          self.setup_coreplugin('ml2')
          cfg.CONF.set_override("network_scheduler_driver",
              'neutron.scheduler.dhcp_agent_scheduler.AZAwareWeightScheduler')
          self.plugin = importutils.import_object('neutron.plugins.ml2.plugin.'
                                                  'Ml2Plugin')
          mock.patch.object(
              self.plugin, 'filter_hosts_with_network_access',
              side_effect=lambda context, network_id, hosts: hosts).start()
          cfg.CONF.set_override('dhcp_agents_per_network', 1)
          cfg.CONF.set_override("dhcp_load_type", "networks")
  
      def test_az_scheduler_one_az_hints(self):
          net_id = uuidutils.generate_uuid()
          self._save_networks([net_id])
          helpers.register_dhcp_agent('az1-host1', networks=1, az='az1')
          helpers.register_dhcp_agent('az1-host2', networks=2, az='az1')
          helpers.register_dhcp_agent('az2-host1', networks=3, az='az2')
          helpers.register_dhcp_agent('az2-host2', networks=4, az='az2')
          self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
              {'id': net_id, 'availability_zone_hints': ['az2']})
          agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
                                                                [net_id])
          self.assertEqual(1, len(agents))
          self.assertEqual('az2-host1', agents[0]['host'])
  
      def test_az_scheduler_default_az_hints(self):
          net_id = uuidutils.generate_uuid()
          cfg.CONF.set_override('default_availability_zones', ['az1'])
          self._save_networks([net_id])
          helpers.register_dhcp_agent('az1-host1', networks=1, az='az1')
          helpers.register_dhcp_agent('az1-host2', networks=2, az='az1')
          helpers.register_dhcp_agent('az2-host1', networks=3, az='az2')
          helpers.register_dhcp_agent('az2-host2', networks=4, az='az2')
          self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
              {'id': net_id, 'availability_zone_hints': []})
          agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
                                                                [net_id])
          self.assertEqual(1, len(agents))
          self.assertEqual('az1-host1', agents[0]['host'])
  
      def test_az_scheduler_two_az_hints(self):
          net_id = uuidutils.generate_uuid()
          cfg.CONF.set_override('dhcp_agents_per_network', 2)
          self._save_networks([net_id])
          helpers.register_dhcp_agent('az1-host1', networks=1, az='az1')
          helpers.register_dhcp_agent('az1-host2', networks=2, az='az1')
          helpers.register_dhcp_agent('az2-host1', networks=3, az='az2')
          helpers.register_dhcp_agent('az2-host2', networks=4, az='az2')
          helpers.register_dhcp_agent('az3-host1', networks=5, az='az3')
          helpers.register_dhcp_agent('az3-host2', networks=6, az='az3')
          self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
              {'id': net_id, 'availability_zone_hints': ['az1', 'az3']})
          agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
                                                                [net_id])
          self.assertEqual(2, len(agents))
          expected_hosts = set(['az1-host1', 'az3-host1'])
          hosts = set([a['host'] for a in agents])
          self.assertEqual(expected_hosts, hosts)
  
      def test_az_scheduler_two_az_hints_one_available_az(self):
          net_id = uuidutils.generate_uuid()
          cfg.CONF.set_override('dhcp_agents_per_network', 2)
          self._save_networks([net_id])
          helpers.register_dhcp_agent('az1-host1', networks=1, az='az1')
          helpers.register_dhcp_agent('az1-host2', networks=2, az='az1')
          helpers.register_dhcp_agent('az2-host1', networks=3, alive=False,
                                      az='az2')
          helpers.register_dhcp_agent('az2-host2', networks=4,
                                      admin_state_up=False, az='az2')
          self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
              {'id': net_id, 'availability_zone_hints': ['az1', 'az2']})
          agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
                                                                [net_id])
          self.assertEqual(2, len(agents))
          expected_hosts = set(['az1-host1', 'az1-host2'])
          hosts = set([a['host'] for a in agents])
          self.assertEqual(expected_hosts, hosts)
  
      def _test_az_scheduler_no_az_hints(self, multiple_agent=False):
          net_id = uuidutils.generate_uuid()
          num_agent = 2 if multiple_agent else 1
          cfg.CONF.set_override('dhcp_agents_per_network', num_agent)
          self._save_networks([net_id])
          helpers.register_dhcp_agent('az1-host1', networks=2, az='az1')
          helpers.register_dhcp_agent('az1-host2', networks=3, az='az1')
          helpers.register_dhcp_agent('az2-host1', networks=2, az='az2')
          helpers.register_dhcp_agent('az2-host2', networks=1, az='az2')
          self.plugin.network_scheduler.schedule(self.plugin, self.ctx,
              {'id': net_id, 'availability_zone_hints': []})
          agents = self.plugin.get_dhcp_agents_hosting_networks(self.ctx,
                                                                [net_id])
          self.assertEqual(num_agent, len(agents))
          if multiple_agent:
              expected_hosts = set(['az1-host1', 'az2-host2'])
          else:
              expected_hosts = set(['az2-host2'])
          hosts = {a['host'] for a in agents}
          self.assertEqual(expected_hosts, hosts)
  
      def test_az_scheduler_no_az_hints_multiple_agent(self):
          self._test_az_scheduler_no_az_hints(multiple_agent=True)
  
      def test_az_scheduler_no_az_hints_one_agent(self):
          self._test_az_scheduler_no_az_hints()
  
      def test_az_scheduler_select_az_with_least_weight(self):
          self._save_networks([uuidutils.generate_uuid()])
          dhcp_agents = []
          # Register 6 dhcp agents in 3 AZs, every AZ will have 2 agents.
          dhcp_agents.append(
              helpers.register_dhcp_agent('az1-host1', networks=6, az='az1'))
          dhcp_agents.append(
              helpers.register_dhcp_agent('az1-host2', networks=5, az='az1'))
          dhcp_agents.append(
              helpers.register_dhcp_agent('az2-host1', networks=4, az='az2'))
          dhcp_agents.append(
              helpers.register_dhcp_agent('az2-host2', networks=3, az='az2'))
          dhcp_agents.append(
              helpers.register_dhcp_agent('az3-host1', networks=2, az='az3'))
          dhcp_agents.append(
              helpers.register_dhcp_agent('az3-host2', networks=1, az='az3'))
  
          # Try multiple times to verify that the select of AZ scheduler will
          # output stably.
          for i in range(3):
              # Shuffle the agents
              random.shuffle(dhcp_agents)
              # Select agents with empty resource_hosted_agents. This means each
              # AZ will have same amount of agents scheduled (0 in this case)
              agents_select = self.plugin.network_scheduler.select(
                  self.plugin, self.ctx, dhcp_agents, [], 2)
  
              self.assertEqual(2, len(agents_select))
              # The agent and az with least weight should always be selected
              # first
              self.assertEqual('az3-host2', agents_select[0]['host'])
              self.assertEqual('az3', agents_select[0]['availability_zone'])
              # The second selected agent should be the agent with least weight,
              # which is also not in the same az as the first selected agent.
              self.assertEqual('az2-host2', agents_select[1]['host'])
              self.assertEqual('az2', agents_select[1]['availability_zone'])
  