BGP Dynamic Routing: introduce BgpDrScheduler model

This patch implements a new extension called "bgp_dragentscheduler" which
does instant & auto scheuling of BgpSpeakers to an active BgpDrAgent. In
addition to this the patch also implements the basic CRUD requirement for
binding BgpSpeakers and BgpDrAgent.

BgpSpeaker to BgpDrAgent association can be 1-to-n. An admin user can only
associate/disassociate BgpSpeaker to/from a BgpDRAgent. Default scheduler
class will only assign non-scheduled BgpSpeaker to an active BgpDrAgent.

Partially-Implements: blueprint bgp-dynamic-routing
Co-Authored-By: Ryan Tidwell <ryan.tidwell@hpe.com>
Co-Authored-By: Jaume Devesa <devvesa@gmail.com>
Co-Authored-By: vikram.choudhary <vikram.choudhary@huawei.com>
Co-Authored-By: Numan Siddique <nusiddiq@redhat.com>
Change-Id: Id305d9a583116e155441ac5979bf3f6aa6a8258b
This commit is contained in:
vikram.choudhary 2016-02-12 14:16:21 +05:30
parent a6ddf981e1
commit bede37f5e2
22 changed files with 1516 additions and 5 deletions

View File

@ -223,5 +223,10 @@
"add_gateway_network": "rule:admin_only",
"remove_gateway_network": "rule:admin_only",
"get_advertised_routes":"rule:admin_only"
"get_advertised_routes":"rule:admin_only",
"add_bgp_speaker_to_dragent": "rule:admin_only",
"remove_bgp_speaker_from_dragent": "rule:admin_only",
"list_bgp_speaker_on_dragent": "rule:admin_only",
"list_dragent_hosting_bgp_speaker": "rule:admin_only"
}

View File

@ -0,0 +1,177 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from oslo_db import exception as db_exc
from oslo_log import log as logging
import sqlalchemy as sa
from sqlalchemy import orm
from neutron._i18n import _
from neutron._i18n import _LW
from neutron.db import agents_db
from neutron.db import agentschedulers_db as as_db
from neutron.db import model_base
from neutron.extensions import bgp_dragentscheduler as bgp_dras_ext
from neutron.services.bgp.common import constants as bgp_consts
LOG = logging.getLogger(__name__)
BGP_DRAGENT_SCHEDULER_OPTS = [
cfg.StrOpt(
'bgp_drscheduler_driver',
default='neutron.services.bgp.scheduler'
'.bgp_dragent_scheduler.ChanceScheduler',
help=_('Driver used for scheduling BGP speakers to BGP DrAgent')),
]
cfg.CONF.register_opts(BGP_DRAGENT_SCHEDULER_OPTS)
class BgpSpeakerDrAgentBinding(model_base.BASEV2):
"""Represents a mapping between BGP speaker and BGP DRAgent"""
__tablename__ = 'bgp_speaker_dragent_bindings'
bgp_speaker_id = sa.Column(sa.String(length=36),
sa.ForeignKey("bgp_speakers.id",
ondelete='CASCADE'),
nullable=False)
dragent = orm.relation(agents_db.Agent)
agent_id = sa.Column(sa.String(length=36),
sa.ForeignKey("agents.id",
ondelete='CASCADE'),
primary_key=True)
class BgpDrAgentSchedulerDbMixin(bgp_dras_ext.BgpDrSchedulerPluginBase,
as_db.AgentSchedulerDbMixin):
bgp_drscheduler = None
def schedule_unscheduled_bgp_speakers(self, context, host):
if self.bgp_drscheduler:
return self.bgp_drscheduler.schedule_unscheduled_bgp_speakers(
context, host)
else:
LOG.warning(_LW("Cannot schedule BgpSpeaker to DrAgent. "
"Reason: No scheduler registered."))
def schedule_bgp_speaker(self, context, created_bgp_speaker):
if self.bgp_drscheduler:
self.bgp_drscheduler.schedule(context, created_bgp_speaker)
else:
LOG.warning(_LW("Cannot schedule BgpSpeaker to DrAgent. "
"Reason: No scheduler registered."))
def add_bgp_speaker_to_dragent(self, context, agent_id, speaker_id):
"""Associate a BgpDrAgent with a BgpSpeaker."""
try:
self._save_bgp_speaker_dragent_binding(context,
agent_id,
speaker_id)
except db_exc.DBDuplicateEntry:
raise bgp_dras_ext.DrAgentAssociationError(
agent_id=agent_id)
LOG.debug('BgpSpeaker %(bgp_speaker_id)s added to '
'BgpDrAgent %(agent_id)s',
{'bgp_speaker_id': speaker_id, 'agent_id': agent_id})
def _save_bgp_speaker_dragent_binding(self, context,
agent_id, speaker_id):
with context.session.begin(subtransactions=True):
agent_db = self._get_agent(context, agent_id)
agent_up = agent_db['admin_state_up']
is_agent_bgp = (agent_db['agent_type'] ==
bgp_consts.AGENT_TYPE_BGP_ROUTING)
if not is_agent_bgp or not agent_up:
raise bgp_dras_ext.DrAgentInvalid(id=agent_id)
binding = BgpSpeakerDrAgentBinding()
binding.bgp_speaker_id = speaker_id
binding.agent_id = agent_id
context.session.add(binding)
def remove_bgp_speaker_from_dragent(self, context, agent_id, speaker_id):
with context.session.begin(subtransactions=True):
agent_db = self._get_agent(context, agent_id)
is_agent_bgp = (agent_db['agent_type'] ==
bgp_consts.AGENT_TYPE_BGP_ROUTING)
if not is_agent_bgp:
raise bgp_dras_ext.DrAgentInvalid(id=agent_id)
query = context.session.query(BgpSpeakerDrAgentBinding)
query = query.filter_by(bgp_speaker_id=speaker_id,
agent_id=agent_id)
num_deleted = query.delete()
if not num_deleted:
raise bgp_dras_ext.DrAgentNotHostingBgpSpeaker(
bgp_speaker_id=speaker_id,
agent_id=agent_id)
LOG.debug('BgpSpeaker %(bgp_speaker_id)s removed from '
'BgpDrAgent %(agent_id)s',
{'bgp_speaker_id': speaker_id,
'agent_id': agent_id})
def get_dragents_hosting_bgp_speakers(self, context, bgp_speaker_ids,
active=None, admin_state_up=None):
query = context.session.query(BgpSpeakerDrAgentBinding)
query = query.options(orm.contains_eager(
BgpSpeakerDrAgentBinding.dragent))
query = query.join(BgpSpeakerDrAgentBinding.dragent)
if len(bgp_speaker_ids) == 1:
query = query.filter(
BgpSpeakerDrAgentBinding.bgp_speaker_id == (
bgp_speaker_ids[0]))
elif bgp_speaker_ids:
query = query.filter(
BgpSpeakerDrAgentBinding.bgp_speaker_id in bgp_speaker_ids)
if admin_state_up is not None:
query = query.filter(agents_db.Agent.admin_state_up ==
admin_state_up)
return [binding.dragent
for binding in query
if as_db.AgentSchedulerDbMixin.is_eligible_agent(
active, binding.dragent)]
def get_dragent_bgp_speaker_bindings(self, context):
return context.session.query(BgpSpeakerDrAgentBinding).all()
def list_dragent_hosting_bgp_speaker(self, context, speaker_id):
dragents = self.get_dragents_hosting_bgp_speakers(context,
[speaker_id])
agent_ids = [dragent.id for dragent in dragents]
if not agent_ids:
return {'agents': []}
return {'agents': self.get_agents(context, filters={'id': agent_ids})}
def list_bgp_speaker_on_dragent(self, context, agent_id):
query = context.session.query(BgpSpeakerDrAgentBinding.bgp_speaker_id)
query = query.filter_by(agent_id=agent_id)
bgp_speaker_ids = [item[0] for item in query]
if not bgp_speaker_ids:
# Exception will be thrown if the requested agent does not exist.
self._get_agent(context, agent_id)
return {'bgp_speakers': []}
return {'bgp_speakers':
self.get_bgp_speakers(context,
filters={'id': bgp_speaker_ids})}

