# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
# 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 collections
import random

from neutron_lib import constants
from neutron_lib import context
from neutron_lib.plugins import constants as plugin_constants
from neutron_lib.plugins import directory
from oslo_utils import uuidutils
import testscenarios

from neutron.objects import network as net_obj
from neutron.scheduler import l3_agent_scheduler
from neutron.services.l3_router import l3_router_plugin
from neutron.tests.common import helpers
from neutron.tests.unit.db import test_db_base_plugin_v2

_uuid = uuidutils.generate_uuid

PLUGIN_NAME = 'neutron.plugins.ml2.plugin.Ml2Plugin'

# Required to generate tests from scenarios. Not compatible with nose.
load_tests = testscenarios.load_tests_apply_scenarios


class L3SchedulerBaseTest(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):

    """Base class for functional test of L3 schedulers.
       Provides basic setup and utility functions.
    """

    def setUp(self):
        super(L3SchedulerBaseTest, self).setUp(PLUGIN_NAME)

        self.l3_plugin = l3_router_plugin.L3RouterPlugin()
        directory.add_plugin(plugin_constants.L3, self.l3_plugin)
        self.adminContext = context.get_admin_context()
        self.adminContext.tenant_id = _uuid()

    def _create_l3_agent(self, host, context, agent_mode='legacy',
                         state=True, ext_net_id=''):
        agent = helpers.register_l3_agent(host, agent_mode)
        helpers.set_agent_admin_state(agent.id, state)
        return agent

    def _create_router(self, name):
        router = {'name': name, 'admin_state_up': True,
                  'tenant_id': self.adminContext.tenant_id}
        return self.l3_plugin.create_router(
            self.adminContext, {'router': router})

    def _create_legacy_agents(self, agent_count, down_agent_count):
        # Creates legacy l3 agents and sets admin state based on
        #  down agent count.
        self.hosts = ['host-%s' % i for i in range(agent_count)]
        self.l3_agents = [self._create_l3_agent(self.hosts[i],
               self.adminContext, 'legacy',
               (i >= down_agent_count)) for i in range(agent_count)]

    def _create_routers(self, scheduled_router_count,
                        expected_scheduled_router_count):
        routers = []
        if (scheduled_router_count + expected_scheduled_router_count):
            for i in range(scheduled_router_count +
                           expected_scheduled_router_count):
                router = self._create_router('schd_rtr' + str(i))
                routers.append(router)
        else:
            # create at least one router to test scheduling
            routers.append(self._create_router('schd_rtr0'))

        return routers

    def _pre_scheduler_routers(self, scheduler, count):
        hosting_agents = []
        # schedule routers before calling schedule:
        for i in range(count):
            router = self.routers[i]
            agent = random.choice(self.l3_agents)
            scheduler.bind_router(self.l3_plugin, self.adminContext,
                                  router['id'], agent.id)
            hosting_agents.append(agent)
        return hosting_agents

    def _test_auto_schedule(self, expected_count):
        router_ids = [rtr['id'] for rtr in self.routers]

        hosting_before = self.l3_plugin.get_l3_agents_hosting_routers(
            self.adminContext, router_ids)

        # Try scheduling on each host
        for host in self.hosts:
            self.scheduler.auto_schedule_routers(
                self.l3_plugin,
                self.adminContext,
                host)

        hosting_after = self.l3_plugin.get_l3_agents_hosting_routers(
            self.adminContext, router_ids)

        if expected_count:
            self.assertNotEqual(hosting_before, hosting_after,
                                'Failed to schedule agent')
        else:
            self.assertEqual(hosting_before, hosting_after,
                             'Agent scheduled, not expected')


