neutron/neutron/tests/functional/scheduler/test_l3_agent_scheduler.py

552 lines
20 KiB
Python

# 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
import testscenarios
from neutron import context
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
# 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()
self.l3_plugin = l3_router_plugin.L3RouterPlugin()
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):
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', self.l3_plugin,
(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.adminContext, router['id'], agent)
hosting_agents.append(agent)
return hosting_agents
def _test_auto_schedule(self, expected_count):
router_ids = [rtr['id'] for rtr in self.routers]
did_it_schedule = False
# Try scheduling on each host
for host in self.hosts:
did_it_schedule = self.scheduler.auto_schedule_routers(
self.l3_plugin,
self.adminContext,
host,
router_ids)
if did_it_schedule:
break
if expected_count:
self.assertTrue(did_it_schedule, 'Failed to schedule agent')
else:
self.assertFalse(did_it_schedule, '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):
core_plugin = 'neutron.plugins.ml2.plugin.Ml2Plugin'
super(L3AZSchedulerBaseTest, self).setUp(plugin=core_plugin)
self.l3_plugin = l3_router_plugin.L3RouterPlugin()
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.
min_l3_agents_per_router
Minimum number of agents on which a router will be scheduled.
N/A for regular router test.
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,
min_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,
min_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,
min_l3_agents_per_router=2,
down_agent_count=[0, 0],
expected_scheduled_agent_count=[2, 1])),
('HA router, not enough agents',
dict(az_count=3,
router_az_hints=2,
agent_count=[2, 2, 2],
max_l3_agents_per_router=3,
min_l3_agents_per_router=2,
down_agent_count=[1, 1, 0],
expected_scheduled_agent_count=[1, 1, 0])),
]
def test_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)
self.config(min_l3_agents_per_router=self.min_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)
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.
min_l3_agents_per_router
Minimum number of agents on which a router will be scheduled.
N/A for regular router test.
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,
min_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,
min_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,
min_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,
min_l3_agents_per_router=2,
down_agent_count=[0, 0, 1],
scheduled_agent_count=[0, 0, 0],
expected_scheduled_agent_count=[0, 0, 0])),
('HA router, partial 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,
min_l3_agents_per_router=2,
down_agent_count=[0, 1, 0],
scheduled_agent_count=[1, 0, 0],
expected_scheduled_agent_count=[1, 1, 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)
self.config(min_l3_agents_per_router=self.min_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.adminContext, router['id'], agent)
# 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'], None)
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))