View File

@ -1 +1 @@
15be73214821
b4caf27aae4

View File

@ -0,0 +1,46 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
#
# 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_bgp_dragent_model_data
Revision ID: b4caf27aae4
Revises: 15be7321482
Create Date: 2015-08-20 17:05:31.038704
"""
# revision identifiers, used by Alembic.
revision = 'b4caf27aae4'
down_revision = '15be73214821'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.create_table(
'bgp_speaker_dragent_bindings',
sa.Column('agent_id',
sa.String(length=36),
primary_key=True),
sa.Column('bgp_speaker_id',
sa.String(length=36),
nullable=False),
sa.ForeignKeyConstraint(['agent_id'], ['agents.id'],
ondelete='CASCADE'),
sa.ForeignKeyConstraint(['bgp_speaker_id'], ['bgp_speakers.id'],
ondelete='CASCADE'),
)

View File

@ -26,6 +26,7 @@ from neutron.db import agents_db # noqa
from neutron.db import agentschedulers_db # noqa
from neutron.db import allowedaddresspairs_db # noqa
from neutron.db import bgp_db # noqa
from neutron.db import bgp_dragentscheduler_db # noqa
from neutron.db import dns_db # noqa
from neutron.db import dvr_mac_db # noqa
from neutron.db import external_net_db # noqa

View File

@ -0,0 +1,171 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import abc
import six
import webob
from oslo_log import log as logging
from neutron.api import extensions
from neutron.api.v2 import base
from neutron.api.v2 import resource
from neutron.common import exceptions
from neutron.extensions import agent
from neutron.extensions import bgp as bgp_ext
from neutron._i18n import _, _LE
from neutron import manager
from neutron import wsgi
LOG = logging.getLogger(__name__)
BGP_DRAGENT_SCHEDULER_EXT_ALIAS = 'bgp_dragent_scheduler'
BGP_DRINSTANCE = 'bgp-drinstance'
BGP_DRINSTANCES = BGP_DRINSTANCE + 's'
BGP_DRAGENT = 'bgp-dragent'
BGP_DRAGENTS = BGP_DRAGENT + 's'
class DrAgentInvalid(agent.AgentNotFound):
message = _("BgpDrAgent %(id)s is invalid or has been disabled.")
class DrAgentNotHostingBgpSpeaker(exceptions.NotFound):
message = _("BGP speaker %(bgp_speaker_id)s is not hosted "
"by the BgpDrAgent %(agent_id)s.")
class DrAgentAssociationError(exceptions.Conflict):
message = _("BgpDrAgent %(agent_id)s is already associated "
"to a BGP speaker.")
class BgpDrSchedulerController(wsgi.Controller):
"""Schedule BgpSpeaker for a BgpDrAgent"""
def get_plugin(self):
plugin = manager.NeutronManager.get_service_plugins().get(
bgp_ext.BGP_EXT_ALIAS)
if not plugin:
LOG.error(_LE('No plugin for BGP routing registered'))
msg = _('The resource could not be found.')
raise webob.exc.HTTPNotFound(msg)
return plugin
def index(self, request, **kwargs):
plugin = self.get_plugin()
return plugin.list_bgp_speaker_on_dragent(
request.context, kwargs['agent_id'])
def create(self, request, body, **kwargs):
plugin = self.get_plugin()
return plugin.add_bgp_speaker_to_dragent(
request.context,
kwargs['agent_id'],
body['bgp_speaker_id'])
def delete(self, request, id, **kwargs):
plugin = self.get_plugin()
return plugin.remove_bgp_speaker_from_dragent(
request.context, kwargs['agent_id'], id)
class BgpDrAgentController(wsgi.Controller):
def get_plugin(self):
plugin = manager.NeutronManager.get_service_plugins().get(
bgp_ext.BGP_EXT_ALIAS)
if not plugin:
LOG.error(_LE('No plugin for BGP routing registered'))
msg = _LE('The resource could not be found.')
raise webob.exc.HTTPNotFound(msg)
return plugin
def index(self, request, **kwargs):
plugin = manager.NeutronManager.get_service_plugins().get(
bgp_ext.BGP_EXT_ALIAS)
return plugin.list_dragent_hosting_bgp_speaker(
request.context, kwargs['bgp_speaker_id'])
class Bgp_dragentscheduler(extensions.ExtensionDescriptor):
"""Extension class supporting Dynamic Routing scheduler.
"""
@classmethod
def get_name(cls):
return "BGP Dynamic Routing Agent Scheduler"
@classmethod
def get_alias(cls):
return BGP_DRAGENT_SCHEDULER_EXT_ALIAS
@classmethod
def get_description(cls):
return "Schedules BgpSpeakers on BgpDrAgent"
@classmethod
def get_updated(cls):
return "2015-07-30T10:00:00-00:00"
@classmethod
def get_resources(cls):
"""Returns Ext Resources."""
exts = []
parent = dict(member_name="agent",
collection_name="agents")
controller = resource.Resource(BgpDrSchedulerController(),
base.FAULT_MAP)
exts.append(extensions.ResourceExtension(BGP_DRINSTANCES,
controller, parent))
parent = dict(member_name="bgp_speaker",
collection_name="bgp-speakers")
controller = resource.Resource(BgpDrAgentController(),
base.FAULT_MAP)
exts.append(extensions.ResourceExtension(BGP_DRAGENTS,
controller, parent))
return exts
def get_extended_resources(self, version):
return {}
@six.add_metaclass(abc.ABCMeta)
class BgpDrSchedulerPluginBase(object):
"""REST API to operate BGP dynamic routing agent scheduler.
All the methods must be executed in admin context.
"""
def get_plugin_description(self):
return "Neutron BGP dynamic routing scheduler Plugin"
def get_plugin_type(self):
return bgp_ext.BGP_EXT_ALIAS
@abc.abstractmethod
def add_bgp_speaker_to_dragent(self, context, agent_id, speaker_id):
pass
@abc.abstractmethod
def remove_bgp_speaker_from_dragent(self, context, agent_id, speaker_id):
pass
@abc.abstractmethod
def list_dragent_hosting_bgp_speaker(self, context, speaker_id):
pass
@abc.abstractmethod
def list_bgp_speaker_on_dragent(self, context, agent_id):
pass

View File

@ -12,20 +12,29 @@
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from oslo_utils import importutils
from neutron.db import bgp_db
from neutron.db import bgp_dragentscheduler_db
from neutron.extensions import bgp as bgp_ext
from neutron.extensions import bgp_dragentscheduler as dras_ext
from neutron.services import service_base
PLUGIN_NAME = bgp_ext.BGP_EXT_ALIAS + '_svc_plugin'
class BgpPlugin(service_base.ServicePluginBase,
bgp_db.BgpDbMixin):
bgp_db.BgpDbMixin,
bgp_dragentscheduler_db.BgpDrAgentSchedulerDbMixin):
supported_extension_aliases = [bgp_ext.BGP_EXT_ALIAS]
supported_extension_aliases = [bgp_ext.BGP_EXT_ALIAS,
dras_ext.BGP_DRAGENT_SCHEDULER_EXT_ALIAS]
def __init__(self):
super(BgpPlugin, self).__init__()
self.bgp_drscheduler = importutils.import_object(
cfg.CONF.bgp_drscheduler_driver)
def get_plugin_name(self):
return PLUGIN_NAME

View File

View File

@ -0,0 +1,16 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
AGENT_TYPE_BGP_ROUTING = 'BGP dynamic routing agent'

View File

@ -0,0 +1,191 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_db import exception as db_exc
from oslo_log import log as logging
from sqlalchemy.orm import exc
from sqlalchemy import sql
from neutron.db import agents_db
from neutron.db import bgp_db
from neutron.db import bgp_dragentscheduler_db as bgp_dras_db
from neutron._i18n import _LI, _LW
from neutron.scheduler import base_resource_filter
from neutron.scheduler import base_scheduler
from neutron.services.bgp.common import constants as bgp_consts
LOG = logging.getLogger(__name__)
BGP_SPEAKER_PER_DRAGENT = 1
class BgpDrAgentFilter(base_resource_filter.BaseResourceFilter):
def bind(self, context, agents, bgp_speaker_id):
"""Bind the BgpSpeaker to a BgpDrAgent."""
bound_agents = agents[:]
for agent in agents:
# saving agent_id to use it after rollback to avoid
# DetachedInstanceError
agent_id = agent.id
binding = bgp_dras_db.BgpSpeakerDrAgentBinding()
binding.agent_id = agent_id
binding.bgp_speaker_id = bgp_speaker_id
try:
with context.session.begin(subtransactions=True):
context.session.add(binding)
except db_exc.DBDuplicateEntry:
# it's totally ok, someone just did our job!
bound_agents.remove(agent)
LOG.info(_LI('BgpDrAgent %s already present'), agent_id)
LOG.debug('BgpSpeaker %(bgp_speaker_id)s is scheduled to be '
'hosted by BgpDrAgent %(agent_id)s',
{'bgp_speaker_id': bgp_speaker_id,
'agent_id': agent_id})
super(BgpDrAgentFilter, self).bind(context, bound_agents,
bgp_speaker_id)
def filter_agents(self, plugin, context, bgp_speaker):
"""Return the agents that can host the BgpSpeaker."""
agents_dict = self._get_bgp_speaker_hostable_dragents(
plugin, context, bgp_speaker)
if not agents_dict['hostable_agents'] or agents_dict['n_agents'] <= 0:
return {'n_agents': 0,
'hostable_agents': [],
'hosted_agents': []}
return agents_dict
def _get_active_dragents(self, plugin, context):
"""Return a list of active BgpDrAgents."""
with context.session.begin(subtransactions=True):
active_dragents = plugin.get_agents_db(
context, filters={
'agent_type': [bgp_consts.AGENT_TYPE_BGP_ROUTING],
'admin_state_up': [True]})
if not active_dragents:
return []
return active_dragents
def _get_num_dragents_hosting_bgp_speaker(self, bgp_speaker_id,
dragent_bindings):
return sum(1 if dragent_binding.bgp_speaker_id == bgp_speaker_id else 0
for dragent_binding in dragent_bindings)
def _get_bgp_speaker_hostable_dragents(self, plugin, context, bgp_speaker):
"""Return number of additional BgpDrAgents which will actually host
the given BgpSpeaker and a list of BgpDrAgents which can host the
given BgpSpeaker
"""
# only one BgpSpeaker can be hosted by a BgpDrAgent for now.
dragents_per_bgp_speaker = BGP_SPEAKER_PER_DRAGENT
dragent_bindings = plugin.get_dragent_bgp_speaker_bindings(context)
agents_hosting = [dragent_binding.agent_id
for dragent_binding in dragent_bindings]
num_dragents_hosting_bgp_speaker = (
self._get_num_dragents_hosting_bgp_speaker(bgp_speaker['id'],
dragent_bindings))
n_agents = dragents_per_bgp_speaker - num_dragents_hosting_bgp_speaker
if n_agents <= 0:
return {'n_agents': 0,
'hostable_agents': [],
'hosted_agents': []}
active_dragents = self._get_active_dragents(plugin, context)
hostable_dragents = [
agent for agent in set(active_dragents)
if agent.id not in agents_hosting and plugin.is_eligible_agent(
active=True, agent=agent)
]
if not hostable_dragents:
return {'n_agents': 0,
'hostable_agents': [],
'hosted_agents': []}
n_agents = min(len(hostable_dragents), n_agents)
return {'n_agents': n_agents,
'hostable_agents': hostable_dragents,
'hosted_agents': num_dragents_hosting_bgp_speaker}
class BgpDrAgentSchedulerBase(BgpDrAgentFilter):
def schedule_unscheduled_bgp_speakers(self, context, host):
"""Schedule unscheduled BgpSpeaker to a BgpDrAgent.
"""
LOG.debug('Started auto-scheduling on host %s', host)
with context.session.begin(subtransactions=True):
query = context.session.query(agents_db.Agent)
query = query.filter_by(
agent_type=bgp_consts.AGENT_TYPE_BGP_ROUTING,
host=host,
admin_state_up=sql.true())
try:
bgp_dragent = query.one()
except (exc.NoResultFound):
LOG.debug('No enabled BgpDrAgent on host %s', host)
return False
if agents_db.AgentDbMixin.is_agent_down(
bgp_dragent.heartbeat_timestamp):
LOG.warn(_LW('BgpDrAgent %s is down'), bgp_dragent.id)
return False
if self._is_bgp_speaker_hosted(context, bgp_dragent['id']):
# One BgpDrAgent can only host one BGP speaker
LOG.debug('BgpDrAgent already hosting a speaker on host %s. '
'Cannot schedule an another one', host)
return False
unscheduled_speakers = self._get_unscheduled_bgp_speakers(context)
if not unscheduled_speakers:
LOG.debug('Nothing to auto-schedule on host %s', host)
return False
self.bind(context, [bgp_dragent], unscheduled_speakers[0])
return True
def _is_bgp_speaker_hosted(self, context, agent_id):
speaker_binding_model = bgp_dras_db.BgpSpeakerDrAgentBinding
query = context.session.query(speaker_binding_model)
query = query.filter(speaker_binding_model.agent_id == agent_id)
return query.count() > 0
def _get_unscheduled_bgp_speakers(self, context):
"""BGP speakers that needs to be scheduled.
"""
no_agent_binding = ~sql.exists().where(
bgp_db.BgpSpeaker.id ==
bgp_dras_db.BgpSpeakerDrAgentBinding.bgp_speaker_id)
query = context.session.query(bgp_db.BgpSpeaker.id).filter(
no_agent_binding)
return [bgp_speaker_id_[0] for bgp_speaker_id_ in query]
class ChanceScheduler(base_scheduler.BaseChanceScheduler,
BgpDrAgentSchedulerBase):
def __init__(self):
super(ChanceScheduler, self).__init__(self)
class WeightScheduler(base_scheduler.BaseWeightScheduler,
BgpDrAgentSchedulerBase):
def __init__(self):
super(WeightScheduler, self).__init__(self)

View File

@ -25,6 +25,7 @@ from neutron.common import topics
from neutron import context
from neutron.db import agents_db
from neutron.db import common_db_mixin
from neutron.services.bgp.common import constants as bgp_const
HOST = 'localhost'
DEFAULT_AZ = 'nova'
@ -107,6 +108,30 @@ def register_dhcp_agent(host=HOST, networks=0, admin_state_up=True,
context.get_admin_context(), agent['agent_type'], agent['host'])
def _get_bgp_dragent_dict(host):
agent = {
'binary': 'neutron-bgp-dragent',
'host': host,
'topic': 'q-bgp_dragent',
'agent_type': bgp_const.AGENT_TYPE_BGP_ROUTING,
'configurations': {'bgp_speakers': 1}}
return agent
def register_bgp_dragent(host=HOST, admin_state_up=True,
alive=True):
agent = _register_agent(
_get_bgp_dragent_dict(host))
if not admin_state_up:
set_agent_admin_state(agent['id'])
if not alive:
kill_agent(agent['id'])
return FakePlugin()._get_agent_by_type_and_host(
context.get_admin_context(), agent['agent_type'], agent['host'])
def kill_agent(agent_id):
hour_ago = timeutils.utcnow() - datetime.timedelta(hours=1)
FakePlugin().update_agent(

View File

@ -223,5 +223,10 @@
"add_gateway_network": "rule:admin_only",
"remove_gateway_network": "rule:admin_only",
"get_advertised_routes":"rule:admin_only"
"get_advertised_routes":"rule:admin_only",
"add_bgp_speaker_to_dragent": "rule:admin_only",
"remove_bgp_speaker_from_dragent": "rule:admin_only",
"list_bgp_speaker_on_dragent": "rule:admin_only",
"list_dragent_hosting_bgp_speaker": "rule:admin_only"
}

View File

@ -0,0 +1,208 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import testscenarios
from neutron import context
from neutron.db import agents_db
from neutron.db import bgp_db
from neutron.db import bgp_dragentscheduler_db as bgp_dras_db
from neutron.db import common_db_mixin
from neutron.services.bgp.scheduler import bgp_dragent_scheduler as bgp_dras
from neutron.tests.common import helpers
from neutron.tests.unit import testlib_api
# Required to generate tests from scenarios. Not compatible with nose.
load_tests = testscenarios.load_tests_apply_scenarios
class TestAutoSchedule(testlib_api.SqlTestCase,
bgp_dras_db.BgpDrAgentSchedulerDbMixin,
agents_db.AgentDbMixin,
common_db_mixin.CommonDbMixin):
"""Test various scenarios for schedule_unscheduled_bgp_speakers.
Below is the brief description of the scenario variables
--------------------------------------------------------
host_count
number of hosts.
agent_count
number of BGP dynamic routing agents.
down_agent_count
number of DRAgents which are inactive.
bgp_speaker_count
Number of bgp_speakers.
hosted_bgp_speakers
A mapping of agent id to the ids of the bgp_speakers that they
should be initially hosting.
expected_schedule_return_value
Expected return value of 'schedule_unscheduled_bgp_speakers'.
expected_hosted_bgp_speakers
This stores the expected bgp_speakers that should have been
scheduled (or that could have already been scheduled) for each
agent after the 'schedule_unscheduled_bgp_speakers' function is
called.
"""
scenarios = [
('No BgpDrAgent scheduled, if no DRAgent is present',
dict(host_count=1,
agent_count=0,
down_agent_count=0,
bgp_speaker_count=1,
hosted_bgp_speakers={},
expected_schedule_return_value=False)),
('No BgpDrAgent scheduled, if no BGP speaker are present',
dict(host_count=1,
agent_count=1,
down_agent_count=0,
bgp_speaker_count=0,
hosted_bgp_speakers={},
expected_schedule_return_value=False,
expected_hosted_bgp_speakers={'agent-0': []})),
('No BgpDrAgent scheduled, if BGP speaker already hosted',
dict(host_count=1,
agent_count=1,
down_agent_count=0,
bgp_speaker_count=1,
hosted_bgp_speakers={'agent-0': ['bgp-speaker-0']},
expected_schedule_return_value=False,
expected_hosted_bgp_speakers={'agent-0': ['bgp-speaker-0']})),
('BgpDrAgent scheduled to the speaker, if the speaker is not hosted',
dict(host_count=1,
agent_count=1,
down_agent_count=0,
bgp_speaker_count=1,
hosted_bgp_speakers={},
expected_schedule_return_value=True,
expected_hosted_bgp_speakers={'agent-0': ['bgp-speaker-0']})),
('No BgpDrAgent scheduled, if all the agents are down',
dict(host_count=2,
agent_count=2,
down_agent_count=2,
bgp_speaker_count=1,
hosted_bgp_speakers={},
expected_schedule_return_value=False,
expected_hosted_bgp_speakers={'agent-0': [],
'agent-1': [], })),
]
def _strip_host_index(self, name):
"""Strips the host index.
Eg. if name = '2-agent-3', then 'agent-3' is returned.
"""
return name[name.find('-') + 1:]
def _extract_index(self, name):
"""Extracts the index number and returns.
Eg. if name = '2-agent-3', then 3 is returned
"""
return int(name.split('-')[-1])
def _get_hosted_bgp_speakers_on_dragent(self, agent_id):
query = self.ctx.session.query(
bgp_dras_db.BgpSpeakerDrAgentBinding.bgp_speaker_id)
query = query.filter(
bgp_dras_db.BgpSpeakerDrAgentBinding.agent_id ==
agent_id)
return [item[0] for item in query]
def _create_and_set_agents_down(self, hosts, agent_count=0,
down_agent_count=0, admin_state_up=True):
agents = []
if agent_count:
for i, host in enumerate(hosts):
is_alive = i >= down_agent_count
agents.append(helpers.register_bgp_dragent(
host,
admin_state_up=admin_state_up,
alive=is_alive))
return agents
def _save_bgp_speakers(self, bgp_speakers):
cls = bgp_db.BgpDbMixin()
bgp_speaker_body = {
'bgp_speaker': {'name': 'fake_bgp_speaker',
'ip_version': '4',
'local_as': '123',
'advertise_floating_ip_host_routes': '0',
'advertise_tenant_networks': '0',
'peers': [],
'networks': []}}
i = 1
for bgp_speaker_id in bgp_speakers:
bgp_speaker_body['bgp_speaker']['local_as'] = i
cls._save_bgp_speaker(self.ctx, bgp_speaker_body,
uuid=bgp_speaker_id)
i = i + 1
def _test_auto_schedule(self, host_index):
scheduler = bgp_dras.ChanceScheduler()
self.ctx = context.get_admin_context()
msg = 'host_index = %s' % host_index
# create hosts
hosts = ['%s-agent-%s' % (host_index, i)
for i in range(self.host_count)]
bgp_dragents = self._create_and_set_agents_down(hosts,
self.agent_count,
self.down_agent_count)
# create bgp_speakers
self._bgp_speakers = ['%s-bgp-speaker-%s' % (host_index, i)
for i in range(self.bgp_speaker_count)]
self._save_bgp_speakers(self._bgp_speakers)
# pre schedule the bgp_speakers to the agents defined in
# self.hosted_bgp_speakers before calling auto_schedule_bgp_speaker
for agent, bgp_speakers in self.hosted_bgp_speakers.items():
agent_index = self._extract_index(agent)
for bgp_speaker in bgp_speakers:
bs_index = self._extract_index(bgp_speaker)
scheduler.bind(self.ctx, [bgp_dragents[agent_index]],
self._bgp_speakers[bs_index])
retval = scheduler.schedule_unscheduled_bgp_speakers(self.ctx,
hosts[host_index])
self.assertEqual(self.expected_schedule_return_value, retval,
message=msg)
if self.agent_count:
agent_id = bgp_dragents[host_index].id
hosted_bgp_speakers = self._get_hosted_bgp_speakers_on_dragent(
agent_id)
hosted_bs_ids = [self._strip_host_index(net)
for net in hosted_bgp_speakers]
expected_hosted_bgp_speakers = self.expected_hosted_bgp_speakers[
'agent-%s' % host_index]
self.assertItemsEqual(hosted_bs_ids, expected_hosted_bgp_speakers,
msg)
def test_auto_schedule(self):
for i in range(self.host_count):
self._test_auto_schedule(i)

View File

@ -0,0 +1,203 @@
# Copyright (c) 2016 Hewlett Packard Enterprise Development Company, L.P.
#
# 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 oslo_config import cfg
from oslo_utils import importutils
from neutron.api.v2 import attributes
from neutron import context
from neutron.db import bgp_db
from neutron.db import bgp_dragentscheduler_db as bgp_dras_db
from neutron.extensions import agent
from neutron.extensions import bgp
from neutron.extensions import bgp_dragentscheduler as bgp_dras_ext
from neutron import manager
from neutron.tests.unit.db import test_bgp_db
from neutron.tests.unit.db import test_db_base_plugin_v2 as test_db_base_plugin
from neutron.tests.unit.extensions import test_agent
from webob import exc
class BgpDrSchedulerTestExtensionManager(object):
def get_resources(self):
attributes.RESOURCE_ATTRIBUTE_MAP.update(
agent.RESOURCE_ATTRIBUTE_MAP)
resources = agent.Agent.get_resources()
resources.extend(bgp_dras_ext.Bgp_dragentscheduler.get_resources())
return resources
def get_actions(self):
return []
def get_request_extensions(self):
return []
class TestBgpDrSchedulerPlugin(bgp_db.BgpDbMixin,
bgp_dras_db.BgpDrAgentSchedulerDbMixin):
bgp_drscheduler = importutils.import_object(
cfg.CONF.bgp_drscheduler_driver)
supported_extension_aliases = ["bgp_dragent_scheduler"]
def get_plugin_description(self):
return ("BGP dynamic routing service Plugin test class that test "
"BGP speaker functionality, with scheduler.")
class BgpDrSchedulingTestCase(test_agent.AgentDBTestMixIn,
test_bgp_db.BgpEntityCreationMixin):
def test_schedule_bgp_speaker(self):
"""Test happy path over full scheduling cycle."""
with self.bgp_speaker(4, 1234) as ri:
bgp_speaker_id = ri['id']
self._register_bgp_dragent(host='host1')
agent = self._list('agents')['agents'][0]
agent_id = agent['id']
data = {'bgp_speaker_id': bgp_speaker_id}
req = self.new_create_request('agents', data, self.fmt,
agent_id, 'bgp-drinstances')
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPCreated.code, res.status_int)
req_show = self.new_show_request('agents', agent_id, self.fmt,
'bgp-drinstances')
res = req_show.get_response(self.ext_api)
self.assertEqual(exc.HTTPOk.code, res.status_int)
res = self.deserialize(self.fmt, res)
self.assertIn('bgp_speakers', res)
self.assertTrue(bgp_speaker_id,
res['bgp_speakers'][0]['id'])
req = self.new_delete_request('agents',
agent_id,
self.fmt,
'bgp-drinstances',
bgp_speaker_id)
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPNoContent.code, res.status_int)
res = req_show.get_response(self.ext_api)
self.assertEqual(exc.HTTPOk.code, res.status_int)
res = self.deserialize(self.fmt, res)
self.assertIn('bgp_speakers', res)
self.assertEqual([], res['bgp_speakers'])
def test_schedule_bgp_speaker_on_invalid_agent(self):
"""Test error while scheduling BGP speaker on an invalid agent."""
with self.bgp_speaker(4, 1234) as ri:
bgp_speaker_id = ri['id']
self._register_l3_agent(host='host1') # Register wrong agent
agent = self._list('agents')['agents'][0]
data = {'bgp_speaker_id': bgp_speaker_id}
req = self.new_create_request(
'agents', data, self.fmt,
agent['id'], 'bgp-drinstances')
res = req.get_response(self.ext_api)
# Raises an AgentNotFound exception if the agent is invalid
self.assertEqual(exc.HTTPNotFound.code, res.status_int)
def test_schedule_bgp_speaker_twice_on_same_agent(self):
"""Test error if a BGP speaker is scheduled twice on same agent"""
with self.bgp_speaker(4, 1234) as ri:
bgp_speaker_id = ri['id']
self._register_bgp_dragent(host='host1')
agent = self._list('agents')['agents'][0]
data = {'bgp_speaker_id': bgp_speaker_id}
req = self.new_create_request(
'agents', data, self.fmt,
agent['id'], 'bgp-drinstances')
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPCreated.code, res.status_int)
# Try second time, should raise conflict
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPConflict.code, res.status_int)
def test_schedule_bgp_speaker_on_two_different_agents(self):
"""Test that a BGP speaker can be associated to two agents."""
with self.bgp_speaker(4, 1234) as ri:
bgp_speaker_id = ri['id']
self._register_bgp_dragent(host='host1')
self._register_bgp_dragent(host='host2')
data = {'bgp_speaker_id': bgp_speaker_id}
agent1 = self._list('agents')['agents'][0]
req = self.new_create_request(
'agents', data, self.fmt,
agent1['id'], 'bgp-drinstances')
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPCreated.code, res.status_int)
agent2 = self._list('agents')['agents'][1]
req = self.new_create_request(
'agents', data, self.fmt,
agent2['id'], 'bgp-drinstances')
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPCreated.code, res.status_int)
def test_schedule_multi_bgp_speaker_on_one_dragent(self):
"""Test only one BGP speaker can be associated to one dragent."""
with self.bgp_speaker(4, 1) as ri1, self.bgp_speaker(4, 2) as ri2:
self._register_bgp_dragent(host='host1')
agent = self._list('agents')['agents'][0]
data = {'bgp_speaker_id': ri1['id']}
req = self.new_create_request(
'agents', data, self.fmt,
agent['id'], 'bgp-drinstances')
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPCreated.code, res.status_int)
data = {'bgp_speaker_id': ri2['id']}
req = self.new_create_request(
'agents', data, self.fmt,
agent['id'], 'bgp-drinstances')
res = req.get_response(self.ext_api)
self.assertEqual(exc.HTTPConflict.code, res.status_int)
def test_non_scheduled_bgp_speaker_binding_removal(self):
"""Test exception while removing an invalid binding."""
with self.bgp_speaker(4, 1234) as ri1:
self._register_bgp_dragent(host='host1')
agent = self._list('agents')['agents'][0]
agent_id = agent['id']
self.assertRaises(bgp_dras_ext.DrAgentNotHostingBgpSpeaker,
self.bgp_plugin.remove_bgp_speaker_from_dragent,
self.context, agent_id, ri1['id'])
class BgpDrPluginSchedulerTests(test_db_base_plugin.NeutronDbPluginV2TestCase,
BgpDrSchedulingTestCase):
def setUp(self, plugin=None, ext_mgr=None, service_plugins=None):
if not plugin:
plugin = ('neutron.tests.unit.db.'
'test_bgp_dragentscheduler_db.TestBgpDrSchedulerPlugin')
if not service_plugins:
service_plugins = {bgp.BGP_EXT_ALIAS:
'neutron.services.bgp.bgp_plugin.BgpPlugin'}
ext_mgr = ext_mgr or BgpDrSchedulerTestExtensionManager()
super(BgpDrPluginSchedulerTests, self).setUp(
plugin=plugin, ext_mgr=ext_mgr, service_plugins=service_plugins)
self.bgp_plugin = manager.NeutronManager.get_service_plugins().get(
bgp.BGP_EXT_ALIAS)
self.context = context.get_admin_context()

View File

@ -122,6 +122,12 @@ class AgentDBTestMixIn(object):
host=L3_HOSTB, agent_mode=constants.L3_AGENT_MODE_DVR)
return [dvr_snat_agent, dvr_agent]
def _register_l3_agent(self, host):
helpers.register_l3_agent(host)
def _register_bgp_dragent(self, host):
helpers.register_bgp_dragent(host)
class AgentDBTestCase(AgentDBTestMixIn,
test_db_base_plugin_v2.NeutronDbPluginV2TestCase):

View File

@ -0,0 +1,224 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import testscenarios
from oslo_utils import importutils
from neutron import context
from neutron.db import bgp_db
from neutron.db import bgp_dragentscheduler_db as bgp_dras_db
from neutron.services.bgp.scheduler import bgp_dragent_scheduler as bgp_dras
from neutron.tests.common import helpers
from neutron.tests.unit import testlib_api
# Required to generate tests from scenarios. Not compatible with nose.
load_tests = testscenarios.load_tests_apply_scenarios
class TestBgpDrAgentSchedulerBaseTestCase(testlib_api.SqlTestCase):
def setUp(self):
super(TestBgpDrAgentSchedulerBaseTestCase, self).setUp()
self.ctx = context.get_admin_context()
self.bgp_speaker = {'id': 'foo_bgp_speaker_id'}
self.bgp_speaker_id = 'foo_bgp_speaker_id'
self._save_bgp_speaker(self.bgp_speaker_id)
def _create_and_set_agents_down(self, hosts, down_agent_count=0,
admin_state_up=True):
agents = []
for i, host in enumerate(hosts):
is_alive = i >= down_agent_count
agents.append(helpers.register_bgp_dragent(
host,
admin_state_up=admin_state_up,
alive=is_alive))
return agents
def _save_bgp_speaker(self, bgp_speaker_id):
cls = bgp_db.BgpDbMixin()
bgp_speaker_body = {'bgp_speaker': {'ip_version': '4',
'name': 'test-speaker',
'local_as': '123',
'advertise_floating_ip_host_routes': '0',
'advertise_tenant_networks': '0',
'peers': [],
'networks': []}}
cls._save_bgp_speaker(self.ctx, bgp_speaker_body, uuid=bgp_speaker_id)
def _test_schedule_bind_bgp_speaker(self, agents, bgp_speaker_id):
scheduler = bgp_dras.ChanceScheduler()
scheduler.resource_filter.bind(self.ctx, agents, bgp_speaker_id)
results = self.ctx.session.query(
bgp_dras_db.BgpSpeakerDrAgentBinding).filter_by(
bgp_speaker_id=bgp_speaker_id).all()
for result in results:
self.assertEqual(bgp_speaker_id, result.bgp_speaker_id)
class TestBgpDrAgentScheduler(TestBgpDrAgentSchedulerBaseTestCase,
bgp_db.BgpDbMixin):
def test_schedule_bind_bgp_speaker_single_agent(self):
agents = self._create_and_set_agents_down(['host-a'])
self._test_schedule_bind_bgp_speaker(agents, self.bgp_speaker_id)
def test_schedule_bind_bgp_speaker_multi_agents(self):
agents = self._create_and_set_agents_down(['host-a', 'host-b'])
self._test_schedule_bind_bgp_speaker(agents, self.bgp_speaker_id)
class TestBgpAgentFilter(TestBgpDrAgentSchedulerBaseTestCase,
bgp_db.BgpDbMixin,
bgp_dras_db.BgpDrAgentSchedulerDbMixin):
def setUp(self):
super(TestBgpAgentFilter, self).setUp()
self.bgp_drscheduler = importutils.import_object(
'neutron.services.bgp.scheduler'
'.bgp_dragent_scheduler.ChanceScheduler'
)
self.plugin = self
def _test_filter_agents_helper(self, bgp_speaker,
expected_filtered_dragent_ids=None,
expected_num_agents=1):
if not expected_filtered_dragent_ids:
expected_filtered_dragent_ids = []
filtered_agents = (
self.plugin.bgp_drscheduler.resource_filter.filter_agents(
self.plugin, self.ctx, bgp_speaker))
self.assertEqual(expected_num_agents,
filtered_agents['n_agents'])
actual_filtered_dragent_ids = [
agent.id for agent in filtered_agents['hostable_agents']]
self.assertEqual(len(expected_filtered_dragent_ids),
len(actual_filtered_dragent_ids))
for filtered_agent_id in actual_filtered_dragent_ids:
self.assertIn(filtered_agent_id, expected_filtered_dragent_ids)
def test_filter_agents_single_agent(self):
agents = self._create_and_set_agents_down(['host-a'])
expected_filtered_dragent_ids = [agents[0].id]
self._test_filter_agents_helper(
self.bgp_speaker,
expected_filtered_dragent_ids=expected_filtered_dragent_ids)
def test_filter_agents_no_agents(self):
expected_filtered_dragent_ids = []
self._test_filter_agents_helper(
self.bgp_speaker,
expected_filtered_dragent_ids=expected_filtered_dragent_ids,
expected_num_agents=0)
def test_filter_agents_two_agents(self):
agents = self._create_and_set_agents_down(['host-a', 'host-b'])
expected_filtered_dragent_ids = [agent.id for agent in agents]
self._test_filter_agents_helper(
self.bgp_speaker,
expected_filtered_dragent_ids=expected_filtered_dragent_ids)
def test_filter_agents_agent_already_scheduled(self):
agents = self._create_and_set_agents_down(['host-a', 'host-b'])
self._test_schedule_bind_bgp_speaker([agents[0]], self.bgp_speaker_id)
self._test_filter_agents_helper(self.bgp_speaker,
expected_num_agents=0)
def test_filter_agents_multiple_agents_bgp_speakers(self):
agents = self._create_and_set_agents_down(['host-a', 'host-b'])
self._test_schedule_bind_bgp_speaker([agents[0]], self.bgp_speaker_id)
bgp_speaker = {'id': 'bar-speaker-id'}
self._save_bgp_speaker(bgp_speaker['id'])
expected_filtered_dragent_ids = [agents[1].id]
self._test_filter_agents_helper(
bgp_speaker,
expected_filtered_dragent_ids=expected_filtered_dragent_ids)
class TestAutoScheduleBgpSpeakers(TestBgpDrAgentSchedulerBaseTestCase):
"""Unit test scenarios for schedule_unscheduled_bgp_speakers.
bgp_speaker_present
BGP speaker is present or not
scheduled_already
BGP speaker is already scheduled to the agent or not
agent_down
BGP DRAgent is down or alive
valid_host
If true, then an valid host is passed to schedule BGP speaker,
else an invalid host is passed.
"""
scenarios = [
('BGP speaker present',
dict(bgp_speaker_present=True,
scheduled_already=False,
agent_down=False,
valid_host=True,
expected_result=True)),
('No BGP speaker',
dict(bgp_speaker_present=False,
scheduled_already=False,
agent_down=False,
valid_host=True,
expected_result=False)),
('BGP speaker already scheduled',
dict(bgp_speaker_present=True,
scheduled_already=True,
agent_down=False,
valid_host=True,
expected_result=False)),
('BGP DR agent down',
dict(bgp_speaker_present=True,
scheduled_already=False,
agent_down=True,
valid_host=False,
expected_result=False)),
('Invalid host',
dict(bgp_speaker_present=True,
scheduled_already=False,
agent_down=False,
valid_host=False,
expected_result=False)),
]
def test_auto_schedule_bgp_speaker(self):
scheduler = bgp_dras.ChanceScheduler()
if self.bgp_speaker_present:
down_agent_count = 1 if self.agent_down else 0
agents = self._create_and_set_agents_down(
['host-a'], down_agent_count=down_agent_count)
if self.scheduled_already:
self._test_schedule_bind_bgp_speaker(agents,
self.bgp_speaker_id)
expected_hosted_agents = (1 if self.bgp_speaker_present and
self.valid_host else 0)
host = "host-a" if self.valid_host else "host-b"
observed_ret_value = scheduler.schedule_unscheduled_bgp_speakers(
self.ctx, host)
self.assertEqual(self.expected_result, observed_ret_value)
hosted_agents = self.ctx.session.query(
bgp_dras_db.BgpSpeakerDrAgentBinding).all()
self.assertEqual(expected_hosted_agents, len(hosted_agents))

View File

@ -0,0 +1,224 @@
# Copyright 2016 Huawei Technologies India Pvt. Ltd.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import testscenarios
from oslo_utils import importutils
from neutron import context
from neutron.db import bgp_db
from neutron.db import bgp_dragentscheduler_db as bgp_dras_db
from neutron.services.bgp.scheduler import bgp_dragent_scheduler as bgp_dras
from neutron.tests.common import helpers
from neutron.tests.unit import testlib_api
# Required to generate tests from scenarios. Not compatible with nose.
load_tests = testscenarios.load_tests_apply_scenarios
class TestBgpDrAgentSchedulerBaseTestCase(testlib_api.SqlTestCase):
def setUp(self):
super(TestBgpDrAgentSchedulerBaseTestCase, self).setUp()
self.ctx = context.get_admin_context()
self.bgp_speaker = {'id': 'foo_bgp_speaker_id'}
self.bgp_speaker_id = 'foo_bgp_speaker_id'
self._save_bgp_speaker(self.bgp_speaker_id)
def _create_and_set_agents_down(self, hosts, down_agent_count=0,
admin_state_up=True):
agents = []
for i, host in enumerate(hosts):
is_alive = i >= down_agent_count
agents.append(helpers.register_bgp_dragent(
host,
admin_state_up=admin_state_up,
alive=is_alive))
return agents
def _save_bgp_speaker(self, bgp_speaker_id):
cls = bgp_db.BgpDbMixin()
bgp_speaker_body = {'bgp_speaker': {
'name': 'fake_bgp_speaker',
'ip_version': '4',
'local_as': '123',
'advertise_floating_ip_host_routes': '0',
'advertise_tenant_networks': '0',
'peers': [],
'networks': []}}
cls._save_bgp_speaker(self.ctx, bgp_speaker_body, uuid=bgp_speaker_id)
def _test_schedule_bind_bgp_speaker(self, agents, bgp_speaker_id):
scheduler = bgp_dras.ChanceScheduler()
scheduler.resource_filter.bind(self.ctx, agents, bgp_speaker_id)
results = self.ctx.session.query(
bgp_dras_db.BgpSpeakerDrAgentBinding).filter_by(
bgp_speaker_id=bgp_speaker_id).all()
for result in results:
self.assertEqual(bgp_speaker_id, result.bgp_speaker_id)
class TestBgpDrAgentScheduler(TestBgpDrAgentSchedulerBaseTestCase,
bgp_db.BgpDbMixin):
def test_schedule_bind_bgp_speaker_single_agent(self):
agents = self._create_and_set_agents_down(['host-a'])
self._test_schedule_bind_bgp_speaker(agents, self.bgp_speaker_id)
def test_schedule_bind_bgp_speaker_multi_agents(self):
agents = self._create_and_set_agents_down(['host-a', 'host-b'])
self._test_schedule_bind_bgp_speaker(agents, self.bgp_speaker_id)
class TestBgpAgentFilter(TestBgpDrAgentSchedulerBaseTestCase,
bgp_db.BgpDbMixin,
bgp_dras_db.BgpDrAgentSchedulerDbMixin):
def setUp(self):
super(TestBgpAgentFilter, self).setUp()
self.bgp_drscheduler = importutils.import_object(
'neutron.services.bgp.scheduler.'
'bgp_dragent_scheduler.ChanceScheduler'
)
self.plugin = self
def _test_filter_agents_helper(self, bgp_speaker,
expected_filtered_dragent_ids=None,
expected_num_agents=1):
filtered_agents = (
self.plugin.bgp_drscheduler.resource_filter.filter_agents(
self.plugin, self.ctx, bgp_speaker))
self.assertEqual(expected_num_agents,
filtered_agents['n_agents'])
actual_filtered_dragent_ids = [
agent.id for agent in filtered_agents['hostable_agents']]
if expected_filtered_dragent_ids is None:
expected_filtered_dragent_ids = []
self.assertEqual(len(expected_filtered_dragent_ids),
len(actual_filtered_dragent_ids))
for filtered_agent_id in actual_filtered_dragent_ids:
self.assertIn(filtered_agent_id, expected_filtered_dragent_ids)
def test_filter_agents_single_agent(self):
agents = self._create_and_set_agents_down(['host-a'])
expected_filtered_dragent_ids = [agents[0].id]
self._test_filter_agents_helper(
self.bgp_speaker,
expected_filtered_dragent_ids=expected_filtered_dragent_ids)
def test_filter_agents_no_agents(self):
expected_filtered_dragent_ids = []
self._test_filter_agents_helper(
self.bgp_speaker,
expected_filtered_dragent_ids=expected_filtered_dragent_ids,
expected_num_agents=0)
def test_filter_agents_two_agents(self):
agents = self._create_and_set_agents_down(['host-a', 'host-b'])
expected_filtered_dragent_ids = [agent.id for agent in agents]
self._test_filter_agents_helper(
self.bgp_speaker,
expected_filtered_dragent_ids=expected_filtered_dragent_ids)
def test_filter_agents_agent_already_scheduled(self):
agents = self._create_and_set_agents_down(['host-a', 'host-b'])
self._test_schedule_bind_bgp_speaker([agents[0]], self.bgp_speaker_id)
self._test_filter_agents_helper(self.bgp_speaker,
expected_num_agents=0)
def test_filter_agents_multiple_agents_bgp_speakers(self):
agents = self._create_and_set_agents_down(['host-a', 'host-b'])
self._test_schedule_bind_bgp_speaker([agents[0]], self.bgp_speaker_id)
bgp_speaker = {'id': 'bar-speaker-id'}
self._save_bgp_speaker(bgp_speaker['id'])
expected_filtered_dragent_ids = [agents[1].id]
self._test_filter_agents_helper(
bgp_speaker,
expected_filtered_dragent_ids=expected_filtered_dragent_ids)
class TestAutoScheduleBgpSpeakers(TestBgpDrAgentSchedulerBaseTestCase):
"""Unit test scenarios for schedule_unscheduled_bgp_speakers.
bgp_speaker_present
BGP speaker is present or not
scheduled_already
BGP speaker is already scheduled to the agent or not
agent_down
BGP DRAgent is down or alive
valid_host
If true, then an valid host is passed to schedule BGP speaker,
else an invalid host is passed.
"""
scenarios = [
('BGP speaker present',
dict(bgp_speaker_present=True,
scheduled_already=False,
agent_down=False,
valid_host=True,
expected_result=True)),
('No BGP speaker',
dict(bgp_speaker_present=False,
scheduled_already=False,
agent_down=False,
valid_host=True,
expected_result=False)),
('BGP speaker already scheduled',
dict(bgp_speaker_present=True,
scheduled_already=True,
agent_down=False,
valid_host=True,
expected_result=False)),
('BGP DR agent down',
dict(bgp_speaker_present=True,
scheduled_already=False,
agent_down=True,
valid_host=False,
expected_result=False)),
('Invalid host',
dict(bgp_speaker_present=True,
scheduled_already=False,
agent_down=False,
valid_host=False,
expected_result=False)),
]
def test_auto_schedule_bgp_speaker(self):
scheduler = bgp_dras.ChanceScheduler()
if self.bgp_speaker_present:
down_agent_count = 1 if self.agent_down else 0
agents = self._create_and_set_agents_down(
['host-a'], down_agent_count=down_agent_count)
if self.scheduled_already:
self._test_schedule_bind_bgp_speaker(agents,
self.bgp_speaker_id)
expected_hosted_agents = (1 if self.bgp_speaker_present and
self.valid_host else 0)
host = "host-a" if self.valid_host else "host-b"
observed_ret_value = scheduler.schedule_unscheduled_bgp_speakers(
self.ctx, host)
self.assertEqual(self.expected_result, observed_ret_value)
hosted_agents = self.ctx.session.query(
bgp_dras_db.BgpSpeakerDrAgentBinding).all()
self.assertEqual(expected_hosted_agents, len(hosted_agents))