class L3ChanceSchedulerTestCase(L3SchedulerBaseTest):

    """Test various scenarios for chance scheduler.

        agent_count
            Number of l3 agents (also number of hosts).

        down_agent_count
            Number of l3 agents which are down.

        scheduled_router_count
            Number of routers that have been previously scheduled.

        expected_scheduled_router_count
            Number of newly scheduled routers.
    """

    scenarios = [
        ('No routers scheduled if no agents are present',
         dict(agent_count=0,
              down_agent_count=0,
              scheduled_router_count=0,
              expected_scheduled_router_count=0)),

        ('No routers scheduled if it is already hosted',
         dict(agent_count=1,
              down_agent_count=0,
              scheduled_router_count=1,
              expected_scheduled_router_count=0)),

        ('No routers scheduled if all agents are down',
         dict(agent_count=2,
              down_agent_count=2,
              scheduled_router_count=0,
              expected_scheduled_router_count=0)),

        ('Router scheduled to the agent if router is not yet hosted',
         dict(agent_count=1,
              down_agent_count=0,
              scheduled_router_count=0,
              expected_scheduled_router_count=1)),

        ('Router scheduled to the agent even if it already hosts a router',
         dict(agent_count=1,
              down_agent_count=0,
              scheduled_router_count=1,
              expected_scheduled_router_count=1)),
    ]

    def setUp(self):
        super(L3ChanceSchedulerTestCase, self).setUp()
        self._create_legacy_agents(self.agent_count, self.down_agent_count)
        self.routers = self._create_routers(self.scheduled_router_count,
                             self.expected_scheduled_router_count)
        self.scheduler = l3_agent_scheduler.ChanceScheduler()

    def test_chance_schedule_router(self):
        # Pre schedule routers
        self._pre_scheduler_routers(self.scheduler,
                                    self.scheduled_router_count)
        # schedule:
        actual_scheduled_agent = self.scheduler.schedule(
            self.l3_plugin, self.adminContext, self.routers[-1]['id'])

        if self.expected_scheduled_router_count:
            self.assertIsNotNone(actual_scheduled_agent,
                                 message='Failed to schedule agent')
        else:
            self.assertIsNone(actual_scheduled_agent,
                              message='Agent scheduled but not expected')

    def test_auto_schedule_routers(self):
        # Pre schedule routers
        self._pre_scheduler_routers(self.scheduler,
                                    self.scheduled_router_count)
        # The test
        self._test_auto_schedule(self.expected_scheduled_router_count)


class L3LeastRoutersSchedulerTestCase(L3SchedulerBaseTest):

    """Test various scenarios for least router scheduler.

        agent_count
            Number of l3 agents (also number of hosts).

        down_agent_count
            Number of l3 agents which are down.

        scheduled_router_count
            Number of routers that have been previously scheduled

        expected_scheduled_router_count
            Number of newly scheduled routers
    """

    scenarios = [
        ('No routers scheduled if no agents are present',
         dict(agent_count=0,
              down_agent_count=0,
              scheduled_router_count=0,
              expected_scheduled_router_count=0)),

        ('No routers scheduled if it is already hosted',
         dict(agent_count=1,
              down_agent_count=0,
              scheduled_router_count=1,
              expected_scheduled_router_count=1)),

        ('No routers scheduled if all agents are down',
         dict(agent_count=2,
              down_agent_count=2,
              scheduled_router_count=0,
              expected_scheduled_router_count=0)),

        ('Router scheduled to the agent if router is not yet hosted',
         dict(agent_count=1,
              down_agent_count=0,
              scheduled_router_count=0,
              expected_scheduled_router_count=1)),

        ('Router scheduled to the agent even if it already hosts a router',
         dict(agent_count=1,
              down_agent_count=0,
              scheduled_router_count=1,
              expected_scheduled_router_count=1)),

        ('Router is scheduled to agent hosting least routers',
         dict(agent_count=2,
              down_agent_count=0,
              scheduled_router_count=1,
              expected_scheduled_router_count=1)),
    ]

    def setUp(self):
        super(L3LeastRoutersSchedulerTestCase, self).setUp()
        self._create_legacy_agents(self.agent_count, self.down_agent_count)
        self.routers = self._create_routers(self.scheduled_router_count,
                             self.expected_scheduled_router_count)
        self.scheduler = l3_agent_scheduler.LeastRoutersScheduler()

    def test_least_routers_schedule(self):
        # Pre schedule routers
        hosting_agents = self._pre_scheduler_routers(self.scheduler,
                                    self.scheduled_router_count)

        actual_scheduled_agent = self.scheduler.schedule(
            self.l3_plugin, self.adminContext, self.routers[-1]['id'])

        if self.expected_scheduled_router_count:
            # For case where there is just one agent:
            if self.agent_count == 1:
                self.assertEqual(actual_scheduled_agent.id,
                                 self.l3_agents[0].id)
            else:
                self.assertNotIn(actual_scheduled_agent.id,
                               [x.id for x in hosting_agents],
                               message='The expected agent was not scheduled')
        else:
            self.assertIsNone(actual_scheduled_agent,
                              message='Expected no agent to be scheduled,'
                                      ' but it got scheduled')

    def test_auto_schedule_routers(self):
        # Pre schedule routers
        self._pre_scheduler_routers(self.scheduler,
                                    self.scheduled_router_count)
        # The test
        self._test_auto_schedule(self.expected_scheduled_router_count)


