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:
parent
1b6cced0d4
commit
ef2a5543cc
43
neutron/db/availability_zone/router.py
Normal file
43
neutron/db/availability_zone/router.py
Normal 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)
|
@ -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]
|
||||
|
@ -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,
|
||||
|
@ -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}])
|
||||
|
||||
|
@ -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:
|
||||
|
@ -1 +1 @@
|
||||
ec7fcfbf72ee
|
||||
dce3ec7a25c9
|
||||
|
@ -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)))
|
68
neutron/extensions/router_availability_zone.py
Normal file
68
neutron/extensions/router_availability_zone.py
Normal 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."""
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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]
|
||||
|
110
neutron/tests/unit/extensions/test_router_availability_zone.py
Normal file
110
neutron/tests/unit/extensions/test_router_availability_zone.py
Normal 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)
|
@ -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)
|
||||
|
@ -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.
|
Loading…
x
Reference in New Issue
Block a user