
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
178 lines
7.3 KiB
Python
178 lines
7.3 KiB
Python
# 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})}
|