class L3AZSchedulerBaseTest(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):

    def setUp(self):
        super(L3AZSchedulerBaseTest, self).setUp(plugin='ml2')

        self.l3_plugin = l3_router_plugin.L3RouterPlugin()
        directory.add_plugin(plugin_constants.L3, self.l3_plugin)
        self.l3_plugin.router_scheduler = None
        directory.add_plugin(plugin_constants.L3, self.l3_plugin)
        self.adminContext = context.get_admin_context()
        self.adminContext.tenant_id = '_func_test_tenant_'

    def _create_l3_agent(self, host, context, agent_mode='legacy', plugin=None,
                         state=True, az='nova'):
        agent = helpers.register_l3_agent(host, agent_mode, az=az)
        helpers.set_agent_admin_state(agent.id, state)
        return agent

    def _create_legacy_agents(self, agent_count, down_agent_count, az):
        # Creates legacy l3 agents and sets admin state based on
        #  down agent count.
        hosts = ['%s-host-%s' % (az, i) for i in range(agent_count)]
        l3_agents = [
            self._create_l3_agent(hosts[i], self.adminContext, 'legacy',
                                  self.l3_plugin, (i >= down_agent_count),
                                  az=az)
            for i in range(agent_count)]
        return l3_agents

    def _create_router(self, az_hints, ha):
        router = {'name': 'router1', 'admin_state_up': True,
                  'availability_zone_hints': az_hints,
                  'tenant_id': self._tenant_id}
        if ha:
            router['ha'] = True
        return self.l3_plugin.create_router(
            self.adminContext, {'router': router})


