Add availability_zone support for router

This patch adds the availability_zone support for router.

APIImpact
DocImpact: Make router scheduler availability zone aware. If multiple
availability zones are used, set router_scheduler_driver =
neutron.scheduler.l3_agent_scheduler.AZLeastRoutersScheduler. This scheduler
selects agent depends on LeastRoutersScheduler logic within an availability
zone so that considers the weight of agent.

Change-Id: Id26d9494b9a5b459767e93a850f47a3b014b11bb
Co-Authored-By: IWAMOTO Toshihiro <iwamoto@valinux.co.jp>
Partially-implements: blueprint add-availability-zone
This commit is contained in:
Hirofumi Ichihara 2015-12-03 14:12:19 +09:00
parent 1b6cced0d4
commit ef2a5543cc
14 changed files with 512 additions and 24 deletions

View File

@ -0,0 +1,43 @@
#
# 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.
from neutron.common import utils
from neutron.db import l3_attrs_db
from neutron.extensions import availability_zone as az_ext
class RouterAvailabilityZoneMixin(l3_attrs_db.ExtraAttributesMixin):
"""Mixin class to enable router's availability zone attributes."""
extra_attributes = [{'name': az_ext.AZ_HINTS, 'default': "[]"}]
def _extend_extra_router_dict(self, router_res, router_db):
super(RouterAvailabilityZoneMixin, self)._extend_extra_router_dict(
router_res, router_db)
if not utils.is_extension_supported(self, 'router_availability_zone'):
return
router_res[az_ext.AZ_HINTS] = az_ext.convert_az_string_to_list(
router_res[az_ext.AZ_HINTS])
router_res['availability_zones'] = (
self.get_router_availability_zones(router_db['id']))
def _process_extra_attr_router_create(
self, context, router_db, router_req):
if az_ext.AZ_HINTS in router_req:
self.validate_availability_zones(context, 'router',
router_req[az_ext.AZ_HINTS])
router_req[az_ext.AZ_HINTS] = az_ext.convert_az_list_to_string(
router_req[az_ext.AZ_HINTS])
super(RouterAvailabilityZoneMixin,
self)._process_extra_attr_router_create(context, router_db,
router_req)

View File

@ -31,9 +31,11 @@ from neutron.common import utils as n_utils
from neutron import context as n_ctx
from neutron.db import agents_db
from neutron.db import agentschedulers_db
from neutron.db import api as db_api
from neutron.db import l3_attrs_db
from neutron.db import model_base
from neutron.extensions import l3agentscheduler
from neutron.extensions import router_availability_zone as router_az
from neutron import manager
from neutron.plugins.common import constants as service_constants
@ -546,3 +548,18 @@ class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
RouterL3AgentBinding.l3_agent_id).order_by('count')
res = query.filter(agents_db.Agent.id.in_(agent_ids)).first()
return res[0]
class AZL3AgentSchedulerDbMixin(L3AgentSchedulerDbMixin,
router_az.RouterAvailabilityZonePluginBase):
"""Mixin class to add availability_zone supported l3 agent scheduler."""
def get_router_availability_zones(self, router_id):
session = db_api.get_session()
with session.begin():
query = session.query(agents_db.Agent.availability_zone)
query = query.join(RouterL3AgentBinding)
query = query.filter(
RouterL3AgentBinding.router_id == router_id)
query = query.group_by(agents_db.Agent.availability_zone)
return [item[0] for item in query]

View File

