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
--- /dev/null
+#
+# 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)
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
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]
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,
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
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}])
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
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:
-ec7fcfbf72ee
+dce3ec7a25c9
--- /dev/null
+#
+# 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)))
--- /dev/null
+#
+# 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."""
# under the License.
import abc
+import collections
+import itertools
import random
from oslo_config import cfg
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__)
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.
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
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
"""
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)
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]
--- /dev/null
+#
+# 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)
# License for the specific language governing permissions and limitations
# under the License.
+import collections
import contextlib
import datetime
import uuid
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,
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})
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
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)
---
+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.
\ No newline at end of file
+ - 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.
\ No newline at end of file