class L3AZLeastRoutersSchedulerTestCase(L3AZSchedulerBaseTest):

    """Test various scenarios for AZ router scheduler.

        az_count
            Number of AZs.

        router_az_hints
            Number of AZs in availability_zone_hints of the router.

        agent_count[each az]
            Number of l3 agents (also number of hosts).

        max_l3_agents_per_router
            Maximum number of agents on which a router will be scheduled.
            0 means test for regular router.

        down_agent_count[each az]
            Number of l3 agents which are down.

        expected_scheduled_agent_count[each az]
            Number of newly scheduled l3 agents.
    """

    scenarios = [
        ('Regular router, Scheduled specified AZ',
         dict(az_count=2,
              router_az_hints=1,
              agent_count=[1, 1],
              max_l3_agents_per_router=0,
              down_agent_count=[0, 0],
              expected_scheduled_agent_count=[1, 0])),

        ('HA router, Scheduled specified AZs',
         dict(az_count=3,
              router_az_hints=2,
              agent_count=[1, 1, 1],
              max_l3_agents_per_router=2,
              down_agent_count=[0, 0, 0],
              expected_scheduled_agent_count=[1, 1, 0])),

        ('HA router, max_l3_agents_per_routers > az_hints',
         dict(az_count=2,
              router_az_hints=2,
              agent_count=[2, 1],
              max_l3_agents_per_router=3,
              down_agent_count=[0, 0],
              expected_scheduled_agent_count=[2, 1])),
    ]

    def setUp(self):
        super(L3AZLeastRoutersSchedulerTestCase, self).setUp()
        self.scheduler = l3_agent_scheduler.AZLeastRoutersScheduler()
        self.l3_plugin.router_scheduler = self.scheduler

    def test_schedule_router(self):
        ha = False
        if self.max_l3_agents_per_router:
            self.config(max_l3_agents_per_router=self.max_l3_agents_per_router)
            ha = True

        # create l3 agents
        for i in range(self.az_count):
            az = 'az%s' % i
            self._create_legacy_agents(self.agent_count[i],
                                       self.down_agent_count[i], az)

        # create router.
        # note that ha-router needs enough agents beforehand.
        az_hints = ['az%s' % i for i in range(self.router_az_hints)]
        router = self._create_router(az_hints, ha)

        self.scheduler.schedule(self.l3_plugin, self.adminContext,
                                router['id'])
        # schedule returns only one agent. so get all agents scheduled.
        scheduled_agents = self.l3_plugin.get_l3_agents_hosting_routers(
            self.adminContext, [router['id']])

        scheduled_azs = collections.defaultdict(int)
        for agent in scheduled_agents:
            scheduled_azs[agent['availability_zone']] += 1

        for i in range(self.az_count):
            self.assertEqual(self.expected_scheduled_agent_count[i],
                             scheduled_azs.get('az%s' % i, 0))


class L3AZAutoScheduleTestCaseBase(L3AZSchedulerBaseTest):

    """Test various scenarios for AZ router scheduler.

        az_count
            Number of AZs.

        router_az_hints
            Number of AZs in availability_zone_hints of the router.

        agent_az
            AZ of newly activated l3 agent.

        agent_count[each az]
            Number of l3 agents (also number of hosts).

        max_l3_agents_per_router
            Maximum number of agents on which a router will be scheduled.
            0 means test for regular router.

        down_agent_count[each az]
            Number of l3 agents which are down.

        scheduled_agent_count[each az]
            Number of l3 agents that have been previously scheduled

        expected_scheduled_agent_count[each az]
            Number of newly scheduled l3 agents
    """

    scenarios = [
        ('Regular router, not scheduled, agent in specified AZ activated',
         dict(az_count=2,
              router_az_hints=1,
              agent_az='az0',
              agent_count=[1, 1],
              max_l3_agents_per_router=0,
              down_agent_count=[1, 1],
              scheduled_agent_count=[0, 0],
              expected_scheduled_agent_count=[1, 0])),

        ('Regular router, not scheduled, agent not in specified AZ activated',
         dict(az_count=2,
              router_az_hints=1,
              agent_az='az1',
              agent_count=[1, 1],
              max_l3_agents_per_router=0,
              down_agent_count=[1, 1],
              scheduled_agent_count=[0, 0],
              expected_scheduled_agent_count=[0, 0])),

        ('HA router, not scheduled, agent in specified AZ activated',
         dict(az_count=3,
              router_az_hints=2,
              agent_az='az1',
              agent_count=[1, 1, 1],
              max_l3_agents_per_router=2,
              down_agent_count=[0, 1, 0],
              scheduled_agent_count=[0, 0, 0],
              expected_scheduled_agent_count=[0, 1, 0])),

        ('HA router, not scheduled, agent not in specified AZ activated',
         dict(az_count=3,
              router_az_hints=2,
              agent_az='az2',
              agent_count=[1, 1, 1],
              max_l3_agents_per_router=2,
              down_agent_count=[0, 0, 1],
              scheduled_agent_count=[0, 0, 0],
              expected_scheduled_agent_count=[0, 0, 0])),

    ]

    def test_auto_schedule_router(self):
        scheduler = l3_agent_scheduler.AZLeastRoutersScheduler()
        ha = False
        if self.max_l3_agents_per_router:
            self.config(max_l3_agents_per_router=self.max_l3_agents_per_router)
            ha = True

        # create l3 agents
        l3_agents = {}
        for i in range(self.az_count):
            az = 'az%s' % i
            l3_agents[az] = self._create_legacy_agents(
                self.agent_count[i], self.down_agent_count[i], az)

        # create router.
        # note that ha-router needs enough agents beforehand.
        az_hints = ['az%s' % i for i in range(self.router_az_hints)]
        router = self._create_router(az_hints, ha)

        # schedule some agents before calling auto schedule
        for i in range(self.az_count):
            az = 'az%s' % i
            for j in range(self.scheduled_agent_count[i]):
                agent = l3_agents[az][j + self.down_agent_count[i]]
                scheduler.bind_router(self.l3_plugin, self.adminContext,
                                      router['id'], agent.id)

        # activate down agent and call auto_schedule_routers
        activate_agent = l3_agents[self.agent_az][0]
        helpers.set_agent_admin_state(activate_agent['id'],
                                      admin_state_up=True)

        scheduler.auto_schedule_routers(self.l3_plugin, self.adminContext,
                                        activate_agent['host'])

        scheduled_agents = self.l3_plugin.get_l3_agents_hosting_routers(
            self.adminContext, [router['id']])

        scheduled_azs = collections.defaultdict(int)
        for agent in scheduled_agents:
            scheduled_azs[agent['availability_zone']] += 1

        for i in range(self.az_count):
            self.assertEqual(self.expected_scheduled_agent_count[i],
                             scheduled_azs.get('az%s' % i, 0))