@ -44,6 +44,8 @@ class RouterExtraAttributes(model_base.BASEV2):
server_default=sa.sql.false(),
nullable=False)
ha_vr_id = sa.Column(sa.Integer())
# Availability Zone support
availability_zone_hints = sa.Column(sa.String(255))
router = orm.relationship(
l3_db.Router,

View File

@ -27,6 +27,7 @@ from neutron.common import constants
from neutron.common import exceptions as n_exc
from neutron.common import utils as n_utils
from neutron.db import agents_db
from neutron.db.availability_zone import router as router_az_db
from neutron.db import l3_attrs_db
from neutron.db import l3_db
from neutron.db import l3_dvr_db
@ -136,11 +137,13 @@ class L3HARouterVRIdAllocation(model_base.BASEV2):
vr_id = sa.Column(sa.Integer(), nullable=False, primary_key=True)
class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin):
class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin,
router_az_db.RouterAvailabilityZoneMixin):
"""Mixin class to add high availability capability to routers."""
extra_attributes = (
l3_dvr_db.L3_NAT_with_dvr_db_mixin.extra_attributes + [
l3_dvr_db.L3_NAT_with_dvr_db_mixin.extra_attributes +
router_az_db.RouterAvailabilityZoneMixin.extra_attributes + [
{'name': 'ha', 'default': cfg.CONF.l3_ha},
{'name': 'ha_vr_id', 'default': 0}])

View File

@ -21,7 +21,7 @@ from neutron.db import l3_attrs_db
from neutron.db import l3_db
class L3_HA_scheduler_db_mixin(l3_sch_db.L3AgentSchedulerDbMixin):
class L3_HA_scheduler_db_mixin(l3_sch_db.AZL3AgentSchedulerDbMixin):
def get_ha_routers_l3_agents_count(self, context):
"""Return a map between HA routers and how many agents every
@ -43,10 +43,11 @@ class L3_HA_scheduler_db_mixin(l3_sch_db.L3AgentSchedulerDbMixin):
filter(l3_attrs_db.RouterExtraAttributes.ha == sql.true()).
group_by(binding_model.router_id).subquery())
query = (context.session.query(
l3_db.Router.id, l3_db.Router.tenant_id, sub_query.c.count).
query = (context.session.query(l3_db.Router, sub_query.c.count).
join(sub_query))
return query
return [(self._make_router_dict(router), agent_count)
for router, agent_count in query]
def get_l3_agents_ordered_by_num_routers(self, context, agent_ids):
if not agent_ids:

View File

@ -1 +1 @@
ec7fcfbf72ee
dce3ec7a25c9

View File

@ -0,0 +1,33 @@
#
# 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.
#
"""Add router availability zone
Revision ID: dce3ec7a25c9
Revises: ec7fcfbf72ee
Create Date: 2015-09-17 09:36:17.468901
"""
# revision identifiers, used by Alembic.
revision = 'dce3ec7a25c9'
down_revision = 'ec7fcfbf72ee'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('router_extra_attributes',
sa.Column('availability_zone_hints', sa.String(length=255)))

View File

@ -0,0 +1,68 @@
#
# 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 abc
import six
from neutron.api import extensions
from neutron.extensions import availability_zone as az_ext
EXTENDED_ATTRIBUTES_2_0 = {
'routers': {
az_ext.AVAILABILITY_ZONES: {'allow_post': False, 'allow_put': False,
'is_visible': True},
az_ext.AZ_HINTS: {
'allow_post': True, 'allow_put': False, 'is_visible': True,
'validate': {'type:availability_zone_hints': None},
'default': []}}
}
class Router_availability_zone(extensions.ExtensionDescriptor):
"""Router availability zone extension."""
@classmethod
def get_name(cls):
return "Router Availability Zone"
@classmethod
def get_alias(cls):
return "router_availability_zone"
@classmethod
def get_description(cls):
return "Availability zone support for router."
@classmethod
def get_updated(cls):
return "2015-01-01T10:00:00-00:00"
def get_required_extensions(self):
return ["router", "availability_zone"]
def get_extended_resources(self, version):
if version == "2.0":
return EXTENDED_ATTRIBUTES_2_0
else:
return {}
@six.add_metaclass(abc.ABCMeta)
class RouterAvailabilityZonePluginBase(object):
@abc.abstractmethod
def get_router_availability_zones(self, router_id):
"""Return availability zones which a router belongs to."""

View File

@ -14,6 +14,8 @@
# under the License.
import abc
import collections
import itertools
import random
from oslo_config import cfg
@ -28,6 +30,7 @@ from neutron.common import utils
from neutron.db import l3_agentschedulers_db
from neutron.db import l3_db
from neutron.db import l3_hamode_db
from neutron.extensions import availability_zone as az_ext
LOG = logging.getLogger(__name__)
@ -298,6 +301,10 @@ class L3Scheduler(object):
port_binding.l3_agent_id = agent['id']
self.bind_router(context, router_id, agent)
def get_ha_routers_l3_agents_counts(self, context, plugin, filters=None):
"""Return a mapping (router, # agents) matching specified filters."""
return plugin.get_ha_routers_l3_agents_count(context)
def _schedule_ha_routers_to_additional_agent(self, plugin, context, agent):
"""Bind already scheduled routers to the agent.
@ -306,18 +313,19 @@ class L3Scheduler(object):
is not yet reached.
"""
routers_agents = plugin.get_ha_routers_l3_agents_count(context)
routers_agents = self.get_ha_routers_l3_agents_counts(context, plugin,
agent)
scheduled = False
admin_ctx = context.elevated()
for router_id, tenant_id, agents in routers_agents:
for router, agents in routers_agents:
max_agents_not_reached = (
not self.max_ha_agents or agents < self.max_ha_agents)
if max_agents_not_reached:
if not self._router_has_binding(admin_ctx, router_id,
if not self._router_has_binding(admin_ctx, router['id'],
agent.id):
self.create_ha_port_and_bind(plugin, admin_ctx,
router_id, tenant_id,
router['id'],
router['tenant_id'],
agent)
scheduled = True
@ -386,3 +394,79 @@ class LeastRoutersScheduler(L3Scheduler):
ordered_agents = plugin.get_l3_agents_ordered_by_num_routers(
context, [candidate['id'] for candidate in candidates])
return ordered_agents[:num_agents]
class AZLeastRoutersScheduler(LeastRoutersScheduler):
"""Availability zone aware scheduler.
If a router is ha router, allocate L3 agents distributed AZs
according to router's az_hints.
"""
def _get_az_hints(self, router):
return (router.get(az_ext.AZ_HINTS) or
cfg.CONF.default_availability_zones)
def _get_routers_can_schedule(self, context, plugin, routers, l3_agent):
"""Overwrite L3Scheduler's method to filter by availability zone."""
target_routers = []
for r in routers:
az_hints = self._get_az_hints(r)
if not az_hints or l3_agent['availability_zone'] in az_hints:
target_routers.append(r)
if not target_routers:
return
return super(AZLeastRoutersScheduler, self)._get_routers_can_schedule(
context, plugin, target_routers, l3_agent)
def _get_candidates(self, plugin, context, sync_router):
"""Overwrite L3Scheduler's method to filter by availability zone."""
all_candidates = (
super(AZLeastRoutersScheduler, self)._get_candidates(
plugin, context, sync_router))
candidates = []
az_hints = self._get_az_hints(sync_router)
for agent in all_candidates:
if not az_hints or agent['availability_zone'] in az_hints:
candidates.append(agent)
return candidates
def get_ha_routers_l3_agents_counts(self, context, plugin, filters=None):
"""Overwrite L3Scheduler's method to filter by availability zone."""
all_routers_agents = (
super(AZLeastRoutersScheduler, self).
get_ha_routers_l3_agents_counts(context, plugin, filters))
if filters is None:
return all_routers_agents
routers_agents = []
for router, agents in all_routers_agents:
az_hints = self._get_az_hints(router)
if az_hints and filters['availability_zone'] not in az_hints:
continue
routers_agents.append((router, agents))
return routers_agents
def _choose_router_agents_for_ha(self, plugin, context, candidates):
ordered_agents = plugin.get_l3_agents_ordered_by_num_routers(
context, [candidate['id'] for candidate in candidates])
num_agents = self._get_num_of_agents_for_ha(len(ordered_agents))
# Order is kept in each az
group_by_az = collections.defaultdict(list)
for agent in ordered_agents:
az = agent['availability_zone']
group_by_az[az].append(agent)
selected_agents = []
for az, agents in itertools.cycle(group_by_az.items()):
if not agents:
continue
selected_agents.append(agents.pop(0))
if len(selected_agents) >= num_agents:
break
return selected_agents

View File

@ -53,7 +53,7 @@ class L3RouterPlugin(service_base.ServicePluginBase,
"""
supported_extension_aliases = ["dvr", "router", "ext-gw-mode",
"extraroute", "l3_agent_scheduler",
"l3-ha"]
"l3-ha", "router_availability_zone"]
@resource_registry.tracked_resources(router=l3_db.Router,
floatingip=l3_db.FloatingIP)

View File

@ -330,7 +330,8 @@ class L3NatTestCaseMixin(object):
data['router']['name'] = name
if admin_state_up:
data['router']['admin_state_up'] = admin_state_up
for arg in (('admin_state_up', 'tenant_id') + (arg_list or ())):
for arg in (('admin_state_up', 'tenant_id', 'availability_zone_hints')
+ (arg_list or ())):
# Arg must be present and not empty
if arg in kwargs:
data['router'][arg] = kwargs[arg]

View File

@ -0,0 +1,110 @@
#
# 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 six
from neutron.db.availability_zone import router as router_az_db
from neutron.db import common_db_mixin
from neutron.db import l3_agentschedulers_db
from neutron.db import l3_db
from neutron.extensions import l3
from neutron.extensions import router_availability_zone as router_az
from neutron.plugins.common import constants as service_constants
from neutron.tests.unit.extensions import test_availability_zone as test_az
from neutron.tests.unit.extensions import test_l3
class AZL3ExtensionManager(test_az.AZExtensionManager):
def get_resources(self):
return (super(AZL3ExtensionManager, self).get_resources() +
l3.L3.get_resources())
class AZRouterTestPlugin(common_db_mixin.CommonDbMixin,
l3_db.L3_NAT_db_mixin,
router_az_db.RouterAvailabilityZoneMixin,
l3_agentschedulers_db.AZL3AgentSchedulerDbMixin):
supported_extension_aliases = ["router", "l3_agent_scheduler",
"router_availability_zone"]
def get_plugin_type(self):
return service_constants.L3_ROUTER_NAT
def get_plugin_description(self):
return "L3 Routing Service Plugin for testing"
def _create_router_db(self, context, router, tenant_id):
# l3-plugin using routerextraattributes must call
# _process_extra_attr_router_create.
with context.session.begin(subtransactions=True):
router_db = super(AZRouterTestPlugin, self)._create_router_db(
context, router, tenant_id)
self._process_extra_attr_router_create(context, router_db, router)
return router_db
class TestAZRouterCase(test_az.AZTestCommon, test_l3.L3NatTestCaseMixin):
def setUp(self):
plugin = ('neutron.tests.unit.extensions.'
'test_availability_zone.AZTestPlugin')
l3_plugin = ('neutron.tests.unit.extensions.'
'test_router_availability_zone.AZRouterTestPlugin')
service_plugins = {'l3_plugin_name': l3_plugin}
self._backup()
l3.RESOURCE_ATTRIBUTE_MAP['routers'].update(
router_az.EXTENDED_ATTRIBUTES_2_0['routers'])
ext_mgr = AZL3ExtensionManager()
super(TestAZRouterCase, self).setUp(plugin=plugin, ext_mgr=ext_mgr,
service_plugins=service_plugins)
def _backup(self):
self.contents_backup = {}
for res, attrs in six.iteritems(l3.RESOURCE_ATTRIBUTE_MAP):
self.contents_backup[res] = attrs.copy()
self.addCleanup(self._restore)
def _restore(self):
l3.RESOURCE_ATTRIBUTE_MAP = self.contents_backup
def test_create_router_with_az(self):
self._register_azs()
az_hints = ['nova2']
with self.router(availability_zone_hints=az_hints) as router:
res = self._show('routers', router['router']['id'])
self.assertItemsEqual(az_hints,
res['router']['availability_zone_hints'])
def test_create_router_with_azs(self):
self._register_azs()
az_hints = ['nova2', 'nova3']
with self.router(availability_zone_hints=az_hints) as router:
res = self._show('routers', router['router']['id'])
self.assertItemsEqual(az_hints,
res['router']['availability_zone_hints'])
def test_create_router_without_az(self):
with self.router() as router:
res = self._show('routers', router['router']['id'])
self.assertEqual([], res['router']['availability_zone_hints'])
def test_create_router_with_empty_az(self):
with self.router(availability_zone_hints=[]) as router:
res = self._show('routers', router['router']['id'])
self.assertEqual([], res['router']['availability_zone_hints'])
def test_create_router_with_none_existing_az(self):
res = self._create_router(self.fmt, 'tenant_id',
availability_zone_hints=['nova4'])
self.assertEqual(404, res.status_int)

View File

@ -13,6 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import collections
import contextlib
import datetime
import uuid
@ -1632,7 +1633,7 @@ class L3DvrSchedulerTestCase(testlib_api.SqlTestCase):
class L3HAPlugin(db_v2.NeutronDbPluginV2,
l3_hamode_db.L3_HA_NAT_db_mixin,
l3_hascheduler_db.L3_HA_scheduler_db_mixin):
supported_extension_aliases = ["l3-ha"]
supported_extension_aliases = ["l3-ha", "router_availability_zone"]
class L3HATestCaseMixin(testlib_api.SqlTestCase,
@ -1657,11 +1658,14 @@ class L3HATestCaseMixin(testlib_api.SqlTestCase,
self._register_l3_agents()
def _create_ha_router(self, ha=True, tenant_id='tenant1'):
def _create_ha_router(self, ha=True, tenant_id='tenant1', az_hints=None):
self.adminContext.tenant_id = tenant_id
router = {'name': 'router1', 'admin_state_up': True}
if ha is not None:
router['ha'] = ha
if az_hints is None:
az_hints = []
router['availability_zone_hints'] = az_hints
return self.plugin.create_router(self.adminContext,
{'router': router})
@ -1682,14 +1686,13 @@ class L3_HA_scheduler_db_mixinTestCase(L3HATestCaseMixin):
router1 = self._create_ha_router()
router2 = self._create_ha_router()
router3 = self._create_ha_router(ha=False)
result = self.plugin.get_ha_routers_l3_agents_count(
self.adminContext).all()
result = self.plugin.get_ha_routers_l3_agents_count(self.adminContext)
self.assertEqual(2, len(result))
self.assertIn((router1['id'], router1['tenant_id'], 4), result)
self.assertIn((router2['id'], router2['tenant_id'], 4), result)
self.assertNotIn((router3['id'], router3['tenant_id'], mock.ANY),
result)
check_result = [(router['id'], agents) for router, agents in result]
self.assertIn((router1['id'], 4), check_result)
self.assertIn((router2['id'], 4), check_result)
self.assertNotIn((router3['id'], mock.ANY), check_result)
def test_get_ordered_l3_agents_by_num_routers(self):
# Mock scheduling so that the test can control it explicitly
@ -2027,3 +2030,120 @@ class TestGetL3AgentsWithAgentModeFilter(testlib_api.SqlTestCase,
returned_agent_modes = [self._get_agent_mode(agent)
for agent in l3_agents]
self.assertEqual(self.expected_agent_modes, returned_agent_modes)
class L3AgentAZLeastRoutersSchedulerTestCase(L3HATestCaseMixin):
def setUp(self):
super(L3AgentAZLeastRoutersSchedulerTestCase, self).setUp()
self.plugin.router_scheduler = importutils.import_object(
'neutron.scheduler.l3_agent_scheduler.AZLeastRoutersScheduler')
# Mock scheduling so that the test can control it explicitly
mock.patch.object(l3_hamode_db.L3_HA_NAT_db_mixin,
'_notify_ha_interfaces_updated').start()
def _register_l3_agents(self):
self.agent1 = helpers.register_l3_agent(host='az1-host1', az='az1')
self.agent2 = helpers.register_l3_agent(host='az1-host2', az='az1')
self.agent3 = helpers.register_l3_agent(host='az2-host1', az='az2')
self.agent4 = helpers.register_l3_agent(host='az2-host2', az='az2')
self.agent5 = helpers.register_l3_agent(host='az3-host1', az='az3')
self.agent6 = helpers.register_l3_agent(host='az3-host2', az='az3')
def test_az_scheduler_auto_schedule(self):
r1 = self._create_ha_router(ha=False, az_hints=['az1'])
self.plugin.auto_schedule_routers(self.adminContext,
'az1-host2', None)
agents = self.plugin.get_l3_agents_hosting_routers(
self.adminContext, [r1['id']])
self.assertEqual(1, len(agents))
self.assertEqual('az1-host2', agents[0]['host'])
def test_az_scheduler_auto_schedule_no_match(self):
r1 = self._create_ha_router(ha=False, az_hints=['az1'])
self.plugin.auto_schedule_routers(self.adminContext,
'az2-host1', None)
agents = self.plugin.get_l3_agents_hosting_routers(
self.adminContext, [r1['id']])
self.assertEqual(0, len(agents))
def test_az_scheduler_default_az(self):
cfg.CONF.set_override('default_availability_zones', ['az2'])
r1 = self._create_ha_router(ha=False)
r2 = self._create_ha_router(ha=False)
r3 = self._create_ha_router(ha=False)
self.plugin.schedule_router(self.adminContext, r1['id'])
self.plugin.schedule_router(self.adminContext, r2['id'])
self.plugin.schedule_router(self.adminContext, r3['id'])
agents = self.plugin.get_l3_agents_hosting_routers(
self.adminContext, [r1['id'], r2['id'], r3['id']])
self.assertEqual(3, len(agents))
expected_hosts = set(['az2-host1', 'az2-host2'])
hosts = set([a['host'] for a in agents])
self.assertEqual(expected_hosts, hosts)
def test_az_scheduler_az_hints(self):
r1 = self._create_ha_router(ha=False, az_hints=['az3'])
r2 = self._create_ha_router(ha=False, az_hints=['az3'])
r3 = self._create_ha_router(ha=False, az_hints=['az3'])
self.plugin.schedule_router(self.adminContext, r1['id'])
self.plugin.schedule_router(self.adminContext, r2['id'])
self.plugin.schedule_router(self.adminContext, r3['id'])
agents = self.plugin.get_l3_agents_hosting_routers(
self.adminContext, [r1['id'], r2['id'], r3['id']])
self.assertEqual(3, len(agents))
expected_hosts = set(['az3-host1', 'az3-host2'])
hosts = set([a['host'] for a in agents])
self.assertEqual(expected_hosts, hosts)
def test_az_scheduler_least_routers(self):
r1 = self._create_ha_router(ha=False, az_hints=['az1'])
r2 = self._create_ha_router(ha=False, az_hints=['az1'])
r3 = self._create_ha_router(ha=False, az_hints=['az1'])
r4 = self._create_ha_router(ha=False, az_hints=['az1'])
self.plugin.schedule_router(self.adminContext, r1['id'])
self.plugin.schedule_router(self.adminContext, r2['id'])
self.plugin.schedule_router(self.adminContext, r3['id'])
self.plugin.schedule_router(self.adminContext, r4['id'])
agents = self.plugin.get_l3_agents_hosting_routers(
self.adminContext, [r1['id'], r2['id'], r3['id'], r4['id']])
host_num = collections.defaultdict(int)
for agent in agents:
host_num[agent['host']] += 1
self.assertEqual(2, host_num['az1-host1'])
self.assertEqual(2, host_num['az1-host2'])
def test_az_scheduler_ha_az_hints(self):
cfg.CONF.set_override('max_l3_agents_per_router', 2)
r1 = self._create_ha_router(az_hints=['az1', 'az3'])
self.plugin.schedule_router(self.adminContext, r1['id'])
agents = self.plugin.get_l3_agents_hosting_routers(
self.adminContext, [r1['id']])
self.assertEqual(2, len(agents))
expected_azs = set(['az1', 'az3'])
azs = set([a['availability_zone'] for a in agents])
self.assertEqual(expected_azs, azs)
def test_az_scheduler_ha_auto_schedule(self):
cfg.CONF.set_override('max_l3_agents_per_router', 3)
r1 = self._create_ha_router(az_hints=['az1', 'az3'])
self._set_l3_agent_admin_state(self.adminContext, self.agent2['id'],
state=False)
self._set_l3_agent_admin_state(self.adminContext, self.agent6['id'],
state=False)
self.plugin.schedule_router(self.adminContext, r1['id'])
agents = self.plugin.get_l3_agents_hosting_routers(
self.adminContext, [r1['id']])
self.assertEqual(2, len(agents))
hosts = set([a['host'] for a in agents])
self.assertEqual(set(['az1-host1', 'az3-host1']), hosts)
self._set_l3_agent_admin_state(self.adminContext, self.agent6['id'],
state=True)
self.plugin.auto_schedule_routers(self.adminContext,
'az3-host2', None)
agents = self.plugin.get_l3_agents_hosting_routers(
self.adminContext, [r1['id']])
self.assertEqual(3, len(agents))
expected_hosts = set(['az1-host1', 'az3-host1', 'az3-host2'])
hosts = set([a['host'] for a in agents])
self.assertEqual(expected_hosts, hosts)

View File

@ -1,4 +1,10 @@
---
prelude: >
Agent scheduling aware of availability zones is now supported.
features:
- DHCP agent is assigned to a availability zone. Network can be host on the
DHCP agent with availability zone which users specify.
- DHCP agent is assigned to an availability zone; network will be hosted by
the DHCP agent with availability zone which user specifies.
- L3 agent is assigned to an availability zone; router will be hosted by the
L3 agent with availability zone which user specifies. This supports the use
of availability zones with HA routers. DVR isn't supported now because L3HA
and DVR integration isn't finished.