# License for the specific language governing permissions and limitations
# under the License.
+import collections
import six
import testscenarios
This stores the expected networks that should have been scheduled
(or that could have already been scheduled) for each agent
after the 'auto_schedule_networks' function is called.
+
+ no_network_with_az_match
+ If this parameter is True, there is no unscheduled network with
+ availability_zone_hints matches to an availability_zone of agents
+ to be scheduled. The default is False.
"""
scenarios = [
expected_auto_schedule_return_value=False,
expected_hosted_networks={'agent-0': [],
'agent-1': []})),
+
+ ('No agents scheduled if unscheduled network does not match AZ',
+ dict(agent_count=1,
+ max_agents_per_network=1,
+ network_count=1,
+ networks_with_dhcp_disabled=[],
+ hosted_networks={},
+ expected_auto_schedule_return_value=True,
+ expected_hosted_networks={'agent-0': []},
+ no_network_with_az_match=True)),
]
def _strip_host_index(self, name):
return subnets
def get_network(self, context, net_id):
- # TODO(hichihara): add test cases of AZ scheduler
- return {'availability_zone_hints': []}
+ az_hints = []
+ if getattr(self, 'no_network_with_az_match', False):
+ az_hints = ['not-match']
+ return {'availability_zone_hints': az_hints}
def _get_hosted_networks_on_dhcp_agent(self, agent_id):
query = self.ctx.session.query(
def test_auto_schedule(self):
for i in range(self.agent_count):
self._test_auto_schedule(i)
+
+
+class TestAZAwareWeightScheduler(test_dhcp_sch.TestDhcpSchedulerBaseTestCase,
+ agentschedulers_db.DhcpAgentSchedulerDbMixin,
+ agents_db.AgentDbMixin,
+ common_db_mixin.CommonDbMixin):
+ """Test various scenarios for AZAwareWeightScheduler.schedule.
+
+ az_count
+ Number of AZs.
+
+ network_az_hints
+ Number of AZs in availability_zone_hints of the network.
+
+ agent_count[each az]
+ Number of dhcp agents (also number of hosts).
+
+ max_agents_per_network
+ Maximum DHCP Agents that can be scheduled for a network.
+
+ scheduled_agent_count[each az]
+ Number of agents the network has previously scheduled
+
+ down_agent_count[each az]
+ Number of dhcp agents which are down
+
+ expected_scheduled_agent_count[each az]
+ Number of scheduled agents the schedule() should return
+ or 'None' if the schedule() cannot schedule the network.
+ """
+
+ scenarios = [
+ ('Single hint, Single agent, Scheduled an agent of the specified AZ',
+ dict(az_count=2,
+ network_az_hints=1,
+ agent_count=[1, 1],
+ max_agents_per_network=1,
+ scheduled_agent_count=[0, 0],
+ down_agent_count=[0, 0],
+ expected_scheduled_agent_count=[1, 0])),
+
+ ('Multi hints, Multi agents Scheduled agents of the specified AZs',
+ dict(az_count=3,
+ network_az_hints=2,
+ agent_count=[1, 1, 1],
+ max_agents_per_network=2,
+ scheduled_agent_count=[0, 0, 0],
+ down_agent_count=[0, 0, 0],
+ expected_scheduled_agent_count=[1, 1, 0])),
+
+ ('Single hint, Multi agents, Scheduled agents of the specified AZ',
+ dict(az_count=2,
+ network_az_hints=1,
+ agent_count=[2, 1],
+ max_agents_per_network=2,
+ scheduled_agent_count=[0, 0],
+ down_agent_count=[0, 0],
+ expected_scheduled_agent_count=[2, 0])),
+
+ ('Multi hints, Multi agents, Only single AZ available',
+ dict(az_count=2,
+ network_az_hints=2,
+ agent_count=[2, 1],
+ max_agents_per_network=2,
+ scheduled_agent_count=[0, 0],
+ down_agent_count=[0, 1],
+ expected_scheduled_agent_count=[2, 0])),
+
+ ('Multi hints, Multi agents, Not enough agents',
+ dict(az_count=3,
+ network_az_hints=3,
+ agent_count=[1, 1, 1],
+ max_agents_per_network=3,
+ scheduled_agent_count=[0, 0, 0],
+ down_agent_count=[0, 1, 0],
+ expected_scheduled_agent_count=[1, 0, 1])),
+
+ ('Multi hints, Multi agents, Partially scheduled, Another AZ selected',
+ dict(az_count=3,
+ network_az_hints=2,
+ agent_count=[1, 1, 1],
+ max_agents_per_network=2,
+ scheduled_agent_count=[1, 0, 0],
+ down_agent_count=[0, 0, 0],
+ expected_scheduled_agent_count=[0, 1, 0])),
+
+ ('No hint, Scheduled independent to AZ',
+ dict(az_count=3,
+ network_az_hints=0,
+ agent_count=[1, 1, 1],
+ max_agents_per_network=3,
+ scheduled_agent_count=[0, 0, 0],
+ down_agent_count=[0, 0, 0],
+ expected_scheduled_agent_count=[1, 1, 1])),
+ ]
+
+ def _set_network_az_hints(self):
+ self.network['availability_zone_hints'] = []
+ for i in range(self.network_az_hints):
+ self.network['availability_zone_hints'].append('az%s' % i)
+
+ def test_schedule_network(self):
+ self.config(dhcp_agents_per_network=self.max_agents_per_network)
+ scheduler = dhcp_agent_scheduler.AZAwareWeightScheduler()
+ self._set_network_az_hints()
+
+ # create dhcp agents
+ for i in range(self.az_count):
+ az = 'az%s' % i
+ hosts = ['%s-host-%s' % (az, j)
+ for j in range(self.agent_count[i])]
+ dhcp_agents = self._create_and_set_agents_down(
+ hosts, down_agent_count=self.down_agent_count[i], az=az)
+
+ active_agents = dhcp_agents[self.down_agent_count[i]:]
+
+ # schedule some agents before calling schedule
+ if self.scheduled_agent_count[i]:
+ # schedule the network
+ schedule_agents = active_agents[:self.scheduled_agent_count[i]]
+ scheduler.resource_filter.bind(
+ self.ctx, schedule_agents, self.network_id)
+
+ actual_scheduled_agents = scheduler.schedule(self, self.ctx,
+ self.network)
+ scheduled_azs = collections.defaultdict(int)
+ for agent in actual_scheduled_agents:
+ scheduled_azs[agent['availability_zone']] += 1
+
+ hosted_agents = self.list_dhcp_agents_hosting_network(
+ self.ctx, self.network_id)
+ hosted_azs = collections.defaultdict(int)
+ for agent in hosted_agents['agents']:
+ hosted_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))
+ self.assertEqual(self.scheduled_agent_count[i] +
+ scheduled_azs.get('az%s' % i, 0),
+ hosted_azs.get('az%s' % i, 0))
# License for the specific language governing permissions and limitations
# under the License.
+import collections
import random
import testscenarios
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}
+ 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))