class L3DVRSchedulerBaseTest(L3SchedulerBaseTest):

    """Base class for functional test of DVR L3 schedulers.
       Provides basic setup and utility functions.
    """

    def setUp(self):
        super(L3DVRSchedulerBaseTest, self).setUp()

        self.default_ext_net_id = _uuid()
        self.default_ext_subnet_id = _uuid()

        self.router_ext_net_id = _uuid()
        self.router_ext_subnet_id = _uuid()

    def _create_router(self, name, distributed, ext_net_id=None):
        router = {'name': name, 'admin_state_up': True,
                  'tenant_id': self.adminContext.tenant_id,
                  'distributed': distributed}

        if ext_net_id:
            router['external_gateway_info'] = {'network_id': ext_net_id}

        return self.l3_plugin.create_router(self.adminContext,
                                            {'router': router})

    def _create_network(self, net_id, name=None, external=False):
        network_dict = {'tenant_id': self.adminContext.tenant_id,
                        'id': net_id,
                        'name': name,
                        'admin_state_up': True,
                        'shared': False,
                        'status': constants.NET_STATUS_ACTIVE}
        network = self.plugin.create_network(self.adminContext,
                                             {'network': network_dict})
        if external:
            network = net_obj.ExternalNetwork(
                self.adminContext, network_id=net_id)
            network.create()

        return network

    def _create_subnet(self, sub_id, network_id, cidr, gw_ip, name='test_sub'):
        subnet = {'tenant_id': self.adminContext.tenant_id,
                  'id': sub_id,
                  'name': name,
                  'network_id': network_id,
                  'ip_version': constants.IP_VERSION_4,
                  'cidr': cidr,
                  'enable_dhcp': False,
                  'gateway_ip': gw_ip,
                  'shared': False,
                  'allocation_pools': constants.ATTR_NOT_SPECIFIED,
                  'dns_nameservers': constants.ATTR_NOT_SPECIFIED,
                  'host_routes': constants.ATTR_NOT_SPECIFIED}

        return self.plugin.create_subnet(self.adminContext, {'subnet': subnet})


class L3DVRSchedulerTestCase(L3DVRSchedulerBaseTest):

    """Test various scenarios for L3 DVR schedulers:

        agent_mode
            L3 agent mode.

        second_agent_mode
            Second L3 agent mode for scenarios with two agents.

        agent_has_ext_network
            Is there external network on the host.

        router_is_distributed
            Is router distributed.

        router_already_hosted
            Is router already hosted.

        router_has_ext_gw
            Does router have external gateway.

        router_agent_have_same_ext_net
            Do router and agent have the same external network.

        expected_router_scheduled
            To verify do we expect router to get scheduled.
    """

    def get_scenario(agent_mode=constants.L3_AGENT_MODE_DVR_SNAT,
                     second_agent_mode=None,
                     agent_has_ext_network=False,
                     router_is_distributed=False,
                     router_already_hosted=False,
                     router_has_ext_gw=False,
                     router_agent_have_same_ext_net=False,
                     expected_router_scheduled=False):
        return dict(agent_mode=agent_mode,
            second_agent_mode=second_agent_mode,
            agent_has_ext_network=agent_has_ext_network,
            router_is_distributed=router_is_distributed,
            router_already_hosted=router_already_hosted,
            router_has_ext_gw=router_has_ext_gw,
            router_agent_have_same_ext_net=router_agent_have_same_ext_net,
            expected_router_scheduled=expected_router_scheduled)

    scenarios = [
        ('Legacy router not scheduled on dvr agent',
         get_scenario(agent_mode=constants.L3_AGENT_MODE_DVR)),

        ('Legacy router scheduled on dvr_snat agent',
         get_scenario(expected_router_scheduled=True)),

        ('Distributed router not scheduled on legacy agent',
         get_scenario(agent_mode=constants.L3_AGENT_MODE_LEGACY,
                      router_is_distributed=True)),

        ('Distributed router not scheduled on dvr agent',
         get_scenario(agent_mode=constants.L3_AGENT_MODE_DVR,
                      router_is_distributed=True)),

        ('Distributed router scheduled on dvr_snat agent',
         get_scenario(router_is_distributed=True,
                      expected_router_scheduled=True)),

        ('Already hosted legacy router not scheduled on dvr agent',
         get_scenario(agent_mode=constants.L3_AGENT_MODE_DVR,
                      router_already_hosted=True)),

        ('Already hosted legacy router not scheduled on dvr_snat agent',
         get_scenario(router_already_hosted=True)),

        ('Already hosted distributed router not scheduled on legacy agent',
         get_scenario(agent_mode=constants.L3_AGENT_MODE_LEGACY,
                      router_already_hosted=True,
                      router_is_distributed=True)),

        ('Already hosted distributed router not scheduled on dvr agent',
         get_scenario(agent_mode=constants.L3_AGENT_MODE_DVR,
                      router_is_distributed=True,
                      router_already_hosted=True)),

        ('Already hosted distributed router not scheduled on dvr_snat agent',
         get_scenario(router_is_distributed=True,
                      router_already_hosted=True)),

        ('Already hosted legacy router not scheduled on additional dvr agent',
         get_scenario(agent_mode=constants.L3_AGENT_MODE_LEGACY,
                      second_agent_mode=constants.L3_AGENT_MODE_DVR_SNAT,
                      router_already_hosted=True)),

        ('Distributed router not scheduled if it is on a different '
         'external network than the dvr_snat agent',
         get_scenario(agent_has_ext_network=True,
                      router_is_distributed=True,
                      router_has_ext_gw=True,
                      router_agent_have_same_ext_net=False)),
    ]

    def setUp(self):
        super(L3DVRSchedulerTestCase, self).setUp()

        agent_cnt = 2 if self.second_agent_mode else 1

        # create hosts for each agent
        self.hosts = ['host-%s' % i for i in range(agent_cnt)]

        # create default external network
        self._create_network(self.default_ext_net_id,
                             name='_test-ext-net', external=True)
        self._create_subnet(self.default_ext_subnet_id,
                            self.default_ext_net_id,
                            '10.10.9.0/24', '10.10.9.1',
                            '_test-ext-net-subnet')

        if self.router_has_ext_gw and not self.router_agent_have_same_ext_net:
            # for the test cases in which router and agent are not on same
            # external network, we create an external network for router
            self._create_network(self.router_ext_net_id,
                                 name='_test-ext-net2', external=True)
            self._create_subnet(self.router_ext_subnet_id,
                                self.router_ext_net_id,
                                '10.10.8.0/24', '10.10.8.1',
                                '_test-ext-net2-subnet')
        # create agents:
        self.l3_agents = [self._create_l3_agent(self.hosts[0],
            self.adminContext, self.agent_mode, True,
            self.default_ext_net_id if self.agent_has_ext_network else '')]
        if self.second_agent_mode:
            self.l3_agents.append(self._create_l3_agent(self.hosts[1],
                self.adminContext, self.second_agent_mode, True,
                self.default_ext_net_id if self.agent_has_ext_network else ''))

        # The router to schedule:
        self.router_to_schedule = self._create_router_to_schedule()

    def _create_router_to_schedule(self):
        router_to_schedule = None

        if self.router_has_ext_gw:
            if self.router_agent_have_same_ext_net:
                router_to_schedule = self._create_router('schd_rtr',
                    self.router_is_distributed,
                    self.default_ext_net_id)
            else:
                router_to_schedule = self._create_router('schd_rtr',
                    self.router_is_distributed,
                    self.router_ext_net_id)
        else:
            router_to_schedule = self._create_router('schd_rtr',
                self.router_is_distributed)

        return router_to_schedule

    def _test_schedule_router(self):
        if self.router_already_hosted:
            self.scheduler.bind_router(self.l3_plugin,
                                       self.adminContext,
                                       self.router_to_schedule['id'],
                                       self.l3_agents[0].id)

        # schedule:
        actual_scheduled_agent = self.scheduler.schedule(
                                     self.l3_plugin,
                                     self.adminContext,
                                     self.router_to_schedule['id'])

        # check for router scheduling:
        self.assertEqual(self.expected_router_scheduled,
                         bool(actual_scheduled_agent),
                         message='Failed to schedule agent')

    def _test_auto_schedule_routers(self):
        if self.router_already_hosted:
            self.scheduler.bind_router(self.l3_plugin,
                                       self.adminContext,
                                       self.router_to_schedule['id'],
                                       self.l3_agents[0].id)
        # schedule:
        hosting_before = self.l3_plugin.get_l3_agents_hosting_routers(
            self.adminContext, [self.router_to_schedule['id']])

        for host in self.hosts:
            self.scheduler.auto_schedule_routers(
                self.l3_plugin, self.adminContext, host)

        hosting_after = self.l3_plugin.get_l3_agents_hosting_routers(
            self.adminContext, [self.router_to_schedule['id']])

        if self.router_already_hosted:
            self.assertEqual(hosting_before, hosting_after,
                             'Agent pre scheduled, yet no binding found!')
        elif self.expected_router_scheduled:
            self.assertNotEqual(hosting_before, hosting_after,
                                'Agent not scheduled, not expected')
        else:
            self.assertEqual(hosting_before, hosting_after,
                             'Agent scheduled, not expected')

    def test_least_routers_schedule_router(self):
        self.scheduler = l3_agent_scheduler.LeastRoutersScheduler()
        self._test_schedule_router()

    def test_least_routers_auto_schedule_routers(self):
        self.scheduler = l3_agent_scheduler.LeastRoutersScheduler()
        self._test_auto_schedule_routers()

    def test_chance_schedule_router(self):
        self.scheduler = l3_agent_scheduler.ChanceScheduler()
        self._test_schedule_router()

    def test_chance_auto_schedule_routers(self):
        self.scheduler = l3_agent_scheduler.ChanceScheduler()
        self._test_auto_schedule_routers()
