Remove BGP code from neutron
Once the spinout is undergoing we should perform the eviction. Partially-implements: blueprint bgp-spinout Depends-on: I8be510153edbc496575cde34943ca4c56645e0fb Change-Id: I20b6ddd37d10eae70e8294d578e53137c0f866fe
This commit is contained in:
parent
78fff41ee3
commit
e8d3626d1c
@ -1,29 +0,0 @@
|
|||||||
function configure_bgp_service_plugin {
|
|
||||||
_neutron_service_plugin_class_add "bgp"
|
|
||||||
}
|
|
||||||
|
|
||||||
function configure_bgp {
|
|
||||||
configure_bgp_service_plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
function configure_bgp_dragent {
|
|
||||||
cp $NEUTRON_DIR/etc/bgp_dragent.ini.sample $Q_BGP_DRAGENT_CONF_FILE
|
|
||||||
|
|
||||||
iniset $Q_BGP_DRAGENT_CONF_FILE DEFAULT verbose True
|
|
||||||
iniset $Q_BGP_DRAGENT_CONF_FILE DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL
|
|
||||||
if [ -n "$BGP_ROUTER_ID" ]; then
|
|
||||||
iniset $Q_BGP_DRAGENT_CONF_FILE BGP bgp_router_id $BGP_ROUTER_ID
|
|
||||||
fi
|
|
||||||
if [ -z "$BGP_SPEAKER_DRIVER" ]; then
|
|
||||||
BGP_SPEAKER_DRIVER=$RYU_BGP_SPEAKER_DRIVER
|
|
||||||
fi
|
|
||||||
iniset $Q_BGP_DRAGENT_CONF_FILE BGP bgp_speaker_driver $BGP_SPEAKER_DRIVER
|
|
||||||
}
|
|
||||||
|
|
||||||
function start_bgp_dragent {
|
|
||||||
run_process q-bgp-agt "$AGENT_BGP_BINARY --config-file $NEUTRON_CONF --config-file /$Q_BGP_DRAGENT_CONF_FILE"
|
|
||||||
}
|
|
||||||
|
|
||||||
function stop_bgp_dragent {
|
|
||||||
stop_process q-bgp-agt
|
|
||||||
}
|
|
@ -1,6 +1,5 @@
|
|||||||
LIBDIR=$DEST/neutron/devstack/lib
|
LIBDIR=$DEST/neutron/devstack/lib
|
||||||
|
|
||||||
source $LIBDIR/bgp
|
|
||||||
source $LIBDIR/flavors
|
source $LIBDIR/flavors
|
||||||
source $LIBDIR/l2_agent
|
source $LIBDIR/l2_agent
|
||||||
source $LIBDIR/l2_agent_sriovnicswitch
|
source $LIBDIR/l2_agent_sriovnicswitch
|
||||||
@ -19,9 +18,6 @@ if [[ "$1" == "stack" ]]; then
|
|||||||
if is_service_enabled q-qos; then
|
if is_service_enabled q-qos; then
|
||||||
configure_qos
|
configure_qos
|
||||||
fi
|
fi
|
||||||
if is_service_enabled q-bgp; then
|
|
||||||
configure_bgp
|
|
||||||
fi
|
|
||||||
if [[ "$Q_AGENT" == "openvswitch" ]] && \
|
if [[ "$Q_AGENT" == "openvswitch" ]] && \
|
||||||
[[ "$Q_BUILD_OVS_FROM_GIT" == "True" ]]; then
|
[[ "$Q_BUILD_OVS_FROM_GIT" == "True" ]]; then
|
||||||
remove_ovs_packages
|
remove_ovs_packages
|
||||||
@ -33,9 +29,6 @@ if [[ "$1" == "stack" ]]; then
|
|||||||
if is_service_enabled q-agt; then
|
if is_service_enabled q-agt; then
|
||||||
configure_l2_agent
|
configure_l2_agent
|
||||||
fi
|
fi
|
||||||
if is_service_enabled q-bgp && is_service_enabled q-bgp-agt; then
|
|
||||||
configure_bgp_dragent
|
|
||||||
fi
|
|
||||||
#Note: sriov agent should run with OVS or linux bridge agent
|
#Note: sriov agent should run with OVS or linux bridge agent
|
||||||
#because they are the mechanisms that bind the DHCP and router ports.
|
#because they are the mechanisms that bind the DHCP and router ports.
|
||||||
#Currently devstack lacks the option to run two agents on the same node.
|
#Currently devstack lacks the option to run two agents on the same node.
|
||||||
@ -51,16 +44,10 @@ if [[ "$1" == "stack" ]]; then
|
|||||||
if is_service_enabled q-sriov-agt; then
|
if is_service_enabled q-sriov-agt; then
|
||||||
start_l2_agent_sriov
|
start_l2_agent_sriov
|
||||||
fi
|
fi
|
||||||
if is_service_enabled q-bgp && is_service_enabled q-bgp-agt; then
|
|
||||||
start_bgp_dragent
|
|
||||||
fi
|
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
elif [[ "$1" == "unstack" ]]; then
|
elif [[ "$1" == "unstack" ]]; then
|
||||||
if is_service_enabled q-sriov-agt; then
|
if is_service_enabled q-sriov-agt; then
|
||||||
stop_l2_agent_sriov
|
stop_l2_agent_sriov
|
||||||
fi
|
fi
|
||||||
if is_service_enabled q-bgp && is_service_enabled q-bgp-agt; then
|
|
||||||
stop_bgp_dragent
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
@ -1,8 +1 @@
|
|||||||
L2_AGENT_EXTENSIONS=${L2_AGENT_EXTENSIONS:-}
|
L2_AGENT_EXTENSIONS=${L2_AGENT_EXTENSIONS:-}
|
||||||
|
|
||||||
#BGP binary and config information
|
|
||||||
AGENT_BGP_BINARY=${AGENT_BGP_BINARY:-"$NEUTRON_BIN_DIR/neutron-bgp-dragent"}
|
|
||||||
Q_BGP_DRAGENT_CONF_FILE=${Q_BGP_DRAGENT_CONF_FILE:-"$NEUTRON_CONF_DIR/bgp_dragent.ini"}
|
|
||||||
BGP_ROUTER_ID=${BGP_ROUTER_ID:-}
|
|
||||||
|
|
||||||
RYU_BGP_SPEAKER_DRIVER="neutron.services.bgp.driver.ryu.driver.RyuBgpDriver"
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
[DEFAULT]
|
|
||||||
output_file = etc/bgp_dragent.ini.sample
|
|
||||||
wrap_width = 79
|
|
||||||
|
|
||||||
namespace = neutron.base.agent
|
|
||||||
namespace = neutron.bgp.agent
|
|
||||||
namespace = oslo.log
|
|
@ -213,26 +213,8 @@
|
|||||||
"get_flavor_service_profile": "rule:regular_user",
|
"get_flavor_service_profile": "rule:regular_user",
|
||||||
"get_auto_allocated_topology": "rule:admin_or_owner",
|
"get_auto_allocated_topology": "rule:admin_or_owner",
|
||||||
|
|
||||||
"get_bgp_speaker": "rule:admin_only",
|
|
||||||
"create_bgp_speaker": "rule:admin_only",
|
|
||||||
"update_bgp_speaker": "rule:admin_only",
|
|
||||||
"delete_bgp_speaker": "rule:admin_only",
|
|
||||||
|
|
||||||
"get_bgp_peer": "rule:admin_only",
|
|
||||||
"create_bgp_peer": "rule:admin_only",
|
|
||||||
"update_bgp_peer": "rule:admin_only",
|
|
||||||
"delete_bgp_peer": "rule:admin_only",
|
|
||||||
|
|
||||||
"add_bgp_peer": "rule:admin_only",
|
|
||||||
"remove_bgp_peer": "rule:admin_only",
|
|
||||||
|
|
||||||
"add_gateway_network": "rule:admin_only",
|
"add_gateway_network": "rule:admin_only",
|
||||||
"remove_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"
|
|
||||||
}
|
}
|
||||||
|
@ -1,105 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
import oslo_messaging
|
|
||||||
|
|
||||||
from neutron.common import rpc as n_rpc
|
|
||||||
from neutron.services.bgp.common import constants as bgp_consts
|
|
||||||
|
|
||||||
|
|
||||||
class BgpDrAgentNotifyApi(object):
|
|
||||||
"""API for plugin to notify BGP DrAgent.
|
|
||||||
|
|
||||||
This class implements the client side of an rpc interface. The server side
|
|
||||||
is neutron.services.bgp_speaker.agent.bgp_dragent.BgpDrAgent. For more
|
|
||||||
information about rpc interfaces, please see doc/source/devref/rpc_api.rst.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, topic=bgp_consts.BGP_DRAGENT):
|
|
||||||
target = oslo_messaging.Target(topic=topic, version='1.0')
|
|
||||||
self.client = n_rpc.get_client(target)
|
|
||||||
self.topic = topic
|
|
||||||
|
|
||||||
def bgp_routes_advertisement(self, context, bgp_speaker_id,
|
|
||||||
routes, host):
|
|
||||||
"""Tell BgpDrAgent to begin advertising the given route.
|
|
||||||
|
|
||||||
Invoked on FIP association, adding router port to a tenant network,
|
|
||||||
and new DVR port-host bindings, and subnet creation(?).
|
|
||||||
"""
|
|
||||||
self._notification_host_cast(context, 'bgp_routes_advertisement_end',
|
|
||||||
{'advertise_routes': {'speaker_id': bgp_speaker_id,
|
|
||||||
'routes': routes}}, host)
|
|
||||||
|
|
||||||
def bgp_routes_withdrawal(self, context, bgp_speaker_id,
|
|
||||||
routes, host):
|
|
||||||
"""Tell BgpDrAgent to stop advertising the given route.
|
|
||||||
|
|
||||||
Invoked on FIP disassociation, removal of a router port on a
|
|
||||||
network, and removal of DVR port-host binding, and subnet delete(?).
|
|
||||||
"""
|
|
||||||
self._notification_host_cast(context, 'bgp_routes_withdrawal_end',
|
|
||||||
{'withdraw_routes': {'speaker_id': bgp_speaker_id,
|
|
||||||
'routes': routes}}, host)
|
|
||||||
|
|
||||||
def bgp_peer_disassociated(self, context, bgp_speaker_id,
|
|
||||||
bgp_peer_ip, host):
|
|
||||||
"""Tell BgpDrAgent about a new BGP Peer association.
|
|
||||||
|
|
||||||
This effectively tells the BgpDrAgent to stop a peering session.
|
|
||||||
"""
|
|
||||||
self._notification_host_cast(context, 'bgp_peer_disassociation_end',
|
|
||||||
{'bgp_peer': {'speaker_id': bgp_speaker_id,
|
|
||||||
'peer_ip': bgp_peer_ip}}, host)
|
|
||||||
|
|
||||||
def bgp_peer_associated(self, context, bgp_speaker_id,
|
|
||||||
bgp_peer_id, host):
|
|
||||||
"""Tell BgpDrAgent about a BGP Peer disassociation.
|
|
||||||
|
|
||||||
This effectively tells the bgp_dragent to open a peering session.
|
|
||||||
"""
|
|
||||||
self._notification_host_cast(context, 'bgp_peer_association_end',
|
|
||||||
{'bgp_peer': {'speaker_id': bgp_speaker_id,
|
|
||||||
'peer_id': bgp_peer_id}}, host)
|
|
||||||
|
|
||||||
def bgp_speaker_created(self, context, bgp_speaker_id, host):
|
|
||||||
"""Tell BgpDrAgent about the creation of a BGP Speaker.
|
|
||||||
|
|
||||||
Because a BGP Speaker can be created with BgpPeer binding in place,
|
|
||||||
we need to inform the BgpDrAgent of a new BGP Speaker in case a
|
|
||||||
peering session needs to opened immediately.
|
|
||||||
"""
|
|
||||||
self._notification_host_cast(context, 'bgp_speaker_create_end',
|
|
||||||
{'bgp_speaker': {'id': bgp_speaker_id}}, host)
|
|
||||||
|
|
||||||
def bgp_speaker_removed(self, context, bgp_speaker_id, host):
|
|
||||||
"""Tell BgpDrAgent about the removal of a BGP Speaker.
|
|
||||||
|
|
||||||
Because a BGP Speaker can be removed with BGP Peer binding in
|
|
||||||
place, we need to inform the BgpDrAgent of the removal of a
|
|
||||||
BGP Speaker in case peering sessions need to be stopped.
|
|
||||||
"""
|
|
||||||
self._notification_host_cast(context, 'bgp_speaker_remove_end',
|
|
||||||
{'bgp_speaker': {'id': bgp_speaker_id}}, host)
|
|
||||||
|
|
||||||
def _notification_host_cast(self, context, method, payload, host):
|
|
||||||
"""Send payload to BgpDrAgent in the cast mode"""
|
|
||||||
cctxt = self.client.prepare(topic=self.topic, server=host)
|
|
||||||
cctxt.cast(context, method, payload=payload)
|
|
||||||
|
|
||||||
def _notification_host_call(self, context, method, payload, host):
|
|
||||||
"""Send payload to BgpDrAgent in the call mode"""
|
|
||||||
cctxt = self.client.prepare(topic=self.topic, server=host)
|
|
||||||
cctxt.call(context, method, payload=payload)
|
|
@ -1,65 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
import oslo_messaging
|
|
||||||
|
|
||||||
from neutron.extensions import bgp as bgp_ext
|
|
||||||
from neutron import manager
|
|
||||||
|
|
||||||
|
|
||||||
class BgpSpeakerRpcCallback(object):
|
|
||||||
"""BgpDrAgent RPC callback in plugin implementations.
|
|
||||||
|
|
||||||
This class implements the server side of an RPC interface.
|
|
||||||
The client side of this interface can be found in
|
|
||||||
neutron.services.bgp_speaker.agent.bgp_dragent.BgpDrPluginApi.
|
|
||||||
For more information about changing RPC interfaces,
|
|
||||||
see doc/source/devref/rpc_api.rst.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# API version history:
|
|
||||||
# 1.0 BGPDRPluginApi BASE_RPC_API_VERSION
|
|
||||||
target = oslo_messaging.Target(version='1.0')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def plugin(self):
|
|
||||||
if not hasattr(self, '_plugin'):
|
|
||||||
self._plugin = manager.NeutronManager.get_service_plugins().get(
|
|
||||||
bgp_ext.BGP_EXT_ALIAS)
|
|
||||||
return self._plugin
|
|
||||||
|
|
||||||
def get_bgp_speaker_info(self, context, bgp_speaker_id):
|
|
||||||
"""Return BGP Speaker details such as peer list and local_as.
|
|
||||||
|
|
||||||
Invoked by the BgpDrAgent to lookup the details of a BGP Speaker.
|
|
||||||
"""
|
|
||||||
return self.plugin.get_bgp_speaker_with_advertised_routes(
|
|
||||||
context, bgp_speaker_id)
|
|
||||||
|
|
||||||
def get_bgp_peer_info(self, context, bgp_peer_id):
|
|
||||||
"""Return BgpPeer details such as IP, remote_as, and credentials.
|
|
||||||
|
|
||||||
Invoked by the BgpDrAgent to lookup the details of a BGP peer.
|
|
||||||
"""
|
|
||||||
return self.plugin.get_bgp_peer(context, bgp_peer_id,
|
|
||||||
['peer_ip', 'remote_as',
|
|
||||||
'auth_type', 'password'])
|
|
||||||
|
|
||||||
def get_bgp_speakers(self, context, host=None, **kwargs):
|
|
||||||
"""Returns the list of all BgpSpeakers.
|
|
||||||
|
|
||||||
Typically invoked by the BgpDrAgent as part of its bootstrap process.
|
|
||||||
"""
|
|
||||||
return self.plugin.get_bgp_speakers_for_agent_host(context, host)
|
|
@ -1,20 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
from neutron.services.bgp.agent import entry as bgp_dragent
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
bgp_dragent.main()
|
|
1009
neutron/db/bgp_db.py
1009
neutron/db/bgp_db.py
File diff suppressed because it is too large
Load Diff
@ -1,215 +0,0 @@
|
|||||||
# 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 sqlalchemy.orm import exc
|
|
||||||
|
|
||||||
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:
|
|
||||||
agents = self.bgp_drscheduler.schedule(context,
|
|
||||||
created_bgp_speaker)
|
|
||||||
for agent in agents:
|
|
||||||
self._bgp_rpc.bgp_speaker_created(context,
|
|
||||||
created_bgp_speaker['id'],
|
|
||||||
agent.host)
|
|
||||||
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)
|
|
||||||
|
|
||||||
self._bgp_rpc.bgp_speaker_created(context, speaker_id, agent_db.host)
|
|
||||||
|
|
||||||
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})
|
|
||||||
|
|
||||||
self._bgp_rpc.bgp_speaker_removed(context, speaker_id, agent_db.host)
|
|
||||||
|
|
||||||
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})}
|
|
||||||
|
|
||||||
def get_bgp_speakers_for_agent_host(self, context, host):
|
|
||||||
agent = self._get_agent_by_type_and_host(
|
|
||||||
context, bgp_consts.AGENT_TYPE_BGP_ROUTING, host)
|
|
||||||
if not agent.admin_state_up:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
query = context.session.query(BgpSpeakerDrAgentBinding)
|
|
||||||
query = query.filter(BgpSpeakerDrAgentBinding.agent_id == agent.id)
|
|
||||||
try:
|
|
||||||
binding = query.one()
|
|
||||||
except exc.NoResultFound:
|
|
||||||
return []
|
|
||||||
bgp_speaker = self.get_bgp_speaker_with_advertised_routes(
|
|
||||||
context, binding['bgp_speaker_id'])
|
|
||||||
return [bgp_speaker]
|
|
||||||
|
|
||||||
def get_bgp_speaker_by_speaker_id(self, context, bgp_speaker_id):
|
|
||||||
try:
|
|
||||||
return self.get_bgp_speaker(context, bgp_speaker_id)
|
|
||||||
except exc.NoResultFound:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def get_bgp_peer_by_peer_id(self, context, bgp_peer_id):
|
|
||||||
try:
|
|
||||||
return self.get_bgp_peer(context, bgp_peer_id)
|
|
||||||
except exc.NoResultFound:
|
|
||||||
return {}
|
|
@ -31,6 +31,15 @@ REPO_ARISTA_TABLES = [
|
|||||||
'arista_provisioned_tenants',
|
'arista_provisioned_tenants',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# BGP models in openstack/neutron-dynamic-routing
|
||||||
|
REPO_NEUTRON_DYNAMIC_ROUTING_TABLES = [
|
||||||
|
'bgp_speakers',
|
||||||
|
'bgp_peers',
|
||||||
|
'bgp_speaker_network_bindings',
|
||||||
|
'bgp_speaker_peer_bindings',
|
||||||
|
'bgp_speaker_dragent_bindings',
|
||||||
|
]
|
||||||
|
|
||||||
# Models moved to openstack/networking-cisco
|
# Models moved to openstack/networking-cisco
|
||||||
REPO_CISCO_TABLES = [
|
REPO_CISCO_TABLES = [
|
||||||
'cisco_ml2_apic_contracts',
|
'cisco_ml2_apic_contracts',
|
||||||
@ -113,6 +122,7 @@ REPO_NUAGE_TABLES = [
|
|||||||
|
|
||||||
TABLES = (FWAAS_TABLES + LBAAS_TABLES + VPNAAS_TABLES +
|
TABLES = (FWAAS_TABLES + LBAAS_TABLES + VPNAAS_TABLES +
|
||||||
REPO_ARISTA_TABLES +
|
REPO_ARISTA_TABLES +
|
||||||
|
REPO_NEUTRON_DYNAMIC_ROUTING_TABLES +
|
||||||
REPO_CISCO_TABLES +
|
REPO_CISCO_TABLES +
|
||||||
REPO_VMWARE_TABLES +
|
REPO_VMWARE_TABLES +
|
||||||
REPO_BROCADE_TABLES +
|
REPO_BROCADE_TABLES +
|
||||||
|
@ -25,8 +25,6 @@ from neutron.db import address_scope_db # noqa
|
|||||||
from neutron.db import agents_db # noqa
|
from neutron.db import agents_db # noqa
|
||||||
from neutron.db import agentschedulers_db # noqa
|
from neutron.db import agentschedulers_db # noqa
|
||||||
from neutron.db import allowedaddresspairs_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 dns_db # noqa
|
||||||
from neutron.db import dvr_mac_db # noqa
|
from neutron.db import dvr_mac_db # noqa
|
||||||
from neutron.db import external_net_db # noqa
|
from neutron.db import external_net_db # noqa
|
||||||
|
@ -1,208 +0,0 @@
|
|||||||
# Copyright 2016 Hewlett Packard Development Coompany LP
|
|
||||||
#
|
|
||||||
# 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_lib.api import converters
|
|
||||||
from neutron_lib import exceptions
|
|
||||||
|
|
||||||
from neutron._i18n import _
|
|
||||||
from neutron.api import extensions
|
|
||||||
from neutron.api.v2 import attributes as attr
|
|
||||||
from neutron.api.v2 import resource_helper as rh
|
|
||||||
from neutron.services.bgp.common import constants as bgp_consts
|
|
||||||
|
|
||||||
BGP_EXT_ALIAS = 'bgp'
|
|
||||||
BGP_SPEAKER_RESOURCE_NAME = 'bgp-speaker'
|
|
||||||
BGP_SPEAKER_BODY_KEY_NAME = 'bgp_speaker'
|
|
||||||
BGP_PEER_BODY_KEY_NAME = 'bgp_peer'
|
|
||||||
|
|
||||||
|
|
||||||
RESOURCE_ATTRIBUTE_MAP = {
|
|
||||||
BGP_SPEAKER_RESOURCE_NAME + 's': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True, 'primary_key': True},
|
|
||||||
'name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': attr.NAME_MAX_LEN},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'local_as': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:range': (bgp_consts.MIN_ASNUM,
|
|
||||||
bgp_consts.MAX_ASNUM)},
|
|
||||||
'is_visible': True, 'default': None,
|
|
||||||
'required_by_policy': False,
|
|
||||||
'enforce_policy': False},
|
|
||||||
'ip_version': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:values': [4, 6]},
|
|
||||||
'is_visible': True, 'default': None,
|
|
||||||
'required_by_policy': False,
|
|
||||||
'enforce_policy': False},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'required_by_policy': False,
|
|
||||||
'validate': {'type:string': attr.TENANT_ID_MAX_LEN},
|
|
||||||
'is_visible': True},
|
|
||||||
'peers': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid_list': None},
|
|
||||||
'is_visible': True, 'default': [],
|
|
||||||
'required_by_policy': False,
|
|
||||||
'enforce_policy': True},
|
|
||||||
'networks': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid_list': None},
|
|
||||||
'is_visible': True, 'default': [],
|
|
||||||
'required_by_policy': False,
|
|
||||||
'enforce_policy': True},
|
|
||||||
'advertise_floating_ip_host_routes': {
|
|
||||||
'allow_post': True,
|
|
||||||
'allow_put': True,
|
|
||||||
'convert_to': converters.convert_to_boolean,
|
|
||||||
'validate': {'type:boolean': None},
|
|
||||||
'is_visible': True, 'default': True,
|
|
||||||
'required_by_policy': False,
|
|
||||||
'enforce_policy': True},
|
|
||||||
'advertise_tenant_networks': {
|
|
||||||
'allow_post': True,
|
|
||||||
'allow_put': True,
|
|
||||||
'convert_to': converters.convert_to_boolean,
|
|
||||||
'validate': {'type:boolean': None},
|
|
||||||
'is_visible': True, 'default': True,
|
|
||||||
'required_by_policy': False,
|
|
||||||
'enforce_policy': True},
|
|
||||||
},
|
|
||||||
'bgp-peers': {
|
|
||||||
'id': {'allow_post': False, 'allow_put': False,
|
|
||||||
'validate': {'type:uuid': None},
|
|
||||||
'is_visible': True, 'primary_key': True},
|
|
||||||
'name': {'allow_post': True, 'allow_put': True,
|
|
||||||
'validate': {'type:string': attr.NAME_MAX_LEN},
|
|
||||||
'is_visible': True, 'default': ''},
|
|
||||||
'peer_ip': {'allow_post': True, 'allow_put': False,
|
|
||||||
'required_by_policy': True,
|
|
||||||
'validate': {'type:ip_address': None},
|
|
||||||
'is_visible': True},
|
|
||||||
'remote_as': {'allow_post': True, 'allow_put': False,
|
|
||||||
'validate': {'type:range': (bgp_consts.MIN_ASNUM,
|
|
||||||
bgp_consts.MAX_ASNUM)},
|
|
||||||
'is_visible': True, 'default': None,
|
|
||||||
'required_by_policy': False,
|
|
||||||
'enforce_policy': False},
|
|
||||||
'auth_type': {'allow_post': True, 'allow_put': False,
|
|
||||||
'required_by_policy': True,
|
|
||||||
'validate': {'type:values':
|
|
||||||
bgp_consts.SUPPORTED_AUTH_TYPES},
|
|
||||||
'is_visible': True},
|
|
||||||
'password': {'allow_post': True, 'allow_put': True,
|
|
||||||
'required_by_policy': True,
|
|
||||||
'validate': {'type:string_or_none': None},
|
|
||||||
'is_visible': False,
|
|
||||||
'default': None},
|
|
||||||
'tenant_id': {'allow_post': True, 'allow_put': False,
|
|
||||||
'required_by_policy': False,
|
|
||||||
'validate': {'type:string': attr.TENANT_ID_MAX_LEN},
|
|
||||||
'is_visible': True}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Dynamic Routing Exceptions
|
|
||||||
class BgpSpeakerNotFound(exceptions.NotFound):
|
|
||||||
message = _("BGP speaker %(id)s could not be found.")
|
|
||||||
|
|
||||||
|
|
||||||
class BgpPeerNotFound(exceptions.NotFound):
|
|
||||||
message = _("BGP peer %(id)s could not be found.")
|
|
||||||
|
|
||||||
|
|
||||||
class BgpPeerNotAuthenticated(exceptions.NotFound):
|
|
||||||
message = _("BGP peer %(bgp_peer_id)s not authenticated.")
|
|
||||||
|
|
||||||
|
|
||||||
class BgpSpeakerPeerNotAssociated(exceptions.NotFound):
|
|
||||||
message = _("BGP peer %(bgp_peer_id)s is not associated with "
|
|
||||||
"BGP speaker %(bgp_speaker_id)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class BgpSpeakerNetworkNotAssociated(exceptions.NotFound):
|
|
||||||
message = _("Network %(network_id)s is not associated with "
|
|
||||||
"BGP speaker %(bgp_speaker_id)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class BgpSpeakerNetworkBindingError(exceptions.Conflict):
|
|
||||||
message = _("Network %(network_id)s is already bound to BgpSpeaker "
|
|
||||||
"%(bgp_speaker_id)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkNotBound(exceptions.NotFound):
|
|
||||||
message = _("Network %(network_id)s is not bound to a BgpSpeaker.")
|
|
||||||
|
|
||||||
|
|
||||||
class DuplicateBgpPeerIpException(exceptions.Conflict):
|
|
||||||
_message = _("BGP Speaker %(bgp_speaker_id)s is already configured to "
|
|
||||||
"peer with a BGP Peer at %(peer_ip)s, it cannot peer with "
|
|
||||||
"BGP Peer %(bgp_peer_id)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidBgpPeerMd5Authentication(exceptions.BadRequest):
|
|
||||||
message = _("A password must be supplied when using auth_type md5.")
|
|
||||||
|
|
||||||
|
|
||||||
class NetworkNotBoundForIpVersion(NetworkNotBound):
|
|
||||||
message = _("Network %(network_id)s is not bound to a IPv%(ip_version)s "
|
|
||||||
"BgpSpeaker.")
|
|
||||||
|
|
||||||
|
|
||||||
class Bgp(extensions.ExtensionDescriptor):
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_name(cls):
|
|
||||||
return "Neutron BGP Dynamic Routing Extension"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_alias(cls):
|
|
||||||
return BGP_EXT_ALIAS
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_description(cls):
|
|
||||||
return("Discover and advertise routes for Neutron prefixes "
|
|
||||||
"dynamically via BGP")
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_updated(cls):
|
|
||||||
return "2014-07-01T15:37:00-00:00"
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_resources(cls):
|
|
||||||
plural_mappings = rh.build_plural_mappings(
|
|
||||||
{}, RESOURCE_ATTRIBUTE_MAP)
|
|
||||||
attr.PLURALS.update(plural_mappings)
|
|
||||||
action_map = {BGP_SPEAKER_RESOURCE_NAME:
|
|
||||||
{'add_bgp_peer': 'PUT',
|
|
||||||
'remove_bgp_peer': 'PUT',
|
|
||||||
'add_gateway_network': 'PUT',
|
|
||||||
'remove_gateway_network': 'PUT',
|
|
||||||
'get_advertised_routes': 'GET'}}
|
|
||||||
exts = rh.build_resource_info(plural_mappings,
|
|
||||||
RESOURCE_ATTRIBUTE_MAP,
|
|
||||||
BGP_EXT_ALIAS,
|
|
||||||
action_map=action_map)
|
|
||||||
|
|
||||||
return exts
|
|
||||||
|
|
||||||
def get_extended_resources(self, version):
|
|
||||||
if version == "2.0":
|
|
||||||
return RESOURCE_ATTRIBUTE_MAP
|
|
||||||
else:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def update_attributes_map(self, attributes):
|
|
||||||
super(Bgp, self).update_attributes_map(
|
|
||||||
attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP)
|
|
@ -1,183 +0,0 @@
|
|||||||
# 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 neutron_lib import exceptions
|
|
||||||
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.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
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_bgp_speakers_for_agent_host(self, context, host):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_bgp_speaker_by_speaker_id(self, context, speaker_id):
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_bgp_peer_by_peer_id(self, context, bgp_peer_id):
|
|
||||||
pass
|
|
@ -1,707 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
import collections
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log as logging
|
|
||||||
import oslo_messaging
|
|
||||||
from oslo_service import loopingcall
|
|
||||||
from oslo_service import periodic_task
|
|
||||||
from oslo_utils import importutils
|
|
||||||
|
|
||||||
from neutron.agent import rpc as agent_rpc
|
|
||||||
from neutron.common import constants
|
|
||||||
from neutron.common import rpc as n_rpc
|
|
||||||
from neutron.common import topics
|
|
||||||
from neutron.common import utils
|
|
||||||
from neutron import context
|
|
||||||
from neutron.extensions import bgp as bgp_ext
|
|
||||||
from neutron._i18n import _, _LE, _LI, _LW
|
|
||||||
from neutron import manager
|
|
||||||
from neutron.services.bgp.common import constants as bgp_consts
|
|
||||||
from neutron.services.bgp.driver import exceptions as driver_exc
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class BgpDrAgent(manager.Manager):
|
|
||||||
"""BGP Dynamic Routing agent service manager.
|
|
||||||
|
|
||||||
Note that the public methods of this class are exposed as the server side
|
|
||||||
of an rpc interface. The neutron server uses
|
|
||||||
neutron.api.rpc.agentnotifiers.bgp_dr_rpc_agent_api.
|
|
||||||
BgpDrAgentNotifyApi as the client side to execute the methods
|
|
||||||
here. For more information about changing rpc interfaces, see
|
|
||||||
doc/source/devref/rpc_api.rst.
|
|
||||||
|
|
||||||
API version history:
|
|
||||||
1.0 initial Version
|
|
||||||
"""
|
|
||||||
target = oslo_messaging.Target(version='1.0')
|
|
||||||
|
|
||||||
def __init__(self, host, conf=None):
|
|
||||||
super(BgpDrAgent, self).__init__()
|
|
||||||
self.initialize_driver(conf)
|
|
||||||
self.needs_resync_reasons = collections.defaultdict(list)
|
|
||||||
self.needs_full_sync_reason = None
|
|
||||||
|
|
||||||
self.cache = BgpSpeakerCache()
|
|
||||||
self.context = context.get_admin_context_without_session()
|
|
||||||
self.plugin_rpc = BgpDrPluginApi(bgp_consts.BGP_PLUGIN,
|
|
||||||
self.context, host)
|
|
||||||
|
|
||||||
def initialize_driver(self, conf):
|
|
||||||
self.conf = conf or cfg.CONF.BGP
|
|
||||||
try:
|
|
||||||
self.dr_driver_cls = (
|
|
||||||
importutils.import_object(self.conf.bgp_speaker_driver,
|
|
||||||
self.conf))
|
|
||||||
except ImportError:
|
|
||||||
LOG.exception(_LE("Error while importing BGP speaker driver %s"),
|
|
||||||
self.conf.bgp_speaker_driver)
|
|
||||||
raise SystemExit(1)
|
|
||||||
|
|
||||||
def _handle_driver_failure(self, bgp_speaker_id, method, driver_exec):
|
|
||||||
self.schedule_resync(reason=driver_exec,
|
|
||||||
speaker_id=bgp_speaker_id)
|
|
||||||
LOG.error(_LE('Call to driver for BGP Speaker %(bgp_speaker)s '
|
|
||||||
'%(method)s has failed with exception '
|
|
||||||
'%(driver_exec)s.'),
|
|
||||||
{'bgp_speaker': bgp_speaker_id,
|
|
||||||
'method': method,
|
|
||||||
'driver_exec': driver_exec})
|
|
||||||
|
|
||||||
def after_start(self):
|
|
||||||
self.run()
|
|
||||||
LOG.info(_LI("BGP Dynamic Routing agent started"))
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""Activate BGP Dynamic Routing agent."""
|
|
||||||
self.sync_state(self.context)
|
|
||||||
self.periodic_resync(self.context)
|
|
||||||
|
|
||||||
@utils.synchronized('bgp-dragent')
|
|
||||||
def sync_state(self, context, full_sync=None, bgp_speakers=None):
|
|
||||||
try:
|
|
||||||
hosted_bgp_speakers = self.plugin_rpc.get_bgp_speakers(context)
|
|
||||||
hosted_bgp_speaker_ids = [bgp_speaker['id']
|
|
||||||
for bgp_speaker in hosted_bgp_speakers]
|
|
||||||
cached_bgp_speakers = self.cache.get_bgp_speaker_ids()
|
|
||||||
for bgp_speaker_id in cached_bgp_speakers:
|
|
||||||
if bgp_speaker_id not in hosted_bgp_speaker_ids:
|
|
||||||
self.remove_bgp_speaker_from_dragent(bgp_speaker_id)
|
|
||||||
|
|
||||||
resync_all = not bgp_speakers or full_sync
|
|
||||||
only_bs = set() if resync_all else set(bgp_speakers)
|
|
||||||
for hosted_bgp_speaker in hosted_bgp_speakers:
|
|
||||||
hosted_bs_id = hosted_bgp_speaker['id']
|
|
||||||
if resync_all or hosted_bs_id in only_bs:
|
|
||||||
if not self.cache.is_bgp_speaker_added(hosted_bs_id):
|
|
||||||
self.safe_configure_dragent_for_bgp_speaker(
|
|
||||||
hosted_bgp_speaker)
|
|
||||||
continue
|
|
||||||
self.sync_bgp_speaker(hosted_bgp_speaker)
|
|
||||||
resync_reason = "Periodic route cache refresh"
|
|
||||||
self.schedule_resync(speaker_id=hosted_bs_id,
|
|
||||||
reason=resync_reason)
|
|
||||||
except Exception as e:
|
|
||||||
self.schedule_full_resync(reason=e)
|
|
||||||
LOG.error(_LE('Unable to sync BGP speaker state.'))
|
|
||||||
|
|
||||||
def sync_bgp_speaker(self, bgp_speaker):
|
|
||||||
# sync BGP Speakers
|
|
||||||
bgp_peer_ips = set(
|
|
||||||
[bgp_peer['peer_ip'] for bgp_peer in bgp_speaker['peers']])
|
|
||||||
cached_bgp_peer_ips = set(
|
|
||||||
self.cache.get_bgp_peer_ips(bgp_speaker['id']))
|
|
||||||
removed_bgp_peer_ips = cached_bgp_peer_ips - bgp_peer_ips
|
|
||||||
|
|
||||||
for bgp_peer_ip in removed_bgp_peer_ips:
|
|
||||||
self.remove_bgp_peer_from_bgp_speaker(bgp_speaker['id'],
|
|
||||||
bgp_peer_ip)
|
|
||||||
if bgp_peer_ips:
|
|
||||||
self.add_bgp_peers_to_bgp_speaker(bgp_speaker)
|
|
||||||
|
|
||||||
# sync advertise routes
|
|
||||||
cached_adv_routes = self.cache.get_adv_routes(bgp_speaker['id'])
|
|
||||||
adv_routes = bgp_speaker['advertised_routes']
|
|
||||||
if cached_adv_routes == adv_routes:
|
|
||||||
return
|
|
||||||
|
|
||||||
for cached_route in cached_adv_routes:
|
|
||||||
if cached_route not in adv_routes:
|
|
||||||
self.withdraw_route_via_bgp_speaker(bgp_speaker['id'],
|
|
||||||
bgp_speaker['local_as'],
|
|
||||||
cached_route)
|
|
||||||
|
|
||||||
self.advertise_routes_via_bgp_speaker(bgp_speaker)
|
|
||||||
|
|
||||||
@utils.exception_logger()
|
|
||||||
def _periodic_resync_helper(self, context):
|
|
||||||
"""Resync the BgpDrAgent state at the configured interval."""
|
|
||||||
if self.needs_resync_reasons or self.needs_full_sync_reason:
|
|
||||||
full_sync = self.needs_full_sync_reason
|
|
||||||
reasons = self.needs_resync_reasons
|
|
||||||
# Reset old reasons
|
|
||||||
self.needs_full_sync_reason = None
|
|
||||||
self.needs_resync_reasons = collections.defaultdict(list)
|
|
||||||
if full_sync:
|
|
||||||
LOG.debug("resync all: %(reason)s", {"reason": full_sync})
|
|
||||||
for bgp_speaker, reason in reasons.items():
|
|
||||||
LOG.debug("resync (%(bgp_speaker)s): %(reason)s",
|
|
||||||
{"reason": reason, "bgp_speaker": bgp_speaker})
|
|
||||||
self.sync_state(
|
|
||||||
context, full_sync=full_sync, bgp_speakers=reasons.keys())
|
|
||||||
|
|
||||||
# NOTE: spacing is set 1 sec. The actual interval is controlled
|
|
||||||
# by neutron/service.py which defaults to CONF.periodic_interval
|
|
||||||
@periodic_task.periodic_task(spacing=1)
|
|
||||||
def periodic_resync(self, context):
|
|
||||||
LOG.debug("Started periodic resync.")
|
|
||||||
self._periodic_resync_helper(context)
|
|
||||||
|
|
||||||
@utils.synchronized('bgp-dr-agent')
|
|
||||||
def bgp_speaker_create_end(self, context, payload):
|
|
||||||
"""Handle bgp_speaker_create_end notification event."""
|
|
||||||
bgp_speaker_id = payload['bgp_speaker']['id']
|
|
||||||
LOG.debug('Received BGP speaker create notification for '
|
|
||||||
'speaker_id=%(speaker_id)s from the neutron server.',
|
|
||||||
{'speaker_id': bgp_speaker_id})
|
|
||||||
self.add_bgp_speaker_helper(bgp_speaker_id)
|
|
||||||
|
|
||||||
@utils.synchronized('bgp-dr-agent')
|
|
||||||
def bgp_speaker_remove_end(self, context, payload):
|
|
||||||
"""Handle bgp_speaker_create_end notification event."""
|
|
||||||
|
|
||||||
bgp_speaker_id = payload['bgp_speaker']['id']
|
|
||||||
LOG.debug('Received BGP speaker remove notification for '
|
|
||||||
'speaker_id=%(speaker_id)s from the neutron server.',
|
|
||||||
{'speaker_id': bgp_speaker_id})
|
|
||||||
self.remove_bgp_speaker_from_dragent(bgp_speaker_id)
|
|
||||||
|
|
||||||
@utils.synchronized('bgp-dr-agent')
|
|
||||||
def bgp_peer_association_end(self, context, payload):
|
|
||||||
"""Handle bgp_peer_association_end notification event."""
|
|
||||||
|
|
||||||
bgp_peer_id = payload['bgp_peer']['peer_id']
|
|
||||||
bgp_speaker_id = payload['bgp_peer']['speaker_id']
|
|
||||||
LOG.debug('Received BGP peer associate notification for '
|
|
||||||
'speaker_id=%(speaker_id)s peer_id=%(peer_id)s '
|
|
||||||
'from the neutron server.',
|
|
||||||
{'speaker_id': bgp_speaker_id,
|
|
||||||
'peer_id': bgp_peer_id})
|
|
||||||
self.add_bgp_peer_helper(bgp_speaker_id, bgp_peer_id)
|
|
||||||
|
|
||||||
@utils.synchronized('bgp-dr-agent')
|
|
||||||
def bgp_peer_disassociation_end(self, context, payload):
|
|
||||||
"""Handle bgp_peer_disassociation_end notification event."""
|
|
||||||
|
|
||||||
bgp_peer_ip = payload['bgp_peer']['peer_ip']
|
|
||||||
bgp_speaker_id = payload['bgp_peer']['speaker_id']
|
|
||||||
LOG.debug('Received BGP peer disassociate notification for '
|
|
||||||
'speaker_id=%(speaker_id)s peer_ip=%(peer_ip)s '
|
|
||||||
'from the neutron server.',
|
|
||||||
{'speaker_id': bgp_speaker_id,
|
|
||||||
'peer_ip': bgp_peer_ip})
|
|
||||||
self.remove_bgp_peer_from_bgp_speaker(bgp_speaker_id, bgp_peer_ip)
|
|
||||||
|
|
||||||
@utils.synchronized('bgp-dr-agent')
|
|
||||||
def bgp_routes_advertisement_end(self, context, payload):
|
|
||||||
"""Handle bgp_routes_advertisement_end notification event."""
|
|
||||||
|
|
||||||
bgp_speaker_id = payload['advertise_routes']['speaker_id']
|
|
||||||
LOG.debug('Received routes advertisement end notification '
|
|
||||||
'for speaker_id=%(speaker_id)s from the neutron server.',
|
|
||||||
{'speaker_id': bgp_speaker_id})
|
|
||||||
routes = payload['advertise_routes']['routes']
|
|
||||||
self.add_routes_helper(bgp_speaker_id, routes)
|
|
||||||
|
|
||||||
@utils.synchronized('bgp-dr-agent')
|
|
||||||
def bgp_routes_withdrawal_end(self, context, payload):
|
|
||||||
"""Handle bgp_routes_withdrawal_end notification event."""
|
|
||||||
|
|
||||||
bgp_speaker_id = payload['withdraw_routes']['speaker_id']
|
|
||||||
LOG.debug('Received route withdrawal notification for '
|
|
||||||
'speaker_id=%(speaker_id)s from the neutron server.',
|
|
||||||
{'speaker_id': bgp_speaker_id})
|
|
||||||
routes = payload['withdraw_routes']['routes']
|
|
||||||
self.withdraw_routes_helper(bgp_speaker_id, routes)
|
|
||||||
|
|
||||||
def add_bgp_speaker_helper(self, bgp_speaker_id):
|
|
||||||
"""Add BGP speaker."""
|
|
||||||
bgp_speaker = self.safe_get_bgp_speaker_info(bgp_speaker_id)
|
|
||||||
if bgp_speaker:
|
|
||||||
self.add_bgp_speaker_on_dragent(bgp_speaker)
|
|
||||||
|
|
||||||
def add_bgp_peer_helper(self, bgp_speaker_id, bgp_peer_id):
|
|
||||||
"""Add BGP peer."""
|
|
||||||
# Ideally BGP Speaker must be added by now, If not then let's
|
|
||||||
# re-sync.
|
|
||||||
if not self.cache.is_bgp_speaker_added(bgp_speaker_id):
|
|
||||||
self.schedule_resync(speaker_id=bgp_speaker_id,
|
|
||||||
reason="BGP Speaker Out-of-sync")
|
|
||||||
return
|
|
||||||
|
|
||||||
bgp_peer = self.safe_get_bgp_peer_info(bgp_speaker_id,
|
|
||||||
bgp_peer_id)
|
|
||||||
if bgp_peer:
|
|
||||||
bgp_speaker_as = self.cache.get_bgp_speaker_local_as(
|
|
||||||
bgp_speaker_id)
|
|
||||||
self.add_bgp_peer_to_bgp_speaker(bgp_speaker_id,
|
|
||||||
bgp_speaker_as,
|
|
||||||
bgp_peer)
|
|
||||||
|
|
||||||
def add_routes_helper(self, bgp_speaker_id, routes):
|
|
||||||
"""Advertise routes to BGP speaker."""
|
|
||||||
# Ideally BGP Speaker must be added by now, If not then let's
|
|
||||||
# re-sync.
|
|
||||||
if not self.cache.is_bgp_speaker_added(bgp_speaker_id):
|
|
||||||
self.schedule_resync(speaker_id=bgp_speaker_id,
|
|
||||||
reason="BGP Speaker Out-of-sync")
|
|
||||||
return
|
|
||||||
|
|
||||||
bgp_speaker_as = self.cache.get_bgp_speaker_local_as(bgp_speaker_id)
|
|
||||||
for route in routes:
|
|
||||||
self.advertise_route_via_bgp_speaker(bgp_speaker_id,
|
|
||||||
bgp_speaker_as,
|
|
||||||
route)
|
|
||||||
if self.is_resync_scheduled(bgp_speaker_id):
|
|
||||||
break
|
|
||||||
|
|
||||||
def withdraw_routes_helper(self, bgp_speaker_id, routes):
|
|
||||||
"""Withdraw routes advertised by BGP speaker."""
|
|
||||||
# Ideally BGP Speaker must be added by now, If not then let's
|
|
||||||
# re-sync.
|
|
||||||
if not self.cache.is_bgp_speaker_added(bgp_speaker_id):
|
|
||||||
self.schedule_resync(speaker_id=bgp_speaker_id,
|
|
||||||
reason="BGP Speaker Out-of-sync")
|
|
||||||
return
|
|
||||||
|
|
||||||
bgp_speaker_as = self.cache.get_bgp_speaker_local_as(bgp_speaker_id)
|
|
||||||
for route in routes:
|
|
||||||
self.withdraw_route_via_bgp_speaker(bgp_speaker_id,
|
|
||||||
bgp_speaker_as,
|
|
||||||
route)
|
|
||||||
if self.is_resync_scheduled(bgp_speaker_id):
|
|
||||||
break
|
|
||||||
|
|
||||||
def safe_get_bgp_speaker_info(self, bgp_speaker_id):
|
|
||||||
try:
|
|
||||||
bgp_speaker = self.plugin_rpc.get_bgp_speaker_info(self.context,
|
|
||||||
bgp_speaker_id)
|
|
||||||
if not bgp_speaker:
|
|
||||||
LOG.warning(_LW('BGP Speaker %s has been deleted.'),
|
|
||||||
bgp_speaker_id)
|
|
||||||
return bgp_speaker
|
|
||||||
except Exception as e:
|
|
||||||
self.schedule_resync(speaker_id=bgp_speaker_id,
|
|
||||||
reason=e)
|
|
||||||
LOG.error(_LE('BGP Speaker %(bgp_speaker)s info call '
|
|
||||||
'failed with reason=%(e)s.'),
|
|
||||||
{'bgp_speaker': bgp_speaker_id, 'e': e})
|
|
||||||
|
|
||||||
def safe_get_bgp_peer_info(self, bgp_speaker_id, bgp_peer_id):
|
|
||||||
try:
|
|
||||||
bgp_peer = self.plugin_rpc.get_bgp_peer_info(self.context,
|
|
||||||
bgp_peer_id)
|
|
||||||
if not bgp_peer:
|
|
||||||
LOG.warning(_LW('BGP Peer %s has been deleted.'), bgp_peer)
|
|
||||||
return bgp_peer
|
|
||||||
except Exception as e:
|
|
||||||
self.schedule_resync(speaker_id=bgp_speaker_id,
|
|
||||||
reason=e)
|
|
||||||
LOG.error(_LE('BGP peer %(bgp_peer)s info call '
|
|
||||||
'failed with reason=%(e)s.'),
|
|
||||||
{'bgp_peer': bgp_peer_id, 'e': e})
|
|
||||||
|
|
||||||
@utils.exception_logger()
|
|
||||||
def safe_configure_dragent_for_bgp_speaker(self, bgp_speaker):
|
|
||||||
try:
|
|
||||||
self.add_bgp_speaker_on_dragent(bgp_speaker)
|
|
||||||
except (bgp_ext.BgpSpeakerNotFound, RuntimeError):
|
|
||||||
LOG.warning(_LW('BGP speaker %s may have been deleted and its '
|
|
||||||
'resources may have already been disposed.'),
|
|
||||||
bgp_speaker['id'])
|
|
||||||
|
|
||||||
def add_bgp_speaker_on_dragent(self, bgp_speaker):
|
|
||||||
# Caching BGP speaker details in BGPSpeakerCache. Will be used
|
|
||||||
# during smooth.
|
|
||||||
self.cache.put_bgp_speaker(bgp_speaker)
|
|
||||||
|
|
||||||
LOG.debug('Calling driver for adding BGP speaker %(speaker_id)s,'
|
|
||||||
' speaking for local_as %(local_as)s',
|
|
||||||
{'speaker_id': bgp_speaker['id'],
|
|
||||||
'local_as': bgp_speaker['local_as']})
|
|
||||||
try:
|
|
||||||
self.dr_driver_cls.add_bgp_speaker(bgp_speaker['local_as'])
|
|
||||||
except driver_exc.BgpSpeakerAlreadyScheduled:
|
|
||||||
return
|
|
||||||
except Exception as e:
|
|
||||||
self._handle_driver_failure(bgp_speaker['id'],
|
|
||||||
'add_bgp_speaker', e)
|
|
||||||
|
|
||||||
# Add peer and route information to the driver.
|
|
||||||
self.add_bgp_peers_to_bgp_speaker(bgp_speaker)
|
|
||||||
self.advertise_routes_via_bgp_speaker(bgp_speaker)
|
|
||||||
self.schedule_resync(speaker_id=bgp_speaker['id'],
|
|
||||||
reason="Periodic route cache refresh")
|
|
||||||
|
|
||||||
def remove_bgp_speaker_from_dragent(self, bgp_speaker_id):
|
|
||||||
if self.cache.is_bgp_speaker_added(bgp_speaker_id):
|
|
||||||
bgp_speaker_as = self.cache.get_bgp_speaker_local_as(
|
|
||||||
bgp_speaker_id)
|
|
||||||
self.cache.remove_bgp_speaker_by_id(bgp_speaker_id)
|
|
||||||
|
|
||||||
LOG.debug('Calling driver for removing BGP speaker %(speaker_as)s',
|
|
||||||
{'speaker_as': bgp_speaker_as})
|
|
||||||
try:
|
|
||||||
self.dr_driver_cls.delete_bgp_speaker(bgp_speaker_as)
|
|
||||||
except Exception as e:
|
|
||||||
self._handle_driver_failure(bgp_speaker_id,
|
|
||||||
'remove_bgp_speaker', e)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Ideally, only the added speakers can be removed by the neutron
|
|
||||||
# server. Looks like there might be some synchronization
|
|
||||||
# issue between the server and the agent. Let's initiate a re-sync
|
|
||||||
# to resolve the issue.
|
|
||||||
self.schedule_resync(speaker_id=bgp_speaker_id,
|
|
||||||
reason="BGP Speaker Out-of-sync")
|
|
||||||
|
|
||||||
def add_bgp_peers_to_bgp_speaker(self, bgp_speaker):
|
|
||||||
for bgp_peer in bgp_speaker['peers']:
|
|
||||||
self.add_bgp_peer_to_bgp_speaker(bgp_speaker['id'],
|
|
||||||
bgp_speaker['local_as'],
|
|
||||||
bgp_peer)
|
|
||||||
if self.is_resync_scheduled(bgp_speaker['id']):
|
|
||||||
break
|
|
||||||
|
|
||||||
def add_bgp_peer_to_bgp_speaker(self, bgp_speaker_id,
|
|
||||||
bgp_speaker_as, bgp_peer):
|
|
||||||
if self.cache.get_bgp_peer_by_ip(bgp_speaker_id, bgp_peer['peer_ip']):
|
|
||||||
return
|
|
||||||
|
|
||||||
self.cache.put_bgp_peer(bgp_speaker_id, bgp_peer)
|
|
||||||
|
|
||||||
LOG.debug('Calling driver interface for adding BGP peer %(peer_ip)s '
|
|
||||||
'remote_as=%(remote_as)s to BGP Speaker running for '
|
|
||||||
'local_as=%(local_as)d',
|
|
||||||
{'peer_ip': bgp_peer['peer_ip'],
|
|
||||||
'remote_as': bgp_peer['remote_as'],
|
|
||||||
'local_as': bgp_speaker_as})
|
|
||||||
try:
|
|
||||||
self.dr_driver_cls.add_bgp_peer(bgp_speaker_as,
|
|
||||||
bgp_peer['peer_ip'],
|
|
||||||
bgp_peer['remote_as'],
|
|
||||||
bgp_peer['auth_type'],
|
|
||||||
bgp_peer['password'])
|
|
||||||
except Exception as e:
|
|
||||||
self._handle_driver_failure(bgp_speaker_id,
|
|
||||||
'add_bgp_peer', e)
|
|
||||||
|
|
||||||
def remove_bgp_peer_from_bgp_speaker(self, bgp_speaker_id, bgp_peer_ip):
|
|
||||||
# Ideally BGP Speaker must be added by now, If not then let's
|
|
||||||
# re-sync.
|
|
||||||
if not self.cache.is_bgp_speaker_added(bgp_speaker_id):
|
|
||||||
self.schedule_resync(speaker_id=bgp_speaker_id,
|
|
||||||
reason="BGP Speaker Out-of-sync")
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.cache.is_bgp_peer_added(bgp_speaker_id, bgp_peer_ip):
|
|
||||||
self.cache.remove_bgp_peer_by_ip(bgp_speaker_id, bgp_peer_ip)
|
|
||||||
|
|
||||||
bgp_speaker_as = self.cache.get_bgp_speaker_local_as(
|
|
||||||
bgp_speaker_id)
|
|
||||||
|
|
||||||
LOG.debug('Calling driver interface to remove BGP peer '
|
|
||||||
'%(peer_ip)s from BGP Speaker running for '
|
|
||||||
'local_as=%(local_as)d',
|
|
||||||
{'peer_ip': bgp_peer_ip, 'local_as': bgp_speaker_as})
|
|
||||||
try:
|
|
||||||
self.dr_driver_cls.delete_bgp_peer(bgp_speaker_as,
|
|
||||||
bgp_peer_ip)
|
|
||||||
except Exception as e:
|
|
||||||
self._handle_driver_failure(bgp_speaker_id,
|
|
||||||
'remove_bgp_peer', e)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Ideally, only the added peers can be removed by the neutron
|
|
||||||
# server. Looks like there might be some synchronization
|
|
||||||
# issue between the server and the agent. Let's initiate a re-sync
|
|
||||||
# to resolve the issue.
|
|
||||||
self.schedule_resync(speaker_id=bgp_speaker_id,
|
|
||||||
reason="BGP Peer Out-of-sync")
|
|
||||||
|
|
||||||
def advertise_routes_via_bgp_speaker(self, bgp_speaker):
|
|
||||||
for route in bgp_speaker['advertised_routes']:
|
|
||||||
self.advertise_route_via_bgp_speaker(bgp_speaker['id'],
|
|
||||||
bgp_speaker['local_as'],
|
|
||||||
route)
|
|
||||||
if self.is_resync_scheduled(bgp_speaker['id']):
|
|
||||||
break
|
|
||||||
|
|
||||||
def advertise_route_via_bgp_speaker(self, bgp_speaker_id,
|
|
||||||
bgp_speaker_as, route):
|
|
||||||
if self.cache.is_route_advertised(bgp_speaker_id, route):
|
|
||||||
# Requested route already advertised. Hence, Nothing to be done.
|
|
||||||
return
|
|
||||||
self.cache.put_adv_route(bgp_speaker_id, route)
|
|
||||||
|
|
||||||
LOG.debug('Calling driver for advertising prefix: %(cidr)s, '
|
|
||||||
'next_hop: %(nexthop)s',
|
|
||||||
{'cidr': route['destination'],
|
|
||||||
'nexthop': route['next_hop']})
|
|
||||||
try:
|
|
||||||
self.dr_driver_cls.advertise_route(bgp_speaker_as,
|
|
||||||
route['destination'],
|
|
||||||
route['next_hop'])
|
|
||||||
except Exception as e:
|
|
||||||
self._handle_driver_failure(bgp_speaker_id,
|
|
||||||
'advertise_route', e)
|
|
||||||
|
|
||||||
def withdraw_route_via_bgp_speaker(self, bgp_speaker_id,
|
|
||||||
bgp_speaker_as, route):
|
|
||||||
if self.cache.is_route_advertised(bgp_speaker_id, route):
|
|
||||||
self.cache.remove_adv_route(bgp_speaker_id, route)
|
|
||||||
LOG.debug('Calling driver for withdrawing prefix: %(cidr)s, '
|
|
||||||
'next_hop: %(nexthop)s',
|
|
||||||
{'cidr': route['destination'],
|
|
||||||
'nexthop': route['next_hop']})
|
|
||||||
try:
|
|
||||||
self.dr_driver_cls.withdraw_route(bgp_speaker_as,
|
|
||||||
route['destination'],
|
|
||||||
route['next_hop'])
|
|
||||||
except Exception as e:
|
|
||||||
self._handle_driver_failure(bgp_speaker_id,
|
|
||||||
'withdraw_route', e)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Ideally, only the advertised routes can be withdrawn by the
|
|
||||||
# neutron server. Looks like there might be some synchronization
|
|
||||||
# issue between the server and the agent. Let's initiate a re-sync
|
|
||||||
# to resolve the issue.
|
|
||||||
self.schedule_resync(speaker_id=bgp_speaker_id,
|
|
||||||
reason="Advertised routes Out-of-sync")
|
|
||||||
|
|
||||||
def schedule_full_resync(self, reason):
|
|
||||||
LOG.debug('Recording full resync request for all BGP Speakers '
|
|
||||||
'with reason=%s', reason)
|
|
||||||
self.needs_full_sync_reason = reason
|
|
||||||
|
|
||||||
def schedule_resync(self, reason, speaker_id):
|
|
||||||
"""Schedule a full resync for a given BGP Speaker.
|
|
||||||
If no BGP Speaker is specified, resync all BGP Speakers.
|
|
||||||
"""
|
|
||||||
LOG.debug('Recording resync request for BGP Speaker %s '
|
|
||||||
'with reason=%s', speaker_id, reason)
|
|
||||||
self.needs_resync_reasons[speaker_id].append(reason)
|
|
||||||
|
|
||||||
def is_resync_scheduled(self, bgp_speaker_id):
|
|
||||||
if bgp_speaker_id not in self.needs_resync_reasons:
|
|
||||||
return False
|
|
||||||
|
|
||||||
reason = self.needs_resync_reasons[bgp_speaker_id]
|
|
||||||
# Re-sync scheduled for the queried BGP speaker. No point
|
|
||||||
# continuing further. Let's stop processing and wait for
|
|
||||||
# re-sync to happen.
|
|
||||||
LOG.debug('Re-sync already scheduled for BGP Speaker %s '
|
|
||||||
'with reason=%s', bgp_speaker_id, reason)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
class BgpDrPluginApi(object):
|
|
||||||
"""Agent side of BgpDrAgent RPC API.
|
|
||||||
|
|
||||||
This class implements the client side of an rpc interface.
|
|
||||||
The server side of this interface can be found in
|
|
||||||
neutron.api.rpc.handlers.bgp_speaker_rpc.BgpSpeakerRpcCallback.
|
|
||||||
For more information about changing rpc interfaces, see
|
|
||||||
doc/source/devref/rpc_api.rst.
|
|
||||||
|
|
||||||
API version history:
|
|
||||||
1.0 - Initial version.
|
|
||||||
"""
|
|
||||||
def __init__(self, topic, context, host):
|
|
||||||
self.context = context
|
|
||||||
self.host = host
|
|
||||||
target = oslo_messaging.Target(topic=topic, version='1.0')
|
|
||||||
self.client = n_rpc.get_client(target)
|
|
||||||
|
|
||||||
def get_bgp_speakers(self, context):
|
|
||||||
"""Make a remote process call to retrieve all BGP speakers info."""
|
|
||||||
cctxt = self.client.prepare()
|
|
||||||
return cctxt.call(context, 'get_bgp_speakers', host=self.host)
|
|
||||||
|
|
||||||
def get_bgp_speaker_info(self, context, bgp_speaker_id):
|
|
||||||
"""Make a remote process call to retrieve a BGP speaker info."""
|
|
||||||
cctxt = self.client.prepare()
|
|
||||||
return cctxt.call(context, 'get_bgp_speaker_info',
|
|
||||||
bgp_speaker_id=bgp_speaker_id)
|
|
||||||
|
|
||||||
def get_bgp_peer_info(self, context, bgp_peer_id):
|
|
||||||
"""Make a remote process call to retrieve a BGP peer info."""
|
|
||||||
cctxt = self.client.prepare()
|
|
||||||
return cctxt.call(context, 'get_bgp_peer_info',
|
|
||||||
bgp_peer_id=bgp_peer_id)
|
|
||||||
|
|
||||||
|
|
||||||
class BgpSpeakerCache(object):
|
|
||||||
"""Agent cache of the current BGP speaker state.
|
|
||||||
|
|
||||||
This class is designed to support the advertisement for
|
|
||||||
multiple BGP speaker via a single driver interface.
|
|
||||||
|
|
||||||
Version history:
|
|
||||||
1.0 - Initial version for caching the state of BGP speaker.
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self.cache = {}
|
|
||||||
|
|
||||||
def get_bgp_speaker_ids(self):
|
|
||||||
return self.cache.keys()
|
|
||||||
|
|
||||||
def put_bgp_speaker(self, bgp_speaker):
|
|
||||||
if bgp_speaker['id'] in self.cache:
|
|
||||||
self.remove_bgp_speaker_by_id(self.cache[bgp_speaker['id']])
|
|
||||||
self.cache[bgp_speaker['id']] = {'bgp_speaker': bgp_speaker,
|
|
||||||
'peers': {},
|
|
||||||
'advertised_routes': []}
|
|
||||||
|
|
||||||
def get_bgp_speaker_by_id(self, bgp_speaker_id):
|
|
||||||
if bgp_speaker_id in self.cache:
|
|
||||||
return self.cache[bgp_speaker_id]['bgp_speaker']
|
|
||||||
|
|
||||||
def get_bgp_speaker_local_as(self, bgp_speaker_id):
|
|
||||||
bgp_speaker = self.get_bgp_speaker_by_id(bgp_speaker_id)
|
|
||||||
if bgp_speaker:
|
|
||||||
return bgp_speaker['local_as']
|
|
||||||
|
|
||||||
def is_bgp_speaker_added(self, bgp_speaker_id):
|
|
||||||
return self.get_bgp_speaker_by_id(bgp_speaker_id)
|
|
||||||
|
|
||||||
def remove_bgp_speaker_by_id(self, bgp_speaker_id):
|
|
||||||
if bgp_speaker_id in self.cache:
|
|
||||||
del self.cache[bgp_speaker_id]
|
|
||||||
|
|
||||||
def put_bgp_peer(self, bgp_speaker_id, bgp_peer):
|
|
||||||
if bgp_peer['peer_ip'] in self.get_bgp_peer_ips(bgp_speaker_id):
|
|
||||||
del self.cache[bgp_speaker_id]['peers'][bgp_peer['peer_ip']]
|
|
||||||
|
|
||||||
self.cache[bgp_speaker_id]['peers'][bgp_peer['peer_ip']] = bgp_peer
|
|
||||||
|
|
||||||
def is_bgp_peer_added(self, bgp_speaker_id, bgp_peer_ip):
|
|
||||||
return self.get_bgp_peer_by_ip(bgp_speaker_id, bgp_peer_ip)
|
|
||||||
|
|
||||||
def get_bgp_peer_ips(self, bgp_speaker_id):
|
|
||||||
bgp_speaker = self.get_bgp_speaker_by_id(bgp_speaker_id)
|
|
||||||
if bgp_speaker:
|
|
||||||
return self.cache[bgp_speaker_id]['peers'].keys()
|
|
||||||
|
|
||||||
def get_bgp_peer_by_ip(self, bgp_speaker_id, bgp_peer_ip):
|
|
||||||
bgp_speaker = self.get_bgp_speaker_by_id(bgp_speaker_id)
|
|
||||||
if bgp_speaker:
|
|
||||||
return self.cache[bgp_speaker_id]['peers'].get(bgp_peer_ip)
|
|
||||||
|
|
||||||
def remove_bgp_peer_by_ip(self, bgp_speaker_id, bgp_peer_ip):
|
|
||||||
if bgp_peer_ip in self.get_bgp_peer_ips(bgp_speaker_id):
|
|
||||||
del self.cache[bgp_speaker_id]['peers'][bgp_peer_ip]
|
|
||||||
|
|
||||||
def put_adv_route(self, bgp_speaker_id, route):
|
|
||||||
self.cache[bgp_speaker_id]['advertised_routes'].append(route)
|
|
||||||
|
|
||||||
def is_route_advertised(self, bgp_speaker_id, route):
|
|
||||||
routes = self.cache[bgp_speaker_id]['advertised_routes']
|
|
||||||
for r in routes:
|
|
||||||
if r['destination'] == route['destination'] and (
|
|
||||||
r['next_hop'] == route['next_hop']):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
def remove_adv_route(self, bgp_speaker_id, route):
|
|
||||||
routes = self.cache[bgp_speaker_id]['advertised_routes']
|
|
||||||
updated_routes = [r for r in routes if (
|
|
||||||
r['destination'] != route['destination'])]
|
|
||||||
self.cache[bgp_speaker_id]['advertised_routes'] = updated_routes
|
|
||||||
|
|
||||||
def get_adv_routes(self, bgp_speaker_id):
|
|
||||||
return self.cache[bgp_speaker_id]['advertised_routes']
|
|
||||||
|
|
||||||
def get_state(self):
|
|
||||||
bgp_speaker_ids = self.get_bgp_speaker_ids()
|
|
||||||
num_bgp_speakers = len(bgp_speaker_ids)
|
|
||||||
num_bgp_peers = 0
|
|
||||||
num_advertised_routes = 0
|
|
||||||
for bgp_speaker_id in bgp_speaker_ids:
|
|
||||||
bgp_speaker = self.get_bgp_speaker_by_id(bgp_speaker_id)
|
|
||||||
num_bgp_peers += len(bgp_speaker['peers'])
|
|
||||||
num_advertised_routes += len(bgp_speaker['advertised_routes'])
|
|
||||||
return {'bgp_speakers': num_bgp_speakers,
|
|
||||||
'bgp_peers': num_bgp_peers,
|
|
||||||
'advertise_routes': num_advertised_routes}
|
|
||||||
|
|
||||||
|
|
||||||
class BgpDrAgentWithStateReport(BgpDrAgent):
|
|
||||||
def __init__(self, host, conf=None):
|
|
||||||
super(BgpDrAgentWithStateReport,
|
|
||||||
self).__init__(host, conf)
|
|
||||||
self.state_rpc = agent_rpc.PluginReportStateAPI(topics.PLUGIN)
|
|
||||||
self.agent_state = {
|
|
||||||
'agent_type': bgp_consts.AGENT_TYPE_BGP_ROUTING,
|
|
||||||
'binary': 'neutron-bgp-dragent',
|
|
||||||
'configurations': {},
|
|
||||||
'host': host,
|
|
||||||
'topic': bgp_consts.BGP_DRAGENT,
|
|
||||||
'start_flag': True}
|
|
||||||
report_interval = cfg.CONF.AGENT.report_interval
|
|
||||||
if report_interval:
|
|
||||||
self.heartbeat = loopingcall.FixedIntervalLoopingCall(
|
|
||||||
self._report_state)
|
|
||||||
self.heartbeat.start(interval=report_interval)
|
|
||||||
|
|
||||||
def _report_state(self):
|
|
||||||
LOG.debug("Report state task started")
|
|
||||||
try:
|
|
||||||
self.agent_state.get('configurations').update(
|
|
||||||
self.cache.get_state())
|
|
||||||
ctx = context.get_admin_context_without_session()
|
|
||||||
agent_status = self.state_rpc.report_state(ctx, self.agent_state,
|
|
||||||
True)
|
|
||||||
if agent_status == constants.AGENT_REVIVED:
|
|
||||||
LOG.info(_LI("Agent has just been revived. "
|
|
||||||
"Scheduling full sync"))
|
|
||||||
self.schedule_full_resync(
|
|
||||||
reason=_("Agent has just been revived"))
|
|
||||||
except AttributeError:
|
|
||||||
# This means the server does not support report_state
|
|
||||||
LOG.warning(_LW("Neutron server does not support state report. "
|
|
||||||
"State report for this agent will be disabled."))
|
|
||||||
self.heartbeat.stop()
|
|
||||||
self.run()
|
|
||||||
return
|
|
||||||
except Exception:
|
|
||||||
LOG.exception(_LE("Failed reporting state!"))
|
|
||||||
return
|
|
||||||
if self.agent_state.pop('start_flag', None):
|
|
||||||
self.run()
|
|
||||||
|
|
||||||
def agent_updated(self, context, payload):
|
|
||||||
"""Handle the agent_updated notification event."""
|
|
||||||
self.schedule_full_resync(
|
|
||||||
reason=_("BgpDrAgent updated: %s") % payload)
|
|
||||||
LOG.info(_LI("agent_updated by server side %s!"), payload)
|
|
||||||
|
|
||||||
def after_start(self):
|
|
||||||
LOG.info(_LI("BGP dynamic routing agent started"))
|
|
@ -1,29 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
|
|
||||||
from neutron._i18n import _
|
|
||||||
|
|
||||||
BGP_DRIVER_OPTS = [
|
|
||||||
cfg.StrOpt('bgp_speaker_driver',
|
|
||||||
help=_("BGP speaker driver class to be instantiated."))
|
|
||||||
]
|
|
||||||
|
|
||||||
BGP_PROTO_CONFIG_OPTS = [
|
|
||||||
cfg.StrOpt('bgp_router_id',
|
|
||||||
help=_("32-bit BGP identifier, typically an IPv4 address "
|
|
||||||
"owned by the system running the BGP DrAgent."))
|
|
||||||
]
|
|
@ -1,47 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_service import service
|
|
||||||
|
|
||||||
from neutron.agent.common import config
|
|
||||||
from neutron.agent.linux import external_process
|
|
||||||
from neutron.common import config as common_config
|
|
||||||
from neutron import service as neutron_service
|
|
||||||
from neutron.services.bgp.agent import config as bgp_dragent_config
|
|
||||||
from neutron.services.bgp.common import constants as bgp_consts
|
|
||||||
|
|
||||||
|
|
||||||
def register_options():
|
|
||||||
config.register_agent_state_opts_helper(cfg.CONF)
|
|
||||||
config.register_root_helper(cfg.CONF)
|
|
||||||
cfg.CONF.register_opts(bgp_dragent_config.BGP_DRIVER_OPTS, 'BGP')
|
|
||||||
cfg.CONF.register_opts(bgp_dragent_config.BGP_PROTO_CONFIG_OPTS, 'BGP')
|
|
||||||
cfg.CONF.register_opts(external_process.OPTS)
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
register_options()
|
|
||||||
common_config.init(sys.argv[1:])
|
|
||||||
config.setup_logging()
|
|
||||||
server = neutron_service.Service.create(
|
|
||||||
binary='neutron-bgp-dragent',
|
|
||||||
topic=bgp_consts.BGP_DRAGENT,
|
|
||||||
report_interval=cfg.CONF.AGENT.report_interval,
|
|
||||||
manager='neutron.services.bgp.agent.bgp_dragent.'
|
|
||||||
'BgpDrAgentWithStateReport')
|
|
||||||
service.launch(cfg.CONF, server).wait()
|
|
@ -1,289 +0,0 @@
|
|||||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
|
||||||
#
|
|
||||||
# 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 netaddr import IPAddress
|
|
||||||
from neutron_lib import constants as n_const
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from oslo_utils import importutils
|
|
||||||
|
|
||||||
from neutron.api.rpc.agentnotifiers import bgp_dr_rpc_agent_api
|
|
||||||
from neutron.api.rpc.handlers import bgp_speaker_rpc as bs_rpc
|
|
||||||
from neutron.callbacks import events
|
|
||||||
from neutron.callbacks import registry
|
|
||||||
from neutron.callbacks import resources
|
|
||||||
from neutron.common import rpc as n_rpc
|
|
||||||
from neutron import context
|
|
||||||
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.bgp.common import constants as bgp_consts
|
|
||||||
from neutron.services import service_base
|
|
||||||
|
|
||||||
PLUGIN_NAME = bgp_ext.BGP_EXT_ALIAS + '_svc_plugin'
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class BgpPlugin(service_base.ServicePluginBase,
|
|
||||||
bgp_db.BgpDbMixin,
|
|
||||||
bgp_dragentscheduler_db.BgpDrAgentSchedulerDbMixin):
|
|
||||||
|
|
||||||
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)
|
|
||||||
self._setup_rpc()
|
|
||||||
self._register_callbacks()
|
|
||||||
|
|
||||||
def get_plugin_name(self):
|
|
||||||
return PLUGIN_NAME
|
|
||||||
|
|
||||||
def get_plugin_type(self):
|
|
||||||
return bgp_ext.BGP_EXT_ALIAS
|
|
||||||
|
|
||||||
def get_plugin_description(self):
|
|
||||||
"""returns string description of the plugin."""
|
|
||||||
return ("BGP dynamic routing service for announcement of next-hops "
|
|
||||||
"for tenant networks, floating IP's, and DVR host routes.")
|
|
||||||
|
|
||||||
def _setup_rpc(self):
|
|
||||||
self.topic = bgp_consts.BGP_PLUGIN
|
|
||||||
self.conn = n_rpc.create_connection()
|
|
||||||
self.agent_notifiers[bgp_consts.AGENT_TYPE_BGP_ROUTING] = (
|
|
||||||
bgp_dr_rpc_agent_api.BgpDrAgentNotifyApi()
|
|
||||||
)
|
|
||||||
self._bgp_rpc = self.agent_notifiers[bgp_consts.AGENT_TYPE_BGP_ROUTING]
|
|
||||||
self.endpoints = [bs_rpc.BgpSpeakerRpcCallback()]
|
|
||||||
self.conn.create_consumer(self.topic, self.endpoints,
|
|
||||||
fanout=False)
|
|
||||||
self.conn.consume_in_threads()
|
|
||||||
|
|
||||||
def _register_callbacks(self):
|
|
||||||
registry.subscribe(self.floatingip_update_callback,
|
|
||||||
resources.FLOATING_IP,
|
|
||||||
events.AFTER_UPDATE)
|
|
||||||
registry.subscribe(self.router_interface_callback,
|
|
||||||
resources.ROUTER_INTERFACE,
|
|
||||||
events.AFTER_CREATE)
|
|
||||||
registry.subscribe(self.router_interface_callback,
|
|
||||||
resources.ROUTER_INTERFACE,
|
|
||||||
events.BEFORE_CREATE)
|
|
||||||
registry.subscribe(self.router_interface_callback,
|
|
||||||
resources.ROUTER_INTERFACE,
|
|
||||||
events.AFTER_DELETE)
|
|
||||||
registry.subscribe(self.router_gateway_callback,
|
|
||||||
resources.ROUTER_GATEWAY,
|
|
||||||
events.AFTER_CREATE)
|
|
||||||
registry.subscribe(self.router_gateway_callback,
|
|
||||||
resources.ROUTER_GATEWAY,
|
|
||||||
events.AFTER_DELETE)
|
|
||||||
|
|
||||||
def create_bgp_speaker(self, context, bgp_speaker):
|
|
||||||
bgp_speaker = super(BgpPlugin, self).create_bgp_speaker(context,
|
|
||||||
bgp_speaker)
|
|
||||||
return bgp_speaker
|
|
||||||
|
|
||||||
def delete_bgp_speaker(self, context, bgp_speaker_id):
|
|
||||||
hosted_bgp_dragents = self.get_dragents_hosting_bgp_speakers(
|
|
||||||
context,
|
|
||||||
[bgp_speaker_id])
|
|
||||||
super(BgpPlugin, self).delete_bgp_speaker(context, bgp_speaker_id)
|
|
||||||
for agent in hosted_bgp_dragents:
|
|
||||||
self._bgp_rpc.bgp_speaker_removed(context,
|
|
||||||
bgp_speaker_id,
|
|
||||||
agent.host)
|
|
||||||
|
|
||||||
def add_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info):
|
|
||||||
ret_value = super(BgpPlugin, self).add_bgp_peer(context,
|
|
||||||
bgp_speaker_id,
|
|
||||||
bgp_peer_info)
|
|
||||||
hosted_bgp_dragents = self.get_dragents_hosting_bgp_speakers(
|
|
||||||
context,
|
|
||||||
[bgp_speaker_id])
|
|
||||||
for agent in hosted_bgp_dragents:
|
|
||||||
self._bgp_rpc.bgp_peer_associated(context, bgp_speaker_id,
|
|
||||||
ret_value['bgp_peer_id'],
|
|
||||||
agent.host)
|
|
||||||
return ret_value
|
|
||||||
|
|
||||||
def remove_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info):
|
|
||||||
hosted_bgp_dragents = self.get_dragents_hosting_bgp_speakers(
|
|
||||||
context, [bgp_speaker_id])
|
|
||||||
|
|
||||||
ret_value = super(BgpPlugin, self).remove_bgp_peer(context,
|
|
||||||
bgp_speaker_id,
|
|
||||||
bgp_peer_info)
|
|
||||||
|
|
||||||
for agent in hosted_bgp_dragents:
|
|
||||||
self._bgp_rpc.bgp_peer_disassociated(context,
|
|
||||||
bgp_speaker_id,
|
|
||||||
ret_value['bgp_peer_id'],
|
|
||||||
agent.host)
|
|
||||||
|
|
||||||
def floatingip_update_callback(self, resource, event, trigger, **kwargs):
|
|
||||||
if event != events.AFTER_UPDATE:
|
|
||||||
return
|
|
||||||
|
|
||||||
ctx = context.get_admin_context()
|
|
||||||
new_router_id = kwargs['router_id']
|
|
||||||
last_router_id = kwargs['last_known_router_id']
|
|
||||||
next_hop = kwargs['next_hop']
|
|
||||||
dest = kwargs['floating_ip_address'] + '/32'
|
|
||||||
bgp_speakers = self._bgp_speakers_for_gw_network_by_family(
|
|
||||||
ctx,
|
|
||||||
kwargs['floating_network_id'],
|
|
||||||
n_const.IP_VERSION_4)
|
|
||||||
|
|
||||||
if last_router_id and new_router_id != last_router_id:
|
|
||||||
for bgp_speaker in bgp_speakers:
|
|
||||||
self.stop_route_advertisements(ctx, self._bgp_rpc,
|
|
||||||
bgp_speaker.id, [dest])
|
|
||||||
|
|
||||||
if next_hop and new_router_id != last_router_id:
|
|
||||||
new_host_route = {'destination': dest, 'next_hop': next_hop}
|
|
||||||
for bgp_speaker in bgp_speakers:
|
|
||||||
self.start_route_advertisements(ctx, self._bgp_rpc,
|
|
||||||
bgp_speaker.id,
|
|
||||||
[new_host_route])
|
|
||||||
|
|
||||||
def router_interface_callback(self, resource, event, trigger, **kwargs):
|
|
||||||
if event == events.AFTER_CREATE:
|
|
||||||
self._handle_router_interface_after_create(**kwargs)
|
|
||||||
if event == events.AFTER_DELETE:
|
|
||||||
gw_network = kwargs['network_id']
|
|
||||||
next_hops = self._next_hops_from_gateway_ips(
|
|
||||||
kwargs['gateway_ips'])
|
|
||||||
ctx = context.get_admin_context()
|
|
||||||
speakers = self._bgp_speakers_for_gateway_network(ctx, gw_network)
|
|
||||||
for speaker in speakers:
|
|
||||||
routes = self._route_list_from_prefixes_and_next_hop(
|
|
||||||
kwargs['cidrs'],
|
|
||||||
next_hops[speaker.ip_version])
|
|
||||||
self._handle_router_interface_after_delete(gw_network, routes)
|
|
||||||
|
|
||||||
def _handle_router_interface_after_create(self, **kwargs):
|
|
||||||
gw_network = kwargs['network_id']
|
|
||||||
if not gw_network:
|
|
||||||
return
|
|
||||||
|
|
||||||
ctx = context.get_admin_context()
|
|
||||||
with ctx.session.begin(subtransactions=True):
|
|
||||||
speakers = self._bgp_speakers_for_gateway_network(ctx,
|
|
||||||
gw_network)
|
|
||||||
next_hops = self._next_hops_from_gateway_ips(
|
|
||||||
kwargs['gateway_ips'])
|
|
||||||
|
|
||||||
for speaker in speakers:
|
|
||||||
prefixes = self._tenant_prefixes_by_router(
|
|
||||||
ctx,
|
|
||||||
kwargs['router_id'],
|
|
||||||
speaker.id)
|
|
||||||
next_hop = next_hops.get(speaker.ip_version)
|
|
||||||
if next_hop:
|
|
||||||
rl = self._route_list_from_prefixes_and_next_hop(prefixes,
|
|
||||||
next_hop)
|
|
||||||
self.start_route_advertisements(ctx,
|
|
||||||
self._bgp_rpc,
|
|
||||||
speaker.id,
|
|
||||||
rl)
|
|
||||||
|
|
||||||
def router_gateway_callback(self, resource, event, trigger, **kwargs):
|
|
||||||
if event == events.AFTER_CREATE:
|
|
||||||
self._handle_router_gateway_after_create(**kwargs)
|
|
||||||
if event == events.AFTER_DELETE:
|
|
||||||
gw_network = kwargs['network_id']
|
|
||||||
router_id = kwargs['router_id']
|
|
||||||
next_hops = self._next_hops_from_gateway_ips(
|
|
||||||
kwargs['gateway_ips'])
|
|
||||||
ctx = context.get_admin_context()
|
|
||||||
speakers = self._bgp_speakers_for_gateway_network(ctx, gw_network)
|
|
||||||
for speaker in speakers:
|
|
||||||
if speaker.ip_version in next_hops:
|
|
||||||
next_hop = next_hops[speaker.ip_version]
|
|
||||||
prefixes = self._tenant_prefixes_by_router(ctx,
|
|
||||||
router_id,
|
|
||||||
speaker.id)
|
|
||||||
routes = self._route_list_from_prefixes_and_next_hop(
|
|
||||||
prefixes,
|
|
||||||
next_hop)
|
|
||||||
self._handle_router_interface_after_delete(gw_network, routes)
|
|
||||||
|
|
||||||
def _handle_router_gateway_after_create(self, **kwargs):
|
|
||||||
ctx = context.get_admin_context()
|
|
||||||
gw_network = kwargs['network_id']
|
|
||||||
router_id = kwargs['router_id']
|
|
||||||
with ctx.session.begin(subtransactions=True):
|
|
||||||
speakers = self._bgp_speakers_for_gateway_network(ctx,
|
|
||||||
gw_network)
|
|
||||||
next_hops = self._next_hops_from_gateway_ips(kwargs['gw_ips'])
|
|
||||||
|
|
||||||
for speaker in speakers:
|
|
||||||
if speaker.ip_version in next_hops:
|
|
||||||
next_hop = next_hops[speaker.ip_version]
|
|
||||||
prefixes = self._tenant_prefixes_by_router(ctx,
|
|
||||||
router_id,
|
|
||||||
speaker.id)
|
|
||||||
routes = self._route_list_from_prefixes_and_next_hop(
|
|
||||||
prefixes,
|
|
||||||
next_hop)
|
|
||||||
self.start_route_advertisements(ctx, self._bgp_rpc,
|
|
||||||
speaker.id, routes)
|
|
||||||
|
|
||||||
def _handle_router_interface_after_delete(self, gw_network, routes):
|
|
||||||
if gw_network and routes:
|
|
||||||
ctx = context.get_admin_context()
|
|
||||||
speakers = self._bgp_speakers_for_gateway_network(ctx, gw_network)
|
|
||||||
for speaker in speakers:
|
|
||||||
self.stop_route_advertisements(ctx, self._bgp_rpc,
|
|
||||||
speaker.id, routes)
|
|
||||||
|
|
||||||
def _next_hops_from_gateway_ips(self, gw_ips):
|
|
||||||
if gw_ips:
|
|
||||||
return {IPAddress(ip).version: ip for ip in gw_ips}
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def start_route_advertisements(self, ctx, bgp_rpc,
|
|
||||||
bgp_speaker_id, routes):
|
|
||||||
agents = self.list_dragent_hosting_bgp_speaker(ctx, bgp_speaker_id)
|
|
||||||
for agent in agents['agents']:
|
|
||||||
bgp_rpc.bgp_routes_advertisement(ctx,
|
|
||||||
bgp_speaker_id,
|
|
||||||
routes,
|
|
||||||
agent['host'])
|
|
||||||
|
|
||||||
msg = "Starting route advertisements for %s on BgpSpeaker %s"
|
|
||||||
self._debug_log_for_routes(msg, routes, bgp_speaker_id)
|
|
||||||
|
|
||||||
def stop_route_advertisements(self, ctx, bgp_rpc,
|
|
||||||
bgp_speaker_id, routes):
|
|
||||||
agents = self.list_dragent_hosting_bgp_speaker(ctx, bgp_speaker_id)
|
|
||||||
for agent in agents['agents']:
|
|
||||||
bgp_rpc.bgp_routes_withdrawal(ctx,
|
|
||||||
bgp_speaker_id,
|
|
||||||
routes,
|
|
||||||
agent['host'])
|
|
||||||
|
|
||||||
msg = "Stopping route advertisements for %s on BgpSpeaker %s"
|
|
||||||
self._debug_log_for_routes(msg, routes, bgp_speaker_id)
|
|
||||||
|
|
||||||
def _debug_log_for_routes(self, msg, routes, bgp_speaker_id):
|
|
||||||
|
|
||||||
# Could have a large number of routes passed, check log level first
|
|
||||||
if LOG.isEnabledFor(logging.DEBUG):
|
|
||||||
for route in routes:
|
|
||||||
LOG.debug(msg, route, bgp_speaker_id)
|
|
@ -1,27 +0,0 @@
|
|||||||
# 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'
|
|
||||||
|
|
||||||
BGP_DRAGENT = 'bgp_dragent'
|
|
||||||
|
|
||||||
BGP_PLUGIN = 'q-bgp-plugin'
|
|
||||||
|
|
||||||
# List of supported authentication types.
|
|
||||||
SUPPORTED_AUTH_TYPES = ['none', 'md5']
|
|
||||||
|
|
||||||
# Supported AS number range
|
|
||||||
MIN_ASNUM = 1
|
|
||||||
MAX_ASNUM = 65535
|
|
@ -1,28 +0,0 @@
|
|||||||
# 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 itertools
|
|
||||||
|
|
||||||
import neutron.services.bgp.agent.config
|
|
||||||
|
|
||||||
|
|
||||||
def list_bgp_agent_opts():
|
|
||||||
return [
|
|
||||||
('BGP',
|
|
||||||
itertools.chain(
|
|
||||||
neutron.services.bgp.agent.config.BGP_DRIVER_OPTS,
|
|
||||||
neutron.services.bgp.agent.config.BGP_PROTO_CONFIG_OPTS)
|
|
||||||
)
|
|
||||||
]
|
|
@ -1,142 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
import abc
|
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
|
||||||
class BgpDriverBase(object):
|
|
||||||
"""Base class for BGP Speaking drivers.
|
|
||||||
|
|
||||||
Any class which provides BGP functionality should extend this
|
|
||||||
defined base class.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def add_bgp_speaker(self, speaker_as):
|
|
||||||
"""Add a BGP speaker.
|
|
||||||
|
|
||||||
:param speaker_as: Specifies BGP Speaker autonomous system number.
|
|
||||||
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
|
||||||
:type speaker_as: integer
|
|
||||||
:raises: BgpSpeakerAlreadyScheduled, BgpSpeakerMaxScheduled,
|
|
||||||
InvalidParamType, InvalidParamRange
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_bgp_speaker(self, speaker_as):
|
|
||||||
"""Deletes BGP speaker.
|
|
||||||
|
|
||||||
:param speaker_as: Specifies BGP Speaker autonomous system number.
|
|
||||||
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
|
||||||
:type speaker_as: integer
|
|
||||||
:raises: BgpSpeakerNotAdded
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def add_bgp_peer(self, speaker_as, peer_ip, peer_as,
|
|
||||||
auth_type='none', password=None):
|
|
||||||
"""Add a new BGP peer.
|
|
||||||
|
|
||||||
:param speaker_as: Specifies BGP Speaker autonomous system number.
|
|
||||||
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
|
||||||
:type speaker_as: integer
|
|
||||||
:param peer_ip: Specifies the IP address of the peer.
|
|
||||||
:type peer_ip: string
|
|
||||||
:param peer_as: Specifies Autonomous Number of the peer.
|
|
||||||
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
|
||||||
:type peer_as: integer
|
|
||||||
:param auth_type: Specifies authentication type.
|
|
||||||
By default, authentication will be disabled.
|
|
||||||
:type auth_type: value in SUPPORTED_AUTH_TYPES
|
|
||||||
:param password: Authentication password.By default, authentication
|
|
||||||
will be disabled.
|
|
||||||
:type password: string
|
|
||||||
:raises: BgpSpeakerNotAdded, InvalidParamType, InvalidParamRange,
|
|
||||||
InvaildAuthType, PasswordNotSpecified
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_bgp_peer(self, speaker_as, peer_ip):
|
|
||||||
"""Delete a BGP peer associated with the given peer IP
|
|
||||||
|
|
||||||
:param speaker_as: Specifies BGP Speaker autonomous system number.
|
|
||||||
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
|
||||||
:type speaker_as: integer
|
|
||||||
:param peer_ip: Specifies the IP address of the peer. Must be the
|
|
||||||
string representation of an IP address.
|
|
||||||
:type peer_ip: string
|
|
||||||
:raises: BgpSpeakerNotAdded, BgpPeerNotAdded
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def advertise_route(self, speaker_as, cidr, nexthop):
|
|
||||||
"""Add a new prefix to advertise.
|
|
||||||
|
|
||||||
:param speaker_as: Specifies BGP Speaker autonomous system number.
|
|
||||||
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
|
||||||
:type speaker_as: integer
|
|
||||||
:param cidr: CIDR of the network to advertise. Must be the string
|
|
||||||
representation of an IP network (e.g., 10.1.1.0/24)
|
|
||||||
:type cidr: string
|
|
||||||
:param nexthop: Specifies the next hop address for the above
|
|
||||||
prefix.
|
|
||||||
:type nexthop: string
|
|
||||||
:raises: BgpSpeakerNotAdded, InvalidParamType
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def withdraw_route(self, speaker_as, cidr, nexthop=None):
|
|
||||||
"""Withdraw an advertised prefix.
|
|
||||||
|
|
||||||
:param speaker_as: Specifies BGP Speaker autonomous system number.
|
|
||||||
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
|
||||||
:type speaker_as: integer
|
|
||||||
:param cidr: CIDR of the network to withdraw. Must be the string
|
|
||||||
representation of an IP network (e.g., 10.1.1.0/24)
|
|
||||||
:type cidr: string
|
|
||||||
:param nexthop: Specifies the next hop address for the above
|
|
||||||
prefix.
|
|
||||||
:type nexthop: string
|
|
||||||
:raises: BgpSpeakerNotAdded, RouteNotAdvertised, InvalidParamType
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_bgp_speaker_statistics(self, speaker_as):
|
|
||||||
"""Collect BGP Speaker statistics.
|
|
||||||
|
|
||||||
:param speaker_as: Specifies BGP Speaker autonomous system number.
|
|
||||||
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
|
||||||
:type speaker_as: integer
|
|
||||||
:raises: BgpSpeakerNotAdded
|
|
||||||
:returns: bgp_speaker_stats: string
|
|
||||||
"""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_bgp_peer_statistics(self, speaker_as, peer_ip, peer_as):
|
|
||||||
"""Collect BGP Peer statistics.
|
|
||||||
|
|
||||||
:param speaker_as: Specifies BGP Speaker autonomous system number.
|
|
||||||
Must be an integer between MIN_ASNUM and MAX_ASNUM.
|
|
||||||
:type speaker_as: integer
|
|
||||||
:param peer_ip: Specifies the IP address of the peer.
|
|
||||||
:type peer_ip: string
|
|
||||||
:param peer_as: Specifies the AS number of the peer. Must be an
|
|
||||||
integer between MIN_ASNUM and MAX_ASNUM.
|
|
||||||
:type peer_as: integer .
|
|
||||||
:raises: BgpSpeakerNotAdded, BgpPeerNotAdded
|
|
||||||
:returns: bgp_peer_stats: string
|
|
||||||
"""
|
|
@ -1,62 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
from neutron_lib import exceptions as n_exc
|
|
||||||
|
|
||||||
from neutron._i18n import _
|
|
||||||
|
|
||||||
|
|
||||||
# BGP Driver Exceptions
|
|
||||||
class BgpSpeakerNotAdded(n_exc.BadRequest):
|
|
||||||
message = _("BGP Speaker for local_as=%(local_as)s with "
|
|
||||||
"router_id=%(rtid)s not added yet.")
|
|
||||||
|
|
||||||
|
|
||||||
class BgpSpeakerMaxScheduled(n_exc.BadRequest):
|
|
||||||
message = _("Already hosting maximum number of BGP Speakers. "
|
|
||||||
"Allowed scheduled count=%(count)d")
|
|
||||||
|
|
||||||
|
|
||||||
class BgpSpeakerAlreadyScheduled(n_exc.Conflict):
|
|
||||||
message = _("Already hosting BGP Speaker for local_as=%(current_as)d with "
|
|
||||||
"router_id=%(rtid)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class BgpPeerNotAdded(n_exc.BadRequest):
|
|
||||||
message = _("BGP Peer %(peer_ip)s for remote_as=%(remote_as)s, running "
|
|
||||||
"for BGP Speaker %(speaker_as)d not added yet.")
|
|
||||||
|
|
||||||
|
|
||||||
class RouteNotAdvertised(n_exc.BadRequest):
|
|
||||||
message = _("Route %(cidr)s not advertised for BGP Speaker "
|
|
||||||
"%(speaker_as)d.")
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidParamType(n_exc.NeutronException):
|
|
||||||
message = _("Parameter %(param)s must be of %(param_type)s type.")
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidParamRange(n_exc.NeutronException):
|
|
||||||
message = _("%(param)s must be in %(range)s range.")
|
|
||||||
|
|
||||||
|
|
||||||
class InvaildAuthType(n_exc.BadRequest):
|
|
||||||
message = _("Authentication type not supported. Requested "
|
|
||||||
"type=%(auth_type)s.")
|
|
||||||
|
|
||||||
|
|
||||||
class PasswordNotSpecified(n_exc.BadRequest):
|
|
||||||
message = _("Password not specified for authentication "
|
|
||||||
"type=%(auth_type)s.")
|
|
@ -1,202 +0,0 @@
|
|||||||
# 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_log import log as logging
|
|
||||||
from ryu.services.protocols.bgp import bgpspeaker
|
|
||||||
from ryu.services.protocols.bgp.rtconf.neighbors import CONNECT_MODE_ACTIVE
|
|
||||||
|
|
||||||
from neutron.services.bgp.driver import base
|
|
||||||
from neutron.services.bgp.driver import exceptions as bgp_driver_exc
|
|
||||||
from neutron.services.bgp.driver import utils
|
|
||||||
from neutron._i18n import _LE, _LI
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# Function for logging BGP peer and path changes.
|
|
||||||
def bgp_peer_down_cb(remote_ip, remote_as):
|
|
||||||
LOG.info(_LI('BGP Peer %(peer_ip)s for remote_as=%(peer_as)d went DOWN.'),
|
|
||||||
{'peer_ip': remote_ip, 'peer_as': remote_as})
|
|
||||||
|
|
||||||
|
|
||||||
def bgp_peer_up_cb(remote_ip, remote_as):
|
|
||||||
LOG.info(_LI('BGP Peer %(peer_ip)s for remote_as=%(peer_as)d is UP.'),
|
|
||||||
{'peer_ip': remote_ip, 'peer_as': remote_as})
|
|
||||||
|
|
||||||
|
|
||||||
def best_path_change_cb(event):
|
|
||||||
LOG.info(_LI("Best path change observed. cidr=%(prefix)s, "
|
|
||||||
"nexthop=%(nexthop)s, remote_as=%(remote_as)d, "
|
|
||||||
"is_withdraw=%(is_withdraw)s"),
|
|
||||||
{'prefix': event.prefix, 'nexthop': event.nexthop,
|
|
||||||
'remote_as': event.remote_as,
|
|
||||||
'is_withdraw': event.is_withdraw})
|
|
||||||
|
|
||||||
|
|
||||||
class RyuBgpDriver(base.BgpDriverBase):
|
|
||||||
"""BGP speaker implementation via Ryu."""
|
|
||||||
|
|
||||||
def __init__(self, cfg):
|
|
||||||
LOG.info(_LI('Initializing Ryu driver for BGP Speaker functionality.'))
|
|
||||||
self._read_config(cfg)
|
|
||||||
|
|
||||||
# Note: Even though Ryu can only support one BGP speaker as of now,
|
|
||||||
# we have tried making the framework generic for the future purposes.
|
|
||||||
self.cache = utils.BgpMultiSpeakerCache()
|
|
||||||
|
|
||||||
def _read_config(self, cfg):
|
|
||||||
if cfg is None or cfg.bgp_router_id is None:
|
|
||||||
# If either cfg or router_id is not specified, raise voice
|
|
||||||
LOG.error(_LE('BGP router-id MUST be specified for the correct '
|
|
||||||
'functional working.'))
|
|
||||||
else:
|
|
||||||
self.routerid = cfg.bgp_router_id
|
|
||||||
LOG.info(_LI('Initialized Ryu BGP Speaker driver interface with '
|
|
||||||
'bgp_router_id=%s'), self.routerid)
|
|
||||||
|
|
||||||
def add_bgp_speaker(self, speaker_as):
|
|
||||||
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
|
|
||||||
if curr_speaker is not None:
|
|
||||||
raise bgp_driver_exc.BgpSpeakerAlreadyScheduled(
|
|
||||||
current_as=speaker_as,
|
|
||||||
rtid=self.routerid)
|
|
||||||
|
|
||||||
# Ryu can only support One speaker
|
|
||||||
if self.cache.get_hosted_bgp_speakers_count() == 1:
|
|
||||||
raise bgp_driver_exc.BgpSpeakerMaxScheduled(count=1)
|
|
||||||
|
|
||||||
# Validate input parameters.
|
|
||||||
# speaker_as must be an integer in the allowed range.
|
|
||||||
utils.validate_as_num('local_as', speaker_as)
|
|
||||||
|
|
||||||
# Notify Ryu about BGP Speaker addition.
|
|
||||||
# Please note: Since, only the route-advertisement support is
|
|
||||||
# implemented we are explicitly setting the bgp_server_port
|
|
||||||
# attribute to 0 which disables listening on port 179.
|
|
||||||
curr_speaker = bgpspeaker.BGPSpeaker(as_number=speaker_as,
|
|
||||||
router_id=self.routerid, bgp_server_port=0,
|
|
||||||
best_path_change_handler=best_path_change_cb,
|
|
||||||
peer_down_handler=bgp_peer_down_cb,
|
|
||||||
peer_up_handler=bgp_peer_up_cb)
|
|
||||||
LOG.info(_LI('Added BGP Speaker for local_as=%(as)d with '
|
|
||||||
'router_id= %(rtid)s.'),
|
|
||||||
{'as': speaker_as, 'rtid': self.routerid})
|
|
||||||
|
|
||||||
self.cache.put_bgp_speaker(speaker_as, curr_speaker)
|
|
||||||
|
|
||||||
def delete_bgp_speaker(self, speaker_as):
|
|
||||||
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
|
|
||||||
if not curr_speaker:
|
|
||||||
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
|
|
||||||
rtid=self.routerid)
|
|
||||||
# Notify Ryu about BGP Speaker deletion
|
|
||||||
curr_speaker.shutdown()
|
|
||||||
LOG.info(_LI('Removed BGP Speaker for local_as=%(as)d with '
|
|
||||||
'router_id=%(rtid)s.'),
|
|
||||||
{'as': speaker_as, 'rtid': self.routerid})
|
|
||||||
self.cache.remove_bgp_speaker(speaker_as)
|
|
||||||
|
|
||||||
def add_bgp_peer(self, speaker_as, peer_ip, peer_as,
|
|
||||||
auth_type='none', password=None):
|
|
||||||
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
|
|
||||||
if not curr_speaker:
|
|
||||||
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
|
|
||||||
rtid=self.routerid)
|
|
||||||
|
|
||||||
# Validate peer_ip and peer_as.
|
|
||||||
utils.validate_as_num('remote_as', peer_as)
|
|
||||||
utils.validate_string(peer_ip)
|
|
||||||
utils.validate_auth(auth_type, password)
|
|
||||||
|
|
||||||
# Notify Ryu about BGP Peer addition
|
|
||||||
curr_speaker.neighbor_add(address=peer_ip,
|
|
||||||
remote_as=peer_as,
|
|
||||||
password=password,
|
|
||||||
connect_mode=CONNECT_MODE_ACTIVE)
|
|
||||||
LOG.info(_LI('Added BGP Peer %(peer)s for remote_as=%(as)d to '
|
|
||||||
'BGP Speaker running for local_as=%(local_as)d.'),
|
|
||||||
{'peer': peer_ip, 'as': peer_as, 'local_as': speaker_as})
|
|
||||||
|
|
||||||
def delete_bgp_peer(self, speaker_as, peer_ip):
|
|
||||||
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
|
|
||||||
if not curr_speaker:
|
|
||||||
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
|
|
||||||
rtid=self.routerid)
|
|
||||||
# Validate peer_ip. It must be a string.
|
|
||||||
utils.validate_string(peer_ip)
|
|
||||||
|
|
||||||
# Notify Ryu about BGP Peer removal
|
|
||||||
curr_speaker.neighbor_del(address=peer_ip)
|
|
||||||
LOG.info(_LI('Removed BGP Peer %(peer)s from BGP Speaker '
|
|
||||||
'running for local_as=%(local_as)d.'),
|
|
||||||
{'peer': peer_ip, 'local_as': speaker_as})
|
|
||||||
|
|
||||||
def advertise_route(self, speaker_as, cidr, nexthop):
|
|
||||||
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
|
|
||||||
if not curr_speaker:
|
|
||||||
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
|
|
||||||
rtid=self.routerid)
|
|
||||||
|
|
||||||
# Validate cidr and nexthop. Both must be strings.
|
|
||||||
utils.validate_string(cidr)
|
|
||||||
utils.validate_string(nexthop)
|
|
||||||
|
|
||||||
# Notify Ryu about route advertisement
|
|
||||||
curr_speaker.prefix_add(prefix=cidr, next_hop=nexthop)
|
|
||||||
LOG.info(_LI('Route cidr=%(prefix)s, nexthop=%(nexthop)s is '
|
|
||||||
'advertised for BGP Speaker running for '
|
|
||||||
'local_as=%(local_as)d.'),
|
|
||||||
{'prefix': cidr, 'nexthop': nexthop, 'local_as': speaker_as})
|
|
||||||
|
|
||||||
def withdraw_route(self, speaker_as, cidr, nexthop=None):
|
|
||||||
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
|
|
||||||
if not curr_speaker:
|
|
||||||
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
|
|
||||||
rtid=self.routerid)
|
|
||||||
# Validate cidr. It must be a string.
|
|
||||||
utils.validate_string(cidr)
|
|
||||||
|
|
||||||
# Notify Ryu about route withdrawal
|
|
||||||
curr_speaker.prefix_del(prefix=cidr)
|
|
||||||
LOG.info(_LI('Route cidr=%(prefix)s is withdrawn from BGP Speaker '
|
|
||||||
'running for local_as=%(local_as)d.'),
|
|
||||||
{'prefix': cidr, 'local_as': speaker_as})
|
|
||||||
|
|
||||||
def get_bgp_speaker_statistics(self, speaker_as):
|
|
||||||
LOG.info(_LI('Collecting BGP Speaker statistics for local_as=%d.'),
|
|
||||||
speaker_as)
|
|
||||||
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
|
|
||||||
if not curr_speaker:
|
|
||||||
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
|
|
||||||
rtid=self.routerid)
|
|
||||||
|
|
||||||
# TODO(vikram): Filter and return the necessary information.
|
|
||||||
# Will be done as part of new RFE requirement
|
|
||||||
# https://bugs.launchpad.net/neutron/+bug/1527993
|
|
||||||
return curr_speaker.neighbor_state_get()
|
|
||||||
|
|
||||||
def get_bgp_peer_statistics(self, speaker_as, peer_ip):
|
|
||||||
LOG.info(_LI('Collecting BGP Peer statistics for peer_ip=%(peer)s, '
|
|
||||||
'running in speaker_as=%(speaker_as)d '),
|
|
||||||
{'peer': peer_ip, 'speaker_as': speaker_as})
|
|
||||||
curr_speaker = self.cache.get_bgp_speaker(speaker_as)
|
|
||||||
if not curr_speaker:
|
|
||||||
raise bgp_driver_exc.BgpSpeakerNotAdded(local_as=speaker_as,
|
|
||||||
rtid=self.routerid)
|
|
||||||
|
|
||||||
# TODO(vikram): Filter and return the necessary information.
|
|
||||||
# Will be done as part of new RFE requirement
|
|
||||||
# https://bugs.launchpad.net/neutron/+bug/1527993
|
|
||||||
return curr_speaker.neighbor_state_get(address=peer_ip)
|
|
@ -1,75 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
import six
|
|
||||||
|
|
||||||
from neutron.services.bgp.common import constants as bgp_consts
|
|
||||||
from neutron.services.bgp.driver import exceptions as bgp_driver_exc
|
|
||||||
|
|
||||||
|
|
||||||
# Parameter validation functions provided are provided by the base.
|
|
||||||
def validate_as_num(param, as_num):
|
|
||||||
if not isinstance(as_num, six.integer_types):
|
|
||||||
raise bgp_driver_exc.InvalidParamType(param=param,
|
|
||||||
param_type='integer')
|
|
||||||
|
|
||||||
if not (bgp_consts.MIN_ASNUM <= as_num <= bgp_consts.MAX_ASNUM):
|
|
||||||
# Must be in [AS_NUM_MIN, AS_NUM_MAX] range.
|
|
||||||
allowed_range = ('[' +
|
|
||||||
str(bgp_consts.MIN_ASNUM) + '-' +
|
|
||||||
str(bgp_consts.MAX_ASNUM) +
|
|
||||||
']')
|
|
||||||
raise bgp_driver_exc.InvalidParamRange(param=param,
|
|
||||||
range=allowed_range)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_auth(auth_type, password):
|
|
||||||
validate_string(password)
|
|
||||||
if auth_type in bgp_consts.SUPPORTED_AUTH_TYPES:
|
|
||||||
if auth_type != 'none' and password is None:
|
|
||||||
raise bgp_driver_exc.PasswordNotSpecified(auth_type=auth_type)
|
|
||||||
if auth_type == 'none' and password is not None:
|
|
||||||
raise bgp_driver_exc.InvaildAuthType(auth_type=auth_type)
|
|
||||||
else:
|
|
||||||
raise bgp_driver_exc.InvaildAuthType(auth_type=auth_type)
|
|
||||||
|
|
||||||
|
|
||||||
def validate_string(param):
|
|
||||||
if param is not None:
|
|
||||||
if not isinstance(param, six.string_types):
|
|
||||||
raise bgp_driver_exc.InvalidParamType(param=param,
|
|
||||||
param_type='string')
|
|
||||||
|
|
||||||
|
|
||||||
class BgpMultiSpeakerCache(object):
|
|
||||||
"""Class for saving multiple BGP speakers information.
|
|
||||||
|
|
||||||
Version history:
|
|
||||||
1.0 - Initial version for caching multiple BGP speaker information.
|
|
||||||
"""
|
|
||||||
def __init__(self):
|
|
||||||
self.cache = {}
|
|
||||||
|
|
||||||
def get_hosted_bgp_speakers_count(self):
|
|
||||||
return len(self.cache)
|
|
||||||
|
|
||||||
def put_bgp_speaker(self, local_as, speaker):
|
|
||||||
self.cache[local_as] = speaker
|
|
||||||
|
|
||||||
def get_bgp_speaker(self, local_as):
|
|
||||||
return self.cache.get(local_as)
|
|
||||||
|
|
||||||
def remove_bgp_speaker(self, local_as):
|
|
||||||
self.cache.pop(local_as, None)
|
|
@ -1,191 +0,0 @@
|
|||||||
# 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.warning(_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)
|
|
@ -26,7 +26,6 @@ from neutron.common import topics
|
|||||||
from neutron import context
|
from neutron import context
|
||||||
from neutron.db import agents_db
|
from neutron.db import agents_db
|
||||||
from neutron.db import common_db_mixin
|
from neutron.db import common_db_mixin
|
||||||
from neutron.services.bgp.common import constants as bgp_const
|
|
||||||
|
|
||||||
HOST = 'localhost'
|
HOST = 'localhost'
|
||||||
DEFAULT_AZ = 'nova'
|
DEFAULT_AZ = 'nova'
|
||||||
@ -109,30 +108,6 @@ def register_dhcp_agent(host=HOST, networks=0, admin_state_up=True,
|
|||||||
context.get_admin_context(), agent['agent_type'], agent['host'])
|
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):
|
def kill_agent(agent_id):
|
||||||
hour_ago = timeutils.utcnow() - datetime.timedelta(hours=1)
|
hour_ago = timeutils.utcnow() - datetime.timedelta(hours=1)
|
||||||
FakePlugin().update_agent(
|
FakePlugin().update_agent(
|
||||||
|
@ -61,7 +61,6 @@ case $VENV in
|
|||||||
"api"|"api-pecan"|"full-pecan"|"dsvm-scenario")
|
"api"|"api-pecan"|"full-pecan"|"dsvm-scenario")
|
||||||
load_rc_hook api_extensions
|
load_rc_hook api_extensions
|
||||||
load_rc_hook qos
|
load_rc_hook qos
|
||||||
load_rc_hook bgp
|
|
||||||
if [[ "$VENV" =~ "pecan" ]]; then
|
if [[ "$VENV" =~ "pecan" ]]; then
|
||||||
load_conf_hook pecan
|
load_conf_hook pecan
|
||||||
fi
|
fi
|
||||||
|
@ -4,8 +4,6 @@ NETWORK_API_EXTENSIONS="
|
|||||||
allowed-address-pairs, \
|
allowed-address-pairs, \
|
||||||
auto-allocated-topology, \
|
auto-allocated-topology, \
|
||||||
availability_zone, \
|
availability_zone, \
|
||||||
bgp, \
|
|
||||||
bgp_dragent_scheduler, \
|
|
||||||
binding, \
|
binding, \
|
||||||
default-subnetpools, \
|
default-subnetpools, \
|
||||||
dhcp_agent_scheduler, \
|
dhcp_agent_scheduler, \
|
||||||
|
@ -1,2 +0,0 @@
|
|||||||
enable_plugin neutron git://git.openstack.org/openstack/neutron
|
|
||||||
enable_service q-bgp
|
|
@ -213,26 +213,8 @@
|
|||||||
"get_flavor_service_profile": "rule:regular_user",
|
"get_flavor_service_profile": "rule:regular_user",
|
||||||
"get_auto_allocated_topology": "rule:admin_or_owner",
|
"get_auto_allocated_topology": "rule:admin_or_owner",
|
||||||
|
|
||||||
"get_bgp_speaker": "rule:admin_only",
|
|
||||||
"create_bgp_speaker": "rule:admin_only",
|
|
||||||
"update_bgp_speaker": "rule:admin_only",
|
|
||||||
"delete_bgp_speaker": "rule:admin_only",
|
|
||||||
|
|
||||||
"get_bgp_peer": "rule:admin_only",
|
|
||||||
"create_bgp_peer": "rule:admin_only",
|
|
||||||
"update_bgp_peer": "rule:admin_only",
|
|
||||||
"delete_bgp_peer": "rule:admin_only",
|
|
||||||
|
|
||||||
"add_bgp_peer": "rule:admin_only",
|
|
||||||
"remove_bgp_peer": "rule:admin_only",
|
|
||||||
|
|
||||||
"add_gateway_network": "rule:admin_only",
|
"add_gateway_network": "rule:admin_only",
|
||||||
"remove_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"
|
|
||||||
}
|
}
|
||||||
|
@ -1,208 +0,0 @@
|
|||||||
# 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)
|
|
@ -1,286 +0,0 @@
|
|||||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
|
||||||
#
|
|
||||||
# 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 netaddr
|
|
||||||
from tempest import config
|
|
||||||
from tempest.lib import exceptions as lib_exc
|
|
||||||
from tempest import test
|
|
||||||
import testtools
|
|
||||||
|
|
||||||
from neutron.tests.tempest.api import base
|
|
||||||
from neutron.tests.tempest.common import tempest_fixtures as fixtures
|
|
||||||
|
|
||||||
CONF = config.CONF
|
|
||||||
|
|
||||||
|
|
||||||
class BgpSpeakerTestJSONBase(base.BaseAdminNetworkTest):
|
|
||||||
|
|
||||||
default_bgp_speaker_args = {'local_as': '1234',
|
|
||||||
'ip_version': 4,
|
|
||||||
'name': 'my-bgp-speaker',
|
|
||||||
'advertise_floating_ip_host_routes': True,
|
|
||||||
'advertise_tenant_networks': True}
|
|
||||||
default_bgp_peer_args = {'remote_as': '4321',
|
|
||||||
'name': 'my-bgp-peer',
|
|
||||||
'peer_ip': '192.168.1.1',
|
|
||||||
'auth_type': 'md5', 'password': 'my-secret'}
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
@test.requires_ext(extension="bgp_speaker", service="network")
|
|
||||||
def resource_setup(cls):
|
|
||||||
super(BgpSpeakerTestJSONBase, cls).resource_setup()
|
|
||||||
|
|
||||||
cls.admin_routerports = []
|
|
||||||
cls.admin_floatingips = []
|
|
||||||
cls.admin_routers = []
|
|
||||||
cls.ext_net_id = CONF.network.public_network_id
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def resource_cleanup(cls):
|
|
||||||
for floatingip in cls.admin_floatingips:
|
|
||||||
cls._try_delete_resource(cls.admin_client.delete_floatingip,
|
|
||||||
floatingip['id'])
|
|
||||||
for routerport in cls.admin_routerports:
|
|
||||||
cls._try_delete_resource(
|
|
||||||
cls.admin_client.remove_router_interface_with_subnet_id,
|
|
||||||
routerport['router_id'], routerport['subnet_id'])
|
|
||||||
for router in cls.admin_routers:
|
|
||||||
cls._try_delete_resource(cls.admin_client.delete_router,
|
|
||||||
router['id'])
|
|
||||||
super(BgpSpeakerTestJSONBase, cls).resource_cleanup()
|
|
||||||
|
|
||||||
def create_bgp_speaker(self, auto_delete=True, **args):
|
|
||||||
data = {'bgp_speaker': args}
|
|
||||||
bgp_speaker = self.admin_client.create_bgp_speaker(data)
|
|
||||||
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
|
|
||||||
if auto_delete:
|
|
||||||
self.addCleanup(self.delete_bgp_speaker, bgp_speaker_id)
|
|
||||||
return bgp_speaker
|
|
||||||
|
|
||||||
def create_bgp_peer(self, **args):
|
|
||||||
bgp_peer = self.admin_client.create_bgp_peer({'bgp_peer': args})
|
|
||||||
bgp_peer_id = bgp_peer['bgp-peer']['id']
|
|
||||||
self.addCleanup(self.delete_bgp_peer, bgp_peer_id)
|
|
||||||
return bgp_peer
|
|
||||||
|
|
||||||
def update_bgp_speaker(self, id, **args):
|
|
||||||
data = {'bgp_speaker': args}
|
|
||||||
return self.admin_client.update_bgp_speaker(id, data)
|
|
||||||
|
|
||||||
def delete_bgp_speaker(self, id):
|
|
||||||
return self.admin_client.delete_bgp_speaker(id)
|
|
||||||
|
|
||||||
def get_bgp_speaker(self, id):
|
|
||||||
return self.admin_client.get_bgp_speaker(id)
|
|
||||||
|
|
||||||
def create_bgp_speaker_and_peer(self):
|
|
||||||
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
|
|
||||||
bgp_peer = self.create_bgp_peer(**self.default_bgp_peer_args)
|
|
||||||
return (bgp_speaker, bgp_peer)
|
|
||||||
|
|
||||||
def delete_bgp_peer(self, id):
|
|
||||||
return self.admin_client.delete_bgp_peer(id)
|
|
||||||
|
|
||||||
def add_bgp_peer(self, bgp_speaker_id, bgp_peer_id):
|
|
||||||
return self.admin_client.add_bgp_peer_with_id(bgp_speaker_id,
|
|
||||||
bgp_peer_id)
|
|
||||||
|
|
||||||
def remove_bgp_peer(self, bgp_speaker_id, bgp_peer_id):
|
|
||||||
return self.admin_client.remove_bgp_peer_with_id(bgp_speaker_id,
|
|
||||||
bgp_peer_id)
|
|
||||||
|
|
||||||
def delete_address_scope(self, id):
|
|
||||||
return self.admin_client.delete_address_scope(id)
|
|
||||||
|
|
||||||
|
|
||||||
class BgpSpeakerTestJSON(BgpSpeakerTestJSONBase):
|
|
||||||
|
|
||||||
"""
|
|
||||||
Tests the following operations in the Neutron API using the REST client for
|
|
||||||
Neutron:
|
|
||||||
|
|
||||||
Create bgp-speaker
|
|
||||||
Delete bgp-speaker
|
|
||||||
Create bgp-peer
|
|
||||||
Update bgp-peer
|
|
||||||
Delete bgp-peer
|
|
||||||
"""
|
|
||||||
|
|
||||||
@test.idempotent_id('df259771-7104-4ffa-b77f-bd183600d7f9')
|
|
||||||
def test_delete_bgp_speaker(self):
|
|
||||||
bgp_speaker = self.create_bgp_speaker(auto_delete=False,
|
|
||||||
**self.default_bgp_speaker_args)
|
|
||||||
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
|
|
||||||
self.delete_bgp_speaker(bgp_speaker_id)
|
|
||||||
self.assertRaises(lib_exc.NotFound,
|
|
||||||
self.get_bgp_speaker,
|
|
||||||
bgp_speaker_id)
|
|
||||||
|
|
||||||
@test.idempotent_id('81d9dc45-19f8-4c6e-88b8-401d965cd1b0')
|
|
||||||
def test_create_bgp_peer(self):
|
|
||||||
self.create_bgp_peer(**self.default_bgp_peer_args)
|
|
||||||
|
|
||||||
@test.idempotent_id('6ade0319-1ee2-493c-ac4b-5eb230ff3a77')
|
|
||||||
def test_add_bgp_peer(self):
|
|
||||||
bgp_speaker, bgp_peer = self.create_bgp_speaker_and_peer()
|
|
||||||
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
|
|
||||||
bgp_peer_id = bgp_peer['bgp-peer']['id']
|
|
||||||
|
|
||||||
self.add_bgp_peer(bgp_speaker_id, bgp_peer_id)
|
|
||||||
bgp_speaker = self.admin_client.get_bgp_speaker(bgp_speaker_id)
|
|
||||||
bgp_peers_list = bgp_speaker['bgp-speaker']['peers']
|
|
||||||
self.assertEqual(1, len(bgp_peers_list))
|
|
||||||
self.assertTrue(bgp_peer_id in bgp_peers_list)
|
|
||||||
|
|
||||||
@test.idempotent_id('f9737708-1d79-440b-8350-779f97d882ee')
|
|
||||||
def test_remove_bgp_peer(self):
|
|
||||||
bgp_peer = self.create_bgp_peer(**self.default_bgp_peer_args)
|
|
||||||
bgp_peer_id = bgp_peer['bgp-peer']['id']
|
|
||||||
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
|
|
||||||
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
|
|
||||||
self.add_bgp_peer(bgp_speaker_id, bgp_peer_id)
|
|
||||||
bgp_speaker = self.admin_client.get_bgp_speaker(bgp_speaker_id)
|
|
||||||
bgp_peers_list = bgp_speaker['bgp-speaker']['peers']
|
|
||||||
self.assertTrue(bgp_peer_id in bgp_peers_list)
|
|
||||||
|
|
||||||
bgp_speaker = self.remove_bgp_peer(bgp_speaker_id, bgp_peer_id)
|
|
||||||
bgp_speaker = self.admin_client.get_bgp_speaker(bgp_speaker_id)
|
|
||||||
bgp_peers_list = bgp_speaker['bgp-speaker']['peers']
|
|
||||||
self.assertTrue(not bgp_peers_list)
|
|
||||||
|
|
||||||
@testtools.skip('bug/1553374')
|
|
||||||
@test.idempotent_id('23c8eb37-d10d-4f43-b2e7-6542cb6a4405')
|
|
||||||
def test_add_gateway_network(self):
|
|
||||||
self.useFixture(fixtures.LockFixture('gateway_network_binding'))
|
|
||||||
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
|
|
||||||
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
|
|
||||||
|
|
||||||
self.admin_client.add_bgp_gateway_network(bgp_speaker_id,
|
|
||||||
self.ext_net_id)
|
|
||||||
bgp_speaker = self.admin_client.get_bgp_speaker(bgp_speaker_id)
|
|
||||||
network_list = bgp_speaker['bgp-speaker']['networks']
|
|
||||||
self.assertEqual(1, len(network_list))
|
|
||||||
self.assertTrue(self.ext_net_id in network_list)
|
|
||||||
|
|
||||||
@testtools.skip('bug/1553374')
|
|
||||||
@test.idempotent_id('6cfc7137-0d99-4a3d-826c-9d1a3a1767b0')
|
|
||||||
def test_remove_gateway_network(self):
|
|
||||||
self.useFixture(fixtures.LockFixture('gateway_network_binding'))
|
|
||||||
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
|
|
||||||
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
|
|
||||||
self.admin_client.add_bgp_gateway_network(bgp_speaker_id,
|
|
||||||
self.ext_net_id)
|
|
||||||
bgp_speaker = self.admin_client.get_bgp_speaker(bgp_speaker_id)
|
|
||||||
networks = bgp_speaker['bgp-speaker']['networks']
|
|
||||||
|
|
||||||
self.assertTrue(self.ext_net_id in networks)
|
|
||||||
self.admin_client.remove_bgp_gateway_network(bgp_speaker_id,
|
|
||||||
self.ext_net_id)
|
|
||||||
bgp_speaker = self.admin_client.get_bgp_speaker(bgp_speaker_id)
|
|
||||||
network_list = bgp_speaker['bgp-speaker']['networks']
|
|
||||||
self.assertTrue(not network_list)
|
|
||||||
|
|
||||||
@testtools.skip('bug/1553374')
|
|
||||||
@test.idempotent_id('5bef22ad-5e70-4f7b-937a-dc1944642996')
|
|
||||||
def test_get_advertised_routes_null_address_scope(self):
|
|
||||||
self.useFixture(fixtures.LockFixture('gateway_network_binding'))
|
|
||||||
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
|
|
||||||
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
|
|
||||||
self.admin_client.add_bgp_gateway_network(bgp_speaker_id,
|
|
||||||
self.ext_net_id)
|
|
||||||
routes = self.admin_client.get_bgp_advertised_routes(bgp_speaker_id)
|
|
||||||
self.assertEqual(0, len(routes['advertised_routes']))
|
|
||||||
|
|
||||||
@testtools.skip('bug/1553374')
|
|
||||||
@test.idempotent_id('cae9cdb1-ad65-423c-9604-d4cd0073616e')
|
|
||||||
def test_get_advertised_routes_floating_ips(self):
|
|
||||||
self.useFixture(fixtures.LockFixture('gateway_network_binding'))
|
|
||||||
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
|
|
||||||
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
|
|
||||||
self.admin_client.add_bgp_gateway_network(bgp_speaker_id,
|
|
||||||
self.ext_net_id)
|
|
||||||
tenant_net = self.create_network()
|
|
||||||
tenant_subnet = self.create_subnet(tenant_net)
|
|
||||||
ext_gw_info = {'network_id': self.ext_net_id}
|
|
||||||
router = self.admin_client.create_router(
|
|
||||||
'my-router',
|
|
||||||
external_gateway_info=ext_gw_info,
|
|
||||||
admin_state_up=True,
|
|
||||||
distributed=False)
|
|
||||||
self.admin_routers.append(router['router'])
|
|
||||||
self.admin_client.add_router_interface_with_subnet_id(
|
|
||||||
router['router']['id'],
|
|
||||||
tenant_subnet['id'])
|
|
||||||
self.admin_routerports.append({'router_id': router['router']['id'],
|
|
||||||
'subnet_id': tenant_subnet['id']})
|
|
||||||
tenant_port = self.create_port(tenant_net)
|
|
||||||
floatingip = self.create_floatingip(self.ext_net_id)
|
|
||||||
self.admin_floatingips.append(floatingip)
|
|
||||||
self.client.update_floatingip(floatingip['id'],
|
|
||||||
port_id=tenant_port['id'])
|
|
||||||
routes = self.admin_client.get_bgp_advertised_routes(bgp_speaker_id)
|
|
||||||
self.assertEqual(1, len(routes['advertised_routes']))
|
|
||||||
self.assertEqual(floatingip['floating_ip_address'] + '/32',
|
|
||||||
routes['advertised_routes'][0]['destination'])
|
|
||||||
|
|
||||||
@testtools.skip('bug/1553374')
|
|
||||||
@test.idempotent_id('c9ad566e-fe8f-4559-8303-bbad9062a30c')
|
|
||||||
def test_get_advertised_routes_tenant_networks(self):
|
|
||||||
self.useFixture(fixtures.LockFixture('gateway_network_binding'))
|
|
||||||
addr_scope = self.create_address_scope('my-scope', ip_version=4)
|
|
||||||
ext_net = self.create_shared_network(**{'router:external': True})
|
|
||||||
tenant_net = self.create_network()
|
|
||||||
ext_subnetpool = self.create_subnetpool(
|
|
||||||
'test-pool-ext',
|
|
||||||
is_admin=True,
|
|
||||||
default_prefixlen=24,
|
|
||||||
address_scope_id=addr_scope['id'],
|
|
||||||
prefixes=['8.0.0.0/8'])
|
|
||||||
tenant_subnetpool = self.create_subnetpool(
|
|
||||||
'tenant-test-pool',
|
|
||||||
default_prefixlen=25,
|
|
||||||
address_scope_id=addr_scope['id'],
|
|
||||||
prefixes=['10.10.0.0/16'])
|
|
||||||
self.create_subnet({'id': ext_net['id']},
|
|
||||||
cidr=netaddr.IPNetwork('8.0.0.0/24'),
|
|
||||||
ip_version=4,
|
|
||||||
client=self.admin_client,
|
|
||||||
subnetpool_id=ext_subnetpool['id'])
|
|
||||||
tenant_subnet = self.create_subnet(
|
|
||||||
{'id': tenant_net['id']},
|
|
||||||
cidr=netaddr.IPNetwork('10.10.0.0/24'),
|
|
||||||
ip_version=4,
|
|
||||||
subnetpool_id=tenant_subnetpool['id'])
|
|
||||||
ext_gw_info = {'network_id': ext_net['id']}
|
|
||||||
router = self.admin_client.create_router(
|
|
||||||
'my-router',
|
|
||||||
external_gateway_info=ext_gw_info,
|
|
||||||
distributed=False)['router']
|
|
||||||
self.admin_routers.append(router)
|
|
||||||
self.admin_client.add_router_interface_with_subnet_id(
|
|
||||||
router['id'],
|
|
||||||
tenant_subnet['id'])
|
|
||||||
self.admin_routerports.append({'router_id': router['id'],
|
|
||||||
'subnet_id': tenant_subnet['id']})
|
|
||||||
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
|
|
||||||
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
|
|
||||||
self.admin_client.add_bgp_gateway_network(bgp_speaker_id,
|
|
||||||
ext_net['id'])
|
|
||||||
routes = self.admin_client.get_bgp_advertised_routes(bgp_speaker_id)
|
|
||||||
self.assertEqual(1, len(routes['advertised_routes']))
|
|
||||||
self.assertEqual(tenant_subnet['cidr'],
|
|
||||||
routes['advertised_routes'][0]['destination'])
|
|
||||||
fixed_ip = router['external_gateway_info']['external_fixed_ips'][0]
|
|
||||||
self.assertEqual(fixed_ip['ip_address'],
|
|
||||||
routes['advertised_routes'][0]['next_hop'])
|
|
@ -1,120 +0,0 @@
|
|||||||
# Copyright 2016 Hewlett Packard Enterprise Development Company LP
|
|
||||||
#
|
|
||||||
# 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 netaddr
|
|
||||||
from tempest.lib import exceptions as lib_exc
|
|
||||||
|
|
||||||
from neutron.tests.tempest.api import test_bgp_speaker_extensions as test_base
|
|
||||||
from tempest import test
|
|
||||||
|
|
||||||
|
|
||||||
class BgpSpeakerTestJSONNegative(test_base.BgpSpeakerTestJSONBase):
|
|
||||||
|
|
||||||
"""Negative test cases asserting proper behavior of BGP API extension"""
|
|
||||||
|
|
||||||
@test.attr(type='negative')
|
|
||||||
@test.idempotent_id('75e9ee2f-6efd-4320-bff7-ae24741c8b06')
|
|
||||||
def test_create_bgp_speaker_illegal_local_asn(self):
|
|
||||||
self.assertRaises(lib_exc.BadRequest,
|
|
||||||
self.create_bgp_speaker,
|
|
||||||
local_as='65537')
|
|
||||||
|
|
||||||
@test.attr(type='negative')
|
|
||||||
@test.idempotent_id('6742ec2e-382a-4453-8791-13a19b47cd13')
|
|
||||||
def test_create_bgp_speaker_non_admin(self):
|
|
||||||
self.assertRaises(lib_exc.Forbidden,
|
|
||||||
self.client.create_bgp_speaker,
|
|
||||||
{'bgp_speaker': self.default_bgp_speaker_args})
|
|
||||||
|
|
||||||
@test.attr(type='negative')
|
|
||||||
@test.idempotent_id('33f7aaf0-9786-478b-b2d1-a51086a50eb4')
|
|
||||||
def test_create_bgp_peer_non_admin(self):
|
|
||||||
self.assertRaises(lib_exc.Forbidden,
|
|
||||||
self.client.create_bgp_peer,
|
|
||||||
{'bgp_peer': self.default_bgp_peer_args})
|
|
||||||
|
|
||||||
@test.attr(type='negative')
|
|
||||||
@test.idempotent_id('39435932-0266-4358-899b-0e9b1e53c3e9')
|
|
||||||
def test_update_bgp_speaker_local_asn(self):
|
|
||||||
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
|
|
||||||
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
|
|
||||||
|
|
||||||
self.assertRaises(lib_exc.BadRequest, self.update_bgp_speaker,
|
|
||||||
bgp_speaker_id, local_as='4321')
|
|
||||||
|
|
||||||
@test.idempotent_id('9cc33701-51e5-421f-a5d5-fd7b330e550f')
|
|
||||||
def test_get_advertised_routes_tenant_networks(self):
|
|
||||||
addr_scope1 = self.create_address_scope('my-scope1', ip_version=4)
|
|
||||||
addr_scope2 = self.create_address_scope('my-scope2', ip_version=4)
|
|
||||||
ext_net = self.create_shared_network(**{'router:external': True})
|
|
||||||
tenant_net1 = self.create_network()
|
|
||||||
tenant_net2 = self.create_network()
|
|
||||||
ext_subnetpool = self.create_subnetpool(
|
|
||||||
'test-pool-ext',
|
|
||||||
is_admin=True,
|
|
||||||
default_prefixlen=24,
|
|
||||||
address_scope_id=addr_scope1['id'],
|
|
||||||
prefixes=['8.0.0.0/8'])
|
|
||||||
tenant_subnetpool1 = self.create_subnetpool(
|
|
||||||
'tenant-test-pool',
|
|
||||||
default_prefixlen=25,
|
|
||||||
address_scope_id=addr_scope1['id'],
|
|
||||||
prefixes=['10.10.0.0/16'])
|
|
||||||
tenant_subnetpool2 = self.create_subnetpool(
|
|
||||||
'tenant-test-pool',
|
|
||||||
default_prefixlen=25,
|
|
||||||
address_scope_id=addr_scope2['id'],
|
|
||||||
prefixes=['11.10.0.0/16'])
|
|
||||||
self.create_subnet({'id': ext_net['id']},
|
|
||||||
cidr=netaddr.IPNetwork('8.0.0.0/24'),
|
|
||||||
ip_version=4,
|
|
||||||
client=self.admin_client,
|
|
||||||
subnetpool_id=ext_subnetpool['id'])
|
|
||||||
tenant_subnet1 = self.create_subnet(
|
|
||||||
{'id': tenant_net1['id']},
|
|
||||||
cidr=netaddr.IPNetwork('10.10.0.0/24'),
|
|
||||||
ip_version=4,
|
|
||||||
subnetpool_id=tenant_subnetpool1['id'])
|
|
||||||
tenant_subnet2 = self.create_subnet(
|
|
||||||
{'id': tenant_net2['id']},
|
|
||||||
cidr=netaddr.IPNetwork('11.10.0.0/24'),
|
|
||||||
ip_version=4,
|
|
||||||
subnetpool_id=tenant_subnetpool2['id'])
|
|
||||||
ext_gw_info = {'network_id': ext_net['id']}
|
|
||||||
router = self.admin_client.create_router(
|
|
||||||
'my-router',
|
|
||||||
distributed=False,
|
|
||||||
external_gateway_info=ext_gw_info)['router']
|
|
||||||
self.admin_routers.append(router)
|
|
||||||
self.admin_client.add_router_interface_with_subnet_id(
|
|
||||||
router['id'],
|
|
||||||
tenant_subnet1['id'])
|
|
||||||
self.admin_routerports.append({'router_id': router['id'],
|
|
||||||
'subnet_id': tenant_subnet1['id']})
|
|
||||||
self.admin_client.add_router_interface_with_subnet_id(
|
|
||||||
router['id'],
|
|
||||||
tenant_subnet2['id'])
|
|
||||||
self.admin_routerports.append({'router_id': router['id'],
|
|
||||||
'subnet_id': tenant_subnet2['id']})
|
|
||||||
bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args)
|
|
||||||
bgp_speaker_id = bgp_speaker['bgp-speaker']['id']
|
|
||||||
self.admin_client.add_bgp_gateway_network(bgp_speaker_id,
|
|
||||||
ext_net['id'])
|
|
||||||
routes = self.admin_client.get_bgp_advertised_routes(bgp_speaker_id)
|
|
||||||
self.assertEqual(1, len(routes['advertised_routes']))
|
|
||||||
self.assertEqual(tenant_subnet1['cidr'],
|
|
||||||
routes['advertised_routes'][0]['destination'])
|
|
||||||
fixed_ip = router['external_gateway_info']['external_fixed_ips'][0]
|
|
||||||
self.assertEqual(fixed_ip['ip_address'],
|
|
||||||
routes['advertised_routes'][0]['next_hop'])
|
|
@ -47,8 +47,6 @@ class NetworkClientJSON(service_client.RestClient):
|
|||||||
# the following map is used to construct proper URI
|
# the following map is used to construct proper URI
|
||||||
# for the given neutron resource
|
# for the given neutron resource
|
||||||
service_resource_prefix_map = {
|
service_resource_prefix_map = {
|
||||||
'bgp-peers': '',
|
|
||||||
'bgp-speakers': '',
|
|
||||||
'networks': '',
|
'networks': '',
|
||||||
'subnets': '',
|
'subnets': '',
|
||||||
'subnetpools': '',
|
'subnetpools': '',
|
||||||
@ -212,125 +210,6 @@ class NetworkClientJSON(service_client.RestClient):
|
|||||||
self.expected_success(200, resp.status)
|
self.expected_success(200, resp.status)
|
||||||
return service_client.ResponseBody(resp, body)
|
return service_client.ResponseBody(resp, body)
|
||||||
|
|
||||||
# BGP speaker methods
|
|
||||||
def create_bgp_speaker(self, post_data):
|
|
||||||
body = self.serialize_list(post_data, "bgp-speakers", "bgp-speaker")
|
|
||||||
uri = self.get_uri("bgp-speakers")
|
|
||||||
resp, body = self.post(uri, body)
|
|
||||||
body = {'bgp-speaker': self.deserialize_list(body)}
|
|
||||||
self.expected_success(201, resp.status)
|
|
||||||
return service_client.ResponseBody(resp, body)
|
|
||||||
|
|
||||||
def get_bgp_speaker(self, id):
|
|
||||||
uri = self.get_uri("bgp-speakers")
|
|
||||||
bgp_speaker_uri = '%s/%s' % (uri, id)
|
|
||||||
resp, body = self.get(bgp_speaker_uri)
|
|
||||||
body = {'bgp-speaker': self.deserialize_list(body)}
|
|
||||||
self.expected_success(200, resp.status)
|
|
||||||
return service_client.ResponseBody(resp, body)
|
|
||||||
|
|
||||||
def get_bgp_speakers(self):
|
|
||||||
uri = self.get_uri("bgp-speakers")
|
|
||||||
resp, body = self.get(uri)
|
|
||||||
body = {'bgp-speakers': self.deserialize_list(body)}
|
|
||||||
self.expected_success(200, resp.status)
|
|
||||||
return service_client.ResponseBody(resp, body)
|
|
||||||
|
|
||||||
def update_bgp_speaker(self, id, put_data):
|
|
||||||
body = self.serialize_list(put_data, "bgp-speakers", "bgp-speaker")
|
|
||||||
uri = self.get_uri("bgp-speakers")
|
|
||||||
bgp_speaker_uri = '%s/%s' % (uri, id)
|
|
||||||
resp, body = self.put(bgp_speaker_uri, body)
|
|
||||||
body = {'bgp-speaker': self.deserialize_list(body)}
|
|
||||||
self.expected_success(200, resp.status)
|
|
||||||
return service_client.ResponseBody(resp, body)
|
|
||||||
|
|
||||||
def delete_bgp_speaker(self, id):
|
|
||||||
uri = self.get_uri("bgp-speakers")
|
|
||||||
bgp_speaker_uri = '%s/%s' % (uri, id)
|
|
||||||
resp, body = self.delete(bgp_speaker_uri)
|
|
||||||
self.expected_success(204, resp.status)
|
|
||||||
return service_client.ResponseBody(resp, body)
|
|
||||||
|
|
||||||
def create_bgp_peer(self, post_data):
|
|
||||||
body = self.serialize_list(post_data, "bgp-peers", "bgp-peer")
|
|
||||||
uri = self.get_uri("bgp-peers")
|
|
||||||
resp, body = self.post(uri, body)
|
|
||||||
body = {'bgp-peer': self.deserialize_list(body)}
|
|
||||||
self.expected_success(201, resp.status)
|
|
||||||
return service_client.ResponseBody(resp, body)
|
|
||||||
|
|
||||||
def get_bgp_peer(self, id):
|
|
||||||
uri = self.get_uri("bgp-peers")
|
|
||||||
bgp_speaker_uri = '%s/%s' % (uri, id)
|
|
||||||
resp, body = self.get(bgp_speaker_uri)
|
|
||||||
body = {'bgp-peer': self.deserialize_list(body)}
|
|
||||||
self.expected_success(200, resp.status)
|
|
||||||
return service_client.ResponseBody(resp, body)
|
|
||||||
|
|
||||||
def delete_bgp_peer(self, id):
|
|
||||||
uri = self.get_uri("bgp-peers")
|
|
||||||
bgp_speaker_uri = '%s/%s' % (uri, id)
|
|
||||||
resp, body = self.delete(bgp_speaker_uri)
|
|
||||||
self.expected_success(204, resp.status)
|
|
||||||
return service_client.ResponseBody(resp, body)
|
|
||||||
|
|
||||||
def add_bgp_peer_with_id(self, bgp_speaker_id, bgp_peer_id):
|
|
||||||
uri = '%s/bgp-speakers/%s/add_bgp_peer' % (self.uri_prefix,
|
|
||||||
bgp_speaker_id)
|
|
||||||
update_body = {"bgp_peer_id": bgp_peer_id}
|
|
||||||
update_body = jsonutils.dumps(update_body)
|
|
||||||
resp, body = self.put(uri, update_body)
|
|
||||||
self.expected_success(200, resp.status)
|
|
||||||
body = jsonutils.loads(body)
|
|
||||||
return service_client.ResponseBody(resp, body)
|
|
||||||
|
|
||||||
def remove_bgp_peer_with_id(self, bgp_speaker_id, bgp_peer_id):
|
|
||||||
uri = '%s/bgp-speakers/%s/remove_bgp_peer' % (self.uri_prefix,
|
|
||||||
bgp_speaker_id)
|
|
||||||
update_body = {"bgp_peer_id": bgp_peer_id}
|
|
||||||
update_body = jsonutils.dumps(update_body)
|
|
||||||
resp, body = self.put(uri, update_body)
|
|
||||||
self.expected_success(200, resp.status)
|
|
||||||
body = jsonutils.loads(body)
|
|
||||||
return service_client.ResponseBody(resp, body)
|
|
||||||
|
|
||||||
def add_bgp_gateway_network(self, bgp_speaker_id, network_id):
|
|
||||||
uri = '%s/bgp-speakers/%s/add_gateway_network' % (self.uri_prefix,
|
|
||||||
bgp_speaker_id)
|
|
||||||
update_body = {"network_id": network_id}
|
|
||||||
update_body = jsonutils.dumps(update_body)
|
|
||||||
resp, body = self.put(uri, update_body)
|
|
||||||
self.expected_success(200, resp.status)
|
|
||||||
body = jsonutils.loads(body)
|
|
||||||
return service_client.ResponseBody(resp, body)
|
|
||||||
|
|
||||||
def remove_bgp_gateway_network(self, bgp_speaker_id, network_id):
|
|
||||||
uri = '%s/bgp-speakers/%s/remove_gateway_network'
|
|
||||||
uri = uri % (self.uri_prefix, bgp_speaker_id)
|
|
||||||
update_body = {"network_id": network_id}
|
|
||||||
update_body = jsonutils.dumps(update_body)
|
|
||||||
resp, body = self.put(uri, update_body)
|
|
||||||
self.expected_success(200, resp.status)
|
|
||||||
body = jsonutils.loads(body)
|
|
||||||
return service_client.ResponseBody(resp, body)
|
|
||||||
|
|
||||||
def get_bgp_advertised_routes(self, bgp_speaker_id):
|
|
||||||
base_uri = '%s/bgp-speakers/%s/get_advertised_routes'
|
|
||||||
uri = base_uri % (self.uri_prefix, bgp_speaker_id)
|
|
||||||
resp, body = self.get(uri)
|
|
||||||
body = {'advertised_routes': self.deserialize_list(body)}
|
|
||||||
self.expected_success(200, resp.status)
|
|
||||||
return service_client.ResponseBody(resp, body)
|
|
||||||
|
|
||||||
def get_bgp_router_routes(self, router_id):
|
|
||||||
base_uri = '%s/router-routes/%s'
|
|
||||||
uri = base_uri % (self.uri_prefix, router_id)
|
|
||||||
resp, body = self.get(uri)
|
|
||||||
body = self.deserialize_list(body)
|
|
||||||
self.expected_success(200, resp.status)
|
|
||||||
return service_client.ResponseBody(resp, body)
|
|
||||||
|
|
||||||
# Common methods that are hard to automate
|
# Common methods that are hard to automate
|
||||||
def create_bulk_network(self, names, shared=False):
|
def create_bulk_network(self, names, shared=False):
|
||||||
network_list = [{'name': name, 'shared': shared} for name in names]
|
network_list = [{'name': name, 'shared': shared} for name in names]
|
||||||
|
@ -1,83 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from neutron.api.rpc.agentnotifiers import bgp_dr_rpc_agent_api
|
|
||||||
from neutron import context
|
|
||||||
from neutron.tests import base
|
|
||||||
|
|
||||||
|
|
||||||
class TestBgpDrAgentNotifyApi(base.BaseTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestBgpDrAgentNotifyApi, self).setUp()
|
|
||||||
self.notifier = (
|
|
||||||
bgp_dr_rpc_agent_api.BgpDrAgentNotifyApi())
|
|
||||||
|
|
||||||
mock_cast_p = mock.patch.object(self.notifier,
|
|
||||||
'_notification_host_cast')
|
|
||||||
self.mock_cast = mock_cast_p.start()
|
|
||||||
mock_call_p = mock.patch.object(self.notifier,
|
|
||||||
'_notification_host_call')
|
|
||||||
self.mock_call = mock_call_p.start()
|
|
||||||
self.context = context.get_admin_context()
|
|
||||||
self.host = 'host-1'
|
|
||||||
|
|
||||||
def test_notify_dragent_bgp_routes_advertisement(self):
|
|
||||||
bgp_speaker_id = 'bgp-speaker-1'
|
|
||||||
routes = [{'destination': '1.1.1.1', 'next_hop': '2.2.2.2'}]
|
|
||||||
self.notifier.bgp_routes_advertisement(self.context, bgp_speaker_id,
|
|
||||||
routes, self.host)
|
|
||||||
self.assertEqual(1, self.mock_cast.call_count)
|
|
||||||
self.assertEqual(0, self.mock_call.call_count)
|
|
||||||
|
|
||||||
def test_notify_dragent_bgp_routes_withdrawal(self):
|
|
||||||
bgp_speaker_id = 'bgp-speaker-1'
|
|
||||||
routes = [{'destination': '1.1.1.1'}]
|
|
||||||
self.notifier.bgp_routes_withdrawal(self.context, bgp_speaker_id,
|
|
||||||
routes, self.host)
|
|
||||||
self.assertEqual(1, self.mock_cast.call_count)
|
|
||||||
self.assertEqual(0, self.mock_call.call_count)
|
|
||||||
|
|
||||||
def test_notify_bgp_peer_disassociated(self):
|
|
||||||
bgp_speaker_id = 'bgp-speaker-1'
|
|
||||||
bgp_peer_ip = '1.1.1.1'
|
|
||||||
self.notifier.bgp_peer_disassociated(self.context, bgp_speaker_id,
|
|
||||||
bgp_peer_ip, self.host)
|
|
||||||
self.assertEqual(1, self.mock_cast.call_count)
|
|
||||||
self.assertEqual(0, self.mock_call.call_count)
|
|
||||||
|
|
||||||
def test_notify_bgp_peer_associated(self):
|
|
||||||
bgp_speaker_id = 'bgp-speaker-1'
|
|
||||||
bgp_peer_id = 'bgp-peer-1'
|
|
||||||
self.notifier.bgp_peer_associated(self.context, bgp_speaker_id,
|
|
||||||
bgp_peer_id, self.host)
|
|
||||||
self.assertEqual(1, self.mock_cast.call_count)
|
|
||||||
self.assertEqual(0, self.mock_call.call_count)
|
|
||||||
|
|
||||||
def test_notify_bgp_speaker_created(self):
|
|
||||||
bgp_speaker_id = 'bgp-speaker-1'
|
|
||||||
self.notifier.bgp_speaker_created(self.context, bgp_speaker_id,
|
|
||||||
self.host)
|
|
||||||
self.assertEqual(1, self.mock_cast.call_count)
|
|
||||||
self.assertEqual(0, self.mock_call.call_count)
|
|
||||||
|
|
||||||
def test_notify_bgp_speaker_removed(self):
|
|
||||||
bgp_speaker_id = 'bgp-speaker-1'
|
|
||||||
self.notifier.bgp_speaker_removed(self.context, bgp_speaker_id,
|
|
||||||
self.host)
|
|
||||||
self.assertEqual(1, self.mock_cast.call_count)
|
|
||||||
self.assertEqual(0, self.mock_call.call_count)
|
|
@ -1,44 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from neutron.api.rpc.handlers import bgp_speaker_rpc
|
|
||||||
from neutron.tests import base
|
|
||||||
|
|
||||||
|
|
||||||
class TestBgpSpeakerRpcCallback(base.BaseTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.plugin_p = mock.patch('neutron.manager.NeutronManager.'
|
|
||||||
'get_service_plugins')
|
|
||||||
self.plugin = self.plugin_p.start()
|
|
||||||
self.callback = bgp_speaker_rpc.BgpSpeakerRpcCallback()
|
|
||||||
super(TestBgpSpeakerRpcCallback, self).setUp()
|
|
||||||
|
|
||||||
def test_get_bgp_speaker_info(self):
|
|
||||||
self.callback.get_bgp_speaker_info(mock.Mock(),
|
|
||||||
bgp_speaker_id='id1')
|
|
||||||
self.assertIsNotNone(len(self.plugin.mock_calls))
|
|
||||||
|
|
||||||
def test_get_bgp_peer_info(self):
|
|
||||||
self.callback.get_bgp_peer_info(mock.Mock(),
|
|
||||||
bgp_peer_id='id1')
|
|
||||||
self.assertIsNotNone(len(self.plugin.mock_calls))
|
|
||||||
|
|
||||||
def test_get_bgp_speakers(self):
|
|
||||||
self.callback.get_bgp_speakers(mock.Mock(),
|
|
||||||
host='host')
|
|
||||||
self.assertIsNotNone(len(self.plugin.mock_calls))
|
|
File diff suppressed because it is too large
Load Diff
@ -1,203 +0,0 @@
|
|||||||
# 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()
|
|
@ -126,9 +126,6 @@ class AgentDBTestMixIn(object):
|
|||||||
def _register_l3_agent(self, host):
|
def _register_l3_agent(self, host):
|
||||||
helpers.register_l3_agent(host)
|
helpers.register_l3_agent(host)
|
||||||
|
|
||||||
def _register_bgp_dragent(self, host):
|
|
||||||
helpers.register_bgp_dragent(host)
|
|
||||||
|
|
||||||
|
|
||||||
class AgentDBTestCase(AgentDBTestMixIn,
|
class AgentDBTestCase(AgentDBTestMixIn,
|
||||||
test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
|
test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
|
||||||
|
@ -1,224 +0,0 @@
|
|||||||
# 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))
|
|
@ -1,736 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
import copy
|
|
||||||
import sys
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
import eventlet
|
|
||||||
import mock
|
|
||||||
from oslo_config import cfg
|
|
||||||
import testtools
|
|
||||||
|
|
||||||
from neutron.common import config as common_config
|
|
||||||
from neutron import context
|
|
||||||
from neutron.services.bgp.agent import bgp_dragent
|
|
||||||
from neutron.services.bgp.agent import config as bgp_config
|
|
||||||
from neutron.services.bgp.agent import entry
|
|
||||||
from neutron.tests import base
|
|
||||||
|
|
||||||
HOSTNAME = 'hostname'
|
|
||||||
rpc_api = bgp_dragent.BgpDrPluginApi
|
|
||||||
BGP_PLUGIN = '%s.%s' % (rpc_api.__module__, rpc_api.__name__)
|
|
||||||
|
|
||||||
FAKE_BGPSPEAKER_UUID = str(uuid.uuid4())
|
|
||||||
FAKE_BGPPEER_UUID = str(uuid.uuid4())
|
|
||||||
|
|
||||||
FAKE_BGP_SPEAKER = {'id': FAKE_BGPSPEAKER_UUID,
|
|
||||||
'local_as': 12345,
|
|
||||||
'peers': [{'remote_as': '2345',
|
|
||||||
'peer_ip': '1.1.1.1',
|
|
||||||
'auth_type': 'none',
|
|
||||||
'password': ''}],
|
|
||||||
'advertised_routes': []}
|
|
||||||
|
|
||||||
FAKE_BGP_PEER = {'id': FAKE_BGPPEER_UUID,
|
|
||||||
'remote_as': '2345',
|
|
||||||
'peer_ip': '1.1.1.1',
|
|
||||||
'auth_type': 'none',
|
|
||||||
'password': ''}
|
|
||||||
|
|
||||||
FAKE_ROUTE = {'id': FAKE_BGPSPEAKER_UUID,
|
|
||||||
'destination': '2.2.2.2/32',
|
|
||||||
'next_hop': '3.3.3.3'}
|
|
||||||
|
|
||||||
FAKE_ROUTES = {'routes': {'id': FAKE_BGPSPEAKER_UUID,
|
|
||||||
'destination': '2.2.2.2/32',
|
|
||||||
'next_hop': '3.3.3.3'}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TestBgpDrAgent(base.BaseTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TestBgpDrAgent, self).setUp()
|
|
||||||
cfg.CONF.register_opts(bgp_config.BGP_DRIVER_OPTS, 'BGP')
|
|
||||||
cfg.CONF.register_opts(bgp_config.BGP_PROTO_CONFIG_OPTS, 'BGP')
|
|
||||||
mock_log_p = mock.patch.object(bgp_dragent, 'LOG')
|
|
||||||
self.mock_log = mock_log_p.start()
|
|
||||||
self.driver_cls_p = mock.patch(
|
|
||||||
'neutron.services.bgp.agent.bgp_dragent.importutils.import_class')
|
|
||||||
self.driver_cls = self.driver_cls_p.start()
|
|
||||||
self.context = context.get_admin_context()
|
|
||||||
|
|
||||||
def test_bgp_dragent_manager(self):
|
|
||||||
state_rpc_str = 'neutron.agent.rpc.PluginReportStateAPI'
|
|
||||||
# sync_state is needed for this test
|
|
||||||
with mock.patch.object(bgp_dragent.BgpDrAgentWithStateReport,
|
|
||||||
'sync_state',
|
|
||||||
autospec=True) as mock_sync_state:
|
|
||||||
with mock.patch(state_rpc_str) as state_rpc:
|
|
||||||
with mock.patch.object(sys, 'argv') as sys_argv:
|
|
||||||
sys_argv.return_value = [
|
|
||||||
'bgp_dragent', '--config-file',
|
|
||||||
base.etcdir('neutron.conf')]
|
|
||||||
common_config.init(sys.argv[1:])
|
|
||||||
agent_mgr = bgp_dragent.BgpDrAgentWithStateReport(
|
|
||||||
'testhost')
|
|
||||||
eventlet.greenthread.sleep(1)
|
|
||||||
agent_mgr.after_start()
|
|
||||||
self.assertIsNotNone(len(mock_sync_state.mock_calls))
|
|
||||||
state_rpc.assert_has_calls(
|
|
||||||
[mock.call(mock.ANY),
|
|
||||||
mock.call().report_state(mock.ANY, mock.ANY,
|
|
||||||
mock.ANY)])
|
|
||||||
|
|
||||||
def test_bgp_dragent_main_agent_manager(self):
|
|
||||||
logging_str = 'neutron.agent.common.config.setup_logging'
|
|
||||||
launcher_str = 'oslo_service.service.ServiceLauncher'
|
|
||||||
with mock.patch(logging_str):
|
|
||||||
with mock.patch.object(sys, 'argv') as sys_argv:
|
|
||||||
with mock.patch(launcher_str) as launcher:
|
|
||||||
sys_argv.return_value = ['bgp_dragent', '--config-file',
|
|
||||||
base.etcdir('neutron.conf')]
|
|
||||||
entry.main()
|
|
||||||
launcher.assert_has_calls(
|
|
||||||
[mock.call(cfg.CONF),
|
|
||||||
mock.call().launch_service(mock.ANY),
|
|
||||||
mock.call().wait()])
|
|
||||||
|
|
||||||
def test_run_completes_single_pass(self):
|
|
||||||
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
|
|
||||||
with mock.patch.object(bgp_dr, 'sync_state') as sync_state:
|
|
||||||
bgp_dr.run()
|
|
||||||
self.assertIsNotNone(len(sync_state.mock_calls))
|
|
||||||
|
|
||||||
def test_after_start(self):
|
|
||||||
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
|
|
||||||
with mock.patch.object(bgp_dr, 'sync_state') as sync_state:
|
|
||||||
bgp_dr.after_start()
|
|
||||||
self.assertIsNotNone(len(sync_state.mock_calls))
|
|
||||||
|
|
||||||
def _test_sync_state_helper(self, bgp_speaker_list=None,
|
|
||||||
cached_info=None,
|
|
||||||
safe_configure_call_count=0,
|
|
||||||
sync_bgp_speaker_call_count=0,
|
|
||||||
remove_bgp_speaker_call_count=0,
|
|
||||||
remove_bgp_speaker_ids=None,
|
|
||||||
added_bgp_speakers=None,
|
|
||||||
synced_bgp_speakers=None):
|
|
||||||
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
|
|
||||||
|
|
||||||
attrs_to_mock = dict(
|
|
||||||
[(a, mock.MagicMock())
|
|
||||||
for a in ['plugin_rpc', 'sync_bgp_speaker',
|
|
||||||
'safe_configure_dragent_for_bgp_speaker',
|
|
||||||
'remove_bgp_speaker_from_dragent']])
|
|
||||||
|
|
||||||
with mock.patch.multiple(bgp_dr, **attrs_to_mock):
|
|
||||||
if not cached_info:
|
|
||||||
cached_info = {}
|
|
||||||
if not added_bgp_speakers:
|
|
||||||
added_bgp_speakers = []
|
|
||||||
if not remove_bgp_speaker_ids:
|
|
||||||
remove_bgp_speaker_ids = []
|
|
||||||
if not synced_bgp_speakers:
|
|
||||||
synced_bgp_speakers = []
|
|
||||||
|
|
||||||
bgp_dr.plugin_rpc.get_bgp_speakers.return_value = bgp_speaker_list
|
|
||||||
bgp_dr.cache.cache = cached_info
|
|
||||||
bgp_dr.cache.clear_cache = mock.Mock()
|
|
||||||
bgp_dr.sync_state(mock.ANY)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
remove_bgp_speaker_call_count,
|
|
||||||
bgp_dr.remove_bgp_speaker_from_dragent.call_count)
|
|
||||||
|
|
||||||
if remove_bgp_speaker_call_count:
|
|
||||||
expected_calls = [mock.call(bgp_speaker_id)
|
|
||||||
for bgp_speaker_id in remove_bgp_speaker_ids]
|
|
||||||
bgp_dr.remove_bgp_speaker_from_dragent.assert_has_calls(
|
|
||||||
expected_calls)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
safe_configure_call_count,
|
|
||||||
bgp_dr.safe_configure_dragent_for_bgp_speaker.call_count)
|
|
||||||
|
|
||||||
if safe_configure_call_count:
|
|
||||||
expected_calls = [mock.call(bgp_speaker)
|
|
||||||
for bgp_speaker in added_bgp_speakers]
|
|
||||||
bgp_dr.safe_configure_dragent_for_bgp_speaker.assert_has_calls(
|
|
||||||
expected_calls)
|
|
||||||
|
|
||||||
self.assertEqual(sync_bgp_speaker_call_count,
|
|
||||||
bgp_dr.sync_bgp_speaker.call_count)
|
|
||||||
|
|
||||||
if sync_bgp_speaker_call_count:
|
|
||||||
expected_calls = [mock.call(bgp_speaker)
|
|
||||||
for bgp_speaker in synced_bgp_speakers]
|
|
||||||
bgp_dr.sync_bgp_speaker.assert_has_calls(expected_calls)
|
|
||||||
|
|
||||||
def test_sync_state_bgp_speaker_added(self):
|
|
||||||
bgp_speaker_list = [{'id': 'foo-id',
|
|
||||||
'local_as': 12345,
|
|
||||||
'peers': [],
|
|
||||||
'advertised_routes': []}]
|
|
||||||
self._test_sync_state_helper(bgp_speaker_list=bgp_speaker_list,
|
|
||||||
safe_configure_call_count=1,
|
|
||||||
added_bgp_speakers=bgp_speaker_list)
|
|
||||||
|
|
||||||
def test_sync_state_bgp_speaker_deleted(self):
|
|
||||||
bgp_speaker_list = []
|
|
||||||
cached_bgp_speaker = {'id': 'foo-id',
|
|
||||||
'local_as': 12345,
|
|
||||||
'peers': ['peer-1'],
|
|
||||||
'advertised_routes': []}
|
|
||||||
cached_info = {'foo-id': cached_bgp_speaker}
|
|
||||||
self._test_sync_state_helper(bgp_speaker_list=bgp_speaker_list,
|
|
||||||
cached_info=cached_info,
|
|
||||||
remove_bgp_speaker_call_count=1,
|
|
||||||
remove_bgp_speaker_ids=['foo-id'])
|
|
||||||
|
|
||||||
def test_sync_state_added_and_deleted(self):
|
|
||||||
bgp_speaker_list = [{'id': 'foo-id',
|
|
||||||
'local_as': 12345,
|
|
||||||
'peers': [],
|
|
||||||
'advertised_routes': []}]
|
|
||||||
cached_bgp_speaker = {'bgp_speaker': {'local_as': 12345},
|
|
||||||
'peers': ['peer-1'],
|
|
||||||
'advertised_routes': []}
|
|
||||||
cached_info = {'bar-id': cached_bgp_speaker}
|
|
||||||
|
|
||||||
self._test_sync_state_helper(bgp_speaker_list=bgp_speaker_list,
|
|
||||||
cached_info=cached_info,
|
|
||||||
remove_bgp_speaker_call_count=1,
|
|
||||||
remove_bgp_speaker_ids=['bar-id'],
|
|
||||||
safe_configure_call_count=1,
|
|
||||||
added_bgp_speakers=bgp_speaker_list)
|
|
||||||
|
|
||||||
def test_sync_state_added_and_synced(self):
|
|
||||||
bgp_speaker_list = [{'id': 'foo-id',
|
|
||||||
'local_as': 12345,
|
|
||||||
'peers': [],
|
|
||||||
'advertised_routes': []},
|
|
||||||
{'id': 'bar-id', 'peers': ['peer-2'],
|
|
||||||
'advertised_routes': []},
|
|
||||||
{'id': 'temp-id', 'peers': ['temp-1'],
|
|
||||||
'advertised_routes': []}]
|
|
||||||
|
|
||||||
cached_bgp_speaker = {'id': 'bar-id', 'bgp_speaker': {'id': 'bar-id'},
|
|
||||||
'peers': ['peer-1'],
|
|
||||||
'advertised_routes': []}
|
|
||||||
cached_bgp_speaker_2 = {'id': 'temp-id',
|
|
||||||
'bgp_speaker': {'id': 'temp-id'},
|
|
||||||
'peers': ['temp-1'],
|
|
||||||
'advertised_routes': []}
|
|
||||||
cached_info = {'bar-id': cached_bgp_speaker,
|
|
||||||
'temp-id': cached_bgp_speaker_2}
|
|
||||||
|
|
||||||
self._test_sync_state_helper(bgp_speaker_list=bgp_speaker_list,
|
|
||||||
cached_info=cached_info,
|
|
||||||
safe_configure_call_count=1,
|
|
||||||
added_bgp_speakers=[bgp_speaker_list[0]],
|
|
||||||
sync_bgp_speaker_call_count=2,
|
|
||||||
synced_bgp_speakers=[bgp_speaker_list[1],
|
|
||||||
bgp_speaker_list[2]]
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_sync_state_added_synced_and_removed(self):
|
|
||||||
bgp_speaker_list = [{'id': 'foo-id',
|
|
||||||
'local_as': 12345,
|
|
||||||
'peers': [],
|
|
||||||
'advertised_routes': []},
|
|
||||||
{'id': 'bar-id', 'peers': ['peer-2'],
|
|
||||||
'advertised_routes': []}]
|
|
||||||
cached_bgp_speaker = {'id': 'bar-id',
|
|
||||||
'bgp_speaker': {'id': 'bar-id'},
|
|
||||||
'peers': ['peer-1'],
|
|
||||||
'advertised_routes': []}
|
|
||||||
cached_bgp_speaker_2 = {'id': 'temp-id',
|
|
||||||
'bgp_speaker': {'id': 'temp-id'},
|
|
||||||
'peers': ['temp-1'],
|
|
||||||
'advertised_routes': []}
|
|
||||||
cached_info = {'bar-id': cached_bgp_speaker,
|
|
||||||
'temp-id': cached_bgp_speaker_2}
|
|
||||||
|
|
||||||
self._test_sync_state_helper(bgp_speaker_list=bgp_speaker_list,
|
|
||||||
cached_info=cached_info,
|
|
||||||
remove_bgp_speaker_call_count=1,
|
|
||||||
remove_bgp_speaker_ids=['temp-id'],
|
|
||||||
safe_configure_call_count=1,
|
|
||||||
added_bgp_speakers=[bgp_speaker_list[0]],
|
|
||||||
sync_bgp_speaker_call_count=1,
|
|
||||||
synced_bgp_speakers=[bgp_speaker_list[1]])
|
|
||||||
|
|
||||||
def _test_sync_bgp_speaker_helper(self, bgp_speaker, cached_info=None,
|
|
||||||
remove_bgp_peer_call_count=0,
|
|
||||||
removed_bgp_peer_ip_list=None,
|
|
||||||
withdraw_route_call_count=0,
|
|
||||||
withdraw_routes_list=None,
|
|
||||||
add_bgp_peers_called=False,
|
|
||||||
advertise_routes_called=False):
|
|
||||||
if not cached_info:
|
|
||||||
cached_info = {}
|
|
||||||
if not removed_bgp_peer_ip_list:
|
|
||||||
removed_bgp_peer_ip_list = []
|
|
||||||
if not withdraw_routes_list:
|
|
||||||
withdraw_routes_list = []
|
|
||||||
|
|
||||||
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
|
|
||||||
|
|
||||||
attrs_to_mock = dict(
|
|
||||||
[(a, mock.MagicMock())
|
|
||||||
for a in ['remove_bgp_peer_from_bgp_speaker',
|
|
||||||
'add_bgp_peers_to_bgp_speaker',
|
|
||||||
'advertise_routes_via_bgp_speaker',
|
|
||||||
'withdraw_route_via_bgp_speaker']])
|
|
||||||
|
|
||||||
with mock.patch.multiple(bgp_dr, **attrs_to_mock):
|
|
||||||
bgp_dr.cache.cache = cached_info
|
|
||||||
bgp_dr.sync_bgp_speaker(bgp_speaker)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
remove_bgp_peer_call_count,
|
|
||||||
bgp_dr.remove_bgp_peer_from_bgp_speaker.call_count)
|
|
||||||
|
|
||||||
if remove_bgp_peer_call_count:
|
|
||||||
expected_calls = [mock.call(bgp_speaker['id'], peer_ip)
|
|
||||||
for peer_ip in removed_bgp_peer_ip_list]
|
|
||||||
bgp_dr.remove_bgp_peer_from_bgp_speaker.assert_has_calls(
|
|
||||||
expected_calls)
|
|
||||||
|
|
||||||
self.assertEqual(add_bgp_peers_called,
|
|
||||||
bgp_dr.add_bgp_peers_to_bgp_speaker.called)
|
|
||||||
|
|
||||||
if add_bgp_peers_called:
|
|
||||||
bgp_dr.add_bgp_peers_to_bgp_speaker.assert_called_with(
|
|
||||||
bgp_speaker)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
withdraw_route_call_count,
|
|
||||||
bgp_dr.withdraw_route_via_bgp_speaker.call_count)
|
|
||||||
|
|
||||||
if withdraw_route_call_count:
|
|
||||||
expected_calls = [mock.call(bgp_speaker['id'], 12345, route)
|
|
||||||
for route in withdraw_routes_list]
|
|
||||||
bgp_dr.withdraw_route_via_bgp_speaker.assert_has_calls(
|
|
||||||
expected_calls)
|
|
||||||
|
|
||||||
self.assertEqual(advertise_routes_called,
|
|
||||||
bgp_dr.advertise_routes_via_bgp_speaker.called)
|
|
||||||
|
|
||||||
if advertise_routes_called:
|
|
||||||
bgp_dr.advertise_routes_via_bgp_speaker.assert_called_with(
|
|
||||||
bgp_speaker)
|
|
||||||
|
|
||||||
def test_sync_bgp_speaker_bgp_peers_updated(self):
|
|
||||||
peers = [{'id': 'peer-1', 'peer_ip': '1.1.1.1'},
|
|
||||||
{'id': 'peer-2', 'peer_ip': '2.2.2.2'}]
|
|
||||||
bgp_speaker = {'id': 'foo-id',
|
|
||||||
'local_as': 12345,
|
|
||||||
'peers': peers,
|
|
||||||
'advertised_routes': []}
|
|
||||||
|
|
||||||
cached_peers = {'1.1.1.1': {'id': 'peer-2', 'peer_ip': '1.1.1.1'},
|
|
||||||
'3.3.3.3': {'id': 'peer-3', 'peer_ip': '3.3.3.3'}}
|
|
||||||
|
|
||||||
cached_bgp_speaker = {'foo-id': {'bgp_speaker': {'local_as': 12345},
|
|
||||||
'peers': cached_peers,
|
|
||||||
'advertised_routes': []}}
|
|
||||||
self._test_sync_bgp_speaker_helper(
|
|
||||||
bgp_speaker, cached_info=cached_bgp_speaker,
|
|
||||||
remove_bgp_peer_call_count=1,
|
|
||||||
removed_bgp_peer_ip_list=['3.3.3.3'],
|
|
||||||
add_bgp_peers_called=True,
|
|
||||||
advertise_routes_called=False)
|
|
||||||
|
|
||||||
def test_sync_bgp_speaker_routes_updated(self):
|
|
||||||
adv_routes = [{'destination': '10.0.0.0/24', 'next_hop': '1.1.1.1'},
|
|
||||||
{'destination': '20.0.0.0/24', 'next_hop': '2.2.2.2'}]
|
|
||||||
bgp_speaker = {'id': 'foo-id',
|
|
||||||
'local_as': 12345,
|
|
||||||
'peers': {},
|
|
||||||
'advertised_routes': adv_routes}
|
|
||||||
|
|
||||||
cached_adv_routes = [{'destination': '20.0.0.0/24',
|
|
||||||
'next_hop': '2.2.2.2'},
|
|
||||||
{'destination': '30.0.0.0/24',
|
|
||||||
'next_hop': '3.3.3.3'}]
|
|
||||||
|
|
||||||
cached_bgp_speaker = {
|
|
||||||
'foo-id': {'bgp_speaker': {'local_as': 12345},
|
|
||||||
'peers': {},
|
|
||||||
'advertised_routes': cached_adv_routes}}
|
|
||||||
|
|
||||||
self._test_sync_bgp_speaker_helper(
|
|
||||||
bgp_speaker, cached_info=cached_bgp_speaker,
|
|
||||||
withdraw_route_call_count=1,
|
|
||||||
withdraw_routes_list=[cached_adv_routes[1]],
|
|
||||||
add_bgp_peers_called=False,
|
|
||||||
advertise_routes_called=True)
|
|
||||||
|
|
||||||
def test_sync_bgp_speaker_peers_routes_added(self):
|
|
||||||
peers = [{'id': 'peer-1', 'peer_ip': '1.1.1.1'},
|
|
||||||
{'id': 'peer-2', 'peer_ip': '2.2.2.2'}]
|
|
||||||
adv_routes = [{'destination': '10.0.0.0/24',
|
|
||||||
'next_hop': '1.1.1.1'},
|
|
||||||
{'destination': '20.0.0.0/24',
|
|
||||||
'next_hop': '2.2.2.2'}]
|
|
||||||
bgp_speaker = {'id': 'foo-id',
|
|
||||||
'local_as': 12345,
|
|
||||||
'peers': peers,
|
|
||||||
'advertised_routes': adv_routes}
|
|
||||||
|
|
||||||
cached_bgp_speaker = {
|
|
||||||
'foo-id': {'bgp_speaker': {'local_as': 12345},
|
|
||||||
'peers': {},
|
|
||||||
'advertised_routes': []}}
|
|
||||||
|
|
||||||
self._test_sync_bgp_speaker_helper(
|
|
||||||
bgp_speaker, cached_info=cached_bgp_speaker,
|
|
||||||
add_bgp_peers_called=True,
|
|
||||||
advertise_routes_called=True)
|
|
||||||
|
|
||||||
def test_sync_state_plugin_error(self):
|
|
||||||
with mock.patch(BGP_PLUGIN) as plug:
|
|
||||||
mock_plugin = mock.Mock()
|
|
||||||
mock_plugin.get_bgp_speakers.side_effect = Exception
|
|
||||||
plug.return_value = mock_plugin
|
|
||||||
|
|
||||||
with mock.patch.object(bgp_dragent.LOG, 'error') as log:
|
|
||||||
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
|
|
||||||
with mock.patch.object(bgp_dr,
|
|
||||||
'schedule_full_resync') as schedule_full_resync:
|
|
||||||
bgp_dr.sync_state(mock.ANY)
|
|
||||||
|
|
||||||
self.assertTrue(log.called)
|
|
||||||
self.assertTrue(schedule_full_resync.called)
|
|
||||||
|
|
||||||
def test_periodic_resync(self):
|
|
||||||
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
|
|
||||||
with mock.patch.object(bgp_dr,
|
|
||||||
'_periodic_resync_helper') as resync_helper:
|
|
||||||
bgp_dr.periodic_resync(self.context)
|
|
||||||
self.assertTrue(resync_helper.called)
|
|
||||||
|
|
||||||
def test_periodic_resync_helper(self):
|
|
||||||
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
|
|
||||||
bgp_dr.schedule_resync('foo reason', 'foo-id')
|
|
||||||
with mock.patch.object(bgp_dr, 'sync_state') as sync_state:
|
|
||||||
sync_state.side_effect = RuntimeError
|
|
||||||
with testtools.ExpectedException(RuntimeError):
|
|
||||||
bgp_dr._periodic_resync_helper(self.context)
|
|
||||||
self.assertTrue(sync_state.called)
|
|
||||||
self.assertEqual(len(bgp_dr.needs_resync_reasons), 0)
|
|
||||||
|
|
||||||
def _test_add_bgp_peer_helper(self, bgp_speaker_id,
|
|
||||||
bgp_peer, cached_bgp_speaker,
|
|
||||||
put_bgp_peer_called=True):
|
|
||||||
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
|
|
||||||
|
|
||||||
bgp_dr.cache.cache = cached_bgp_speaker
|
|
||||||
with mock.patch.object(
|
|
||||||
bgp_dr.cache, 'put_bgp_peer') as mock_put_bgp_peer:
|
|
||||||
bgp_dr.add_bgp_peer_to_bgp_speaker('foo-id', 12345, bgp_peer)
|
|
||||||
if put_bgp_peer_called:
|
|
||||||
mock_put_bgp_peer.assert_called_once_with(
|
|
||||||
bgp_speaker_id, bgp_peer)
|
|
||||||
else:
|
|
||||||
self.assertFalse(mock_put_bgp_peer.called)
|
|
||||||
|
|
||||||
def test_add_bgp_peer_not_cached(self):
|
|
||||||
bgp_peer = {'peer_ip': '1.1.1.1', 'remote_as': 34567,
|
|
||||||
'auth_type': 'md5', 'password': 'abc'}
|
|
||||||
cached_bgp_speaker = {'foo-id': {'bgp_speaker': {'local_as': 12345},
|
|
||||||
'peers': {},
|
|
||||||
'advertised_routes': []}}
|
|
||||||
|
|
||||||
self._test_add_bgp_peer_helper('foo-id', bgp_peer, cached_bgp_speaker)
|
|
||||||
|
|
||||||
def test_add_bgp_peer_already_cached(self):
|
|
||||||
bgp_peer = {'peer_ip': '1.1.1.1', 'remote_as': 34567,
|
|
||||||
'auth_type': 'md5', 'password': 'abc'}
|
|
||||||
cached_peers = {'1.1.1.1': {'peer_ip': '1.1.1.1', 'remote_as': 34567}}
|
|
||||||
cached_bgp_speaker = {'foo-id': {'bgp_speaker': {'local_as': 12345},
|
|
||||||
'peers': cached_peers,
|
|
||||||
'advertised_routes': []}}
|
|
||||||
|
|
||||||
self._test_add_bgp_peer_helper('foo-id', bgp_peer, cached_bgp_speaker,
|
|
||||||
put_bgp_peer_called=False)
|
|
||||||
|
|
||||||
def _test_advertise_route_helper(self, bgp_speaker_id,
|
|
||||||
route, cached_bgp_speaker,
|
|
||||||
put_adv_route_called=True):
|
|
||||||
bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
|
|
||||||
|
|
||||||
bgp_dr.cache.cache = cached_bgp_speaker
|
|
||||||
with mock.patch.object(
|
|
||||||
bgp_dr.cache, 'put_adv_route') as mock_put_adv_route:
|
|
||||||
bgp_dr.advertise_route_via_bgp_speaker(bgp_speaker_id, 12345,
|
|
||||||
route)
|
|
||||||
if put_adv_route_called:
|
|
||||||
mock_put_adv_route.assert_called_once_with(
|
|
||||||
bgp_speaker_id, route)
|
|
||||||
else:
|
|
||||||
self.assertFalse(mock_put_adv_route.called)
|
|
||||||
|
|
||||||
def test_advertise_route_helper_not_cached(self):
|
|
||||||
route = {'destination': '10.0.0.0/24', 'next_hop': '1.1.1.1'}
|
|
||||||
cached_bgp_speaker = {'foo-id': {'bgp_speaker': {'local_as': 12345},
|
|
||||||
'peers': {},
|
|
||||||
'advertised_routes': []}}
|
|
||||||
|
|
||||||
self._test_advertise_route_helper('foo-id', route, cached_bgp_speaker,
|
|
||||||
put_adv_route_called=True)
|
|
||||||
|
|
||||||
def test_advertise_route_helper_already_cached(self):
|
|
||||||
route = {'destination': '10.0.0.0/24', 'next_hop': '1.1.1.1'}
|
|
||||||
cached_bgp_speaker = {'foo-id': {'bgp_speaker': {'local_as': 12345},
|
|
||||||
'peers': {},
|
|
||||||
'advertised_routes': [route]}}
|
|
||||||
|
|
||||||
self._test_advertise_route_helper('foo-id', route, cached_bgp_speaker,
|
|
||||||
put_adv_route_called=False)
|
|
||||||
|
|
||||||
|
|
||||||
class TestBgpDrAgentEventHandler(base.BaseTestCase):
|
|
||||||
|
|
||||||
cache_cls = 'neutron.services.bgp.agent.bgp_dragent.BgpSpeakerCache'
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestBgpDrAgentEventHandler, self).setUp()
|
|
||||||
cfg.CONF.register_opts(bgp_config.BGP_DRIVER_OPTS, 'BGP')
|
|
||||||
cfg.CONF.register_opts(bgp_config.BGP_PROTO_CONFIG_OPTS, 'BGP')
|
|
||||||
|
|
||||||
mock_log_p = mock.patch.object(bgp_dragent, 'LOG')
|
|
||||||
self.mock_log = mock_log_p.start()
|
|
||||||
|
|
||||||
self.plugin_p = mock.patch(BGP_PLUGIN)
|
|
||||||
plugin_cls = self.plugin_p.start()
|
|
||||||
self.plugin = mock.Mock()
|
|
||||||
plugin_cls.return_value = self.plugin
|
|
||||||
|
|
||||||
self.cache_p = mock.patch(self.cache_cls)
|
|
||||||
cache_cls = self.cache_p.start()
|
|
||||||
self.cache = mock.Mock()
|
|
||||||
cache_cls.return_value = self.cache
|
|
||||||
|
|
||||||
self.driver_cls_p = mock.patch(
|
|
||||||
'neutron.services.bgp.agent.bgp_dragent.importutils.import_class')
|
|
||||||
self.driver_cls = self.driver_cls_p.start()
|
|
||||||
|
|
||||||
self.bgp_dr = bgp_dragent.BgpDrAgent(HOSTNAME)
|
|
||||||
self.schedule_full_resync_p = mock.patch.object(
|
|
||||||
self.bgp_dr, 'schedule_full_resync')
|
|
||||||
self.schedule_full_resync = self.schedule_full_resync_p.start()
|
|
||||||
self.context = mock.Mock()
|
|
||||||
|
|
||||||
def test_bgp_speaker_create_end(self):
|
|
||||||
payload = {'bgp_speaker': {'id': FAKE_BGPSPEAKER_UUID}}
|
|
||||||
|
|
||||||
with mock.patch.object(self.bgp_dr,
|
|
||||||
'add_bgp_speaker_helper') as enable:
|
|
||||||
self.bgp_dr.bgp_speaker_create_end(None, payload)
|
|
||||||
enable.assert_called_once_with(FAKE_BGP_SPEAKER['id'])
|
|
||||||
|
|
||||||
def test_bgp_peer_association_end(self):
|
|
||||||
payload = {'bgp_peer': {'speaker_id': FAKE_BGPSPEAKER_UUID,
|
|
||||||
'peer_id': FAKE_BGPPEER_UUID}}
|
|
||||||
|
|
||||||
with mock.patch.object(self.bgp_dr,
|
|
||||||
'add_bgp_peer_helper') as enable:
|
|
||||||
self.bgp_dr.bgp_peer_association_end(None, payload)
|
|
||||||
enable.assert_called_once_with(FAKE_BGP_SPEAKER['id'],
|
|
||||||
FAKE_BGP_PEER['id'])
|
|
||||||
|
|
||||||
def test_route_advertisement_end(self):
|
|
||||||
routes = [{'destination': '2.2.2.2/32', 'next_hop': '3.3.3.3'},
|
|
||||||
{'destination': '4.4.4.4/32', 'next_hop': '5.5.5.5'}]
|
|
||||||
payload = {'advertise_routes': {'speaker_id': FAKE_BGPSPEAKER_UUID,
|
|
||||||
'routes': routes}}
|
|
||||||
|
|
||||||
expected_calls = [mock.call(FAKE_BGP_SPEAKER['id'], routes)]
|
|
||||||
|
|
||||||
with mock.patch.object(self.bgp_dr,
|
|
||||||
'add_routes_helper') as enable:
|
|
||||||
self.bgp_dr.bgp_routes_advertisement_end(None, payload)
|
|
||||||
enable.assert_has_calls(expected_calls)
|
|
||||||
|
|
||||||
def test_add_bgp_speaker_helper(self):
|
|
||||||
self.plugin.get_bgp_speaker_info.return_value = FAKE_BGP_SPEAKER
|
|
||||||
add_bs_p = mock.patch.object(self.bgp_dr,
|
|
||||||
'add_bgp_speaker_on_dragent')
|
|
||||||
add_bs = add_bs_p.start()
|
|
||||||
self.bgp_dr.add_bgp_speaker_helper(FAKE_BGP_SPEAKER['id'])
|
|
||||||
self.plugin.assert_has_calls([
|
|
||||||
mock.call.get_bgp_speaker_info(mock.ANY,
|
|
||||||
FAKE_BGP_SPEAKER['id'])])
|
|
||||||
add_bs.assert_called_once_with(FAKE_BGP_SPEAKER)
|
|
||||||
|
|
||||||
def test_add_bgp_peer_helper(self):
|
|
||||||
self.plugin.get_bgp_peer_info.return_value = FAKE_BGP_PEER
|
|
||||||
add_bp_p = mock.patch.object(self.bgp_dr,
|
|
||||||
'add_bgp_peer_to_bgp_speaker')
|
|
||||||
add_bp = add_bp_p.start()
|
|
||||||
self.bgp_dr.add_bgp_peer_helper(FAKE_BGP_SPEAKER['id'],
|
|
||||||
FAKE_BGP_PEER['id'])
|
|
||||||
self.plugin.assert_has_calls([
|
|
||||||
mock.call.get_bgp_peer_info(mock.ANY,
|
|
||||||
FAKE_BGP_PEER['id'])])
|
|
||||||
self.assertEqual(1, add_bp.call_count)
|
|
||||||
|
|
||||||
def test_add_routes_helper(self):
|
|
||||||
add_rt_p = mock.patch.object(self.bgp_dr,
|
|
||||||
'advertise_route_via_bgp_speaker')
|
|
||||||
add_bp = add_rt_p.start()
|
|
||||||
self.bgp_dr.add_routes_helper(FAKE_BGP_SPEAKER['id'], FAKE_ROUTES)
|
|
||||||
self.assertEqual(1, add_bp.call_count)
|
|
||||||
|
|
||||||
def test_bgp_speaker_remove_end(self):
|
|
||||||
payload = {'bgp_speaker': {'id': FAKE_BGPSPEAKER_UUID}}
|
|
||||||
|
|
||||||
with mock.patch.object(self.bgp_dr,
|
|
||||||
'remove_bgp_speaker_from_dragent') as disable:
|
|
||||||
self.bgp_dr.bgp_speaker_remove_end(None, payload)
|
|
||||||
disable.assert_called_once_with(FAKE_BGP_SPEAKER['id'])
|
|
||||||
|
|
||||||
def test_bgp_peer_disassociation_end(self):
|
|
||||||
payload = {'bgp_peer': {'speaker_id': FAKE_BGPSPEAKER_UUID,
|
|
||||||
'peer_ip': '1.1.1.1'}}
|
|
||||||
|
|
||||||
with mock.patch.object(self.bgp_dr,
|
|
||||||
'remove_bgp_peer_from_bgp_speaker') as disable:
|
|
||||||
self.bgp_dr.bgp_peer_disassociation_end(None, payload)
|
|
||||||
disable.assert_called_once_with(FAKE_BGPSPEAKER_UUID,
|
|
||||||
FAKE_BGP_PEER['peer_ip'])
|
|
||||||
|
|
||||||
def test_bgp_routes_withdrawal_end(self):
|
|
||||||
withdraw_routes = [{'destination': '2.2.2.2/32'},
|
|
||||||
{'destination': '3.3.3.3/32'}]
|
|
||||||
payload = {'withdraw_routes': {'speaker_id': FAKE_BGPSPEAKER_UUID,
|
|
||||||
'routes': withdraw_routes}}
|
|
||||||
|
|
||||||
expected_calls = [mock.call(FAKE_BGP_SPEAKER['id'], withdraw_routes)]
|
|
||||||
|
|
||||||
with mock.patch.object(self.bgp_dr,
|
|
||||||
'withdraw_routes_helper') as disable:
|
|
||||||
self.bgp_dr.bgp_routes_withdrawal_end(None, payload)
|
|
||||||
disable.assert_has_calls(expected_calls)
|
|
||||||
|
|
||||||
|
|
||||||
class TestBGPSpeakerCache(base.BaseTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestBGPSpeakerCache, self).setUp()
|
|
||||||
self.expected_cache = {FAKE_BGP_SPEAKER['id']:
|
|
||||||
{'bgp_speaker': FAKE_BGP_SPEAKER,
|
|
||||||
'peers': {},
|
|
||||||
'advertised_routes': []}}
|
|
||||||
self.bs_cache = bgp_dragent.BgpSpeakerCache()
|
|
||||||
|
|
||||||
def test_put_bgp_speaker(self):
|
|
||||||
self.bs_cache.put_bgp_speaker(FAKE_BGP_SPEAKER)
|
|
||||||
self.assertEqual(self.expected_cache, self.bs_cache.cache)
|
|
||||||
|
|
||||||
def test_put_bgp_speaker_existing(self):
|
|
||||||
prev_bs_info = {'id': 'foo-id'}
|
|
||||||
with mock.patch.object(self.bs_cache,
|
|
||||||
'remove_bgp_speaker_by_id') as remove:
|
|
||||||
self.bs_cache.cache[FAKE_BGP_SPEAKER['id']] = prev_bs_info
|
|
||||||
self.bs_cache.put_bgp_speaker(FAKE_BGP_SPEAKER)
|
|
||||||
remove.assert_called_once_with(prev_bs_info)
|
|
||||||
self.assertEqual(self.expected_cache, self.bs_cache.cache)
|
|
||||||
|
|
||||||
def remove_bgp_speaker_by_id(self):
|
|
||||||
self.bs_cache.put_bgp_speaker(FAKE_BGP_SPEAKER)
|
|
||||||
self.assertEqual(1, len(self.bs_cache.cache))
|
|
||||||
self.bs_cache.remove_bgp_speaker_by_id(FAKE_BGP_SPEAKER['id'])
|
|
||||||
self.assertEqual(0, len(self.bs_cache.cache))
|
|
||||||
|
|
||||||
def test_get_bgp_speaker_by_id(self):
|
|
||||||
self.bs_cache.put_bgp_speaker(FAKE_BGP_SPEAKER)
|
|
||||||
|
|
||||||
self.assertEqual(
|
|
||||||
FAKE_BGP_SPEAKER,
|
|
||||||
self.bs_cache.get_bgp_speaker_by_id(FAKE_BGP_SPEAKER['id']))
|
|
||||||
|
|
||||||
def test_get_bgp_speaker_ids(self):
|
|
||||||
self.bs_cache.put_bgp_speaker(FAKE_BGP_SPEAKER)
|
|
||||||
|
|
||||||
self.assertEqual([FAKE_BGP_SPEAKER['id']],
|
|
||||||
list(self.bs_cache.get_bgp_speaker_ids()))
|
|
||||||
|
|
||||||
def _test_bgp_peer_helper(self, remove=False):
|
|
||||||
self.bs_cache.put_bgp_speaker(FAKE_BGP_SPEAKER)
|
|
||||||
self.bs_cache.put_bgp_peer(FAKE_BGP_SPEAKER['id'], FAKE_BGP_PEER)
|
|
||||||
expected_cache = copy.deepcopy(self.expected_cache)
|
|
||||||
expected_cache[FAKE_BGP_SPEAKER['id']]['peers'] = {
|
|
||||||
FAKE_BGP_PEER['peer_ip']: FAKE_BGP_PEER}
|
|
||||||
self.assertEqual(expected_cache, self.bs_cache.cache)
|
|
||||||
|
|
||||||
if remove:
|
|
||||||
self.bs_cache.remove_bgp_peer_by_ip(FAKE_BGP_SPEAKER['id'],
|
|
||||||
'foo-ip')
|
|
||||||
self.assertEqual(expected_cache, self.bs_cache.cache)
|
|
||||||
|
|
||||||
self.bs_cache.remove_bgp_peer_by_ip(FAKE_BGP_SPEAKER['id'],
|
|
||||||
FAKE_BGP_PEER['peer_ip'])
|
|
||||||
self.assertEqual(self.expected_cache, self.bs_cache.cache)
|
|
||||||
|
|
||||||
def test_put_bgp_peer(self):
|
|
||||||
self._test_bgp_peer_helper()
|
|
||||||
|
|
||||||
def test_remove_bgp_peer(self):
|
|
||||||
self._test_bgp_peer_helper(remove=True)
|
|
||||||
|
|
||||||
def _test_bgp_speaker_adv_route_helper(self, remove=False):
|
|
||||||
self.bs_cache.put_bgp_speaker(FAKE_BGP_SPEAKER)
|
|
||||||
self.bs_cache.put_adv_route(FAKE_BGP_SPEAKER['id'], FAKE_ROUTE)
|
|
||||||
expected_cache = copy.deepcopy(self.expected_cache)
|
|
||||||
expected_cache[FAKE_BGP_SPEAKER['id']]['advertised_routes'].append(
|
|
||||||
FAKE_ROUTE)
|
|
||||||
self.assertEqual(expected_cache, self.bs_cache.cache)
|
|
||||||
|
|
||||||
fake_route_2 = copy.deepcopy(FAKE_ROUTE)
|
|
||||||
fake_route_2['destination'] = '4.4.4.4/32'
|
|
||||||
self.bs_cache.put_adv_route(FAKE_BGP_SPEAKER['id'], fake_route_2)
|
|
||||||
|
|
||||||
expected_cache[FAKE_BGP_SPEAKER['id']]['advertised_routes'].append(
|
|
||||||
fake_route_2)
|
|
||||||
self.assertEqual(expected_cache, self.bs_cache.cache)
|
|
||||||
|
|
||||||
if remove:
|
|
||||||
self.bs_cache.remove_adv_route(FAKE_BGP_SPEAKER['id'],
|
|
||||||
fake_route_2)
|
|
||||||
expected_cache[FAKE_BGP_SPEAKER['id']]['advertised_routes'] = (
|
|
||||||
[FAKE_ROUTE])
|
|
||||||
self.assertEqual(expected_cache, self.bs_cache.cache)
|
|
||||||
|
|
||||||
self.bs_cache.remove_adv_route(FAKE_BGP_SPEAKER['id'],
|
|
||||||
FAKE_ROUTE)
|
|
||||||
self.assertEqual(self.expected_cache, self.bs_cache.cache)
|
|
||||||
|
|
||||||
def test_put_bgp_speaker_adv_route(self):
|
|
||||||
self._test_bgp_speaker_adv_route_helper()
|
|
||||||
|
|
||||||
def test_remove_bgp_speaker_adv_route(self):
|
|
||||||
self._test_bgp_speaker_adv_route_helper(remove=True)
|
|
||||||
|
|
||||||
def test_is_bgp_speaker_adv_route_present(self):
|
|
||||||
self._test_bgp_speaker_adv_route_helper()
|
|
||||||
self.assertTrue(self.bs_cache.is_route_advertised(
|
|
||||||
FAKE_BGP_SPEAKER['id'], FAKE_ROUTE))
|
|
||||||
self.assertFalse(self.bs_cache.is_route_advertised(
|
|
||||||
FAKE_BGP_SPEAKER['id'], {'destination': 'foo-destination',
|
|
||||||
'next_hop': 'foo-next-hop'}))
|
|
@ -1,250 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from oslo_config import cfg
|
|
||||||
from ryu.services.protocols.bgp import bgpspeaker
|
|
||||||
from ryu.services.protocols.bgp.rtconf.neighbors import CONNECT_MODE_ACTIVE
|
|
||||||
|
|
||||||
from neutron.services.bgp.agent import config as bgp_config
|
|
||||||
from neutron.services.bgp.driver import exceptions as bgp_driver_exc
|
|
||||||
from neutron.services.bgp.driver.ryu import driver as ryu_driver
|
|
||||||
from neutron.tests import base
|
|
||||||
|
|
||||||
# Test variables for BGP Speaker
|
|
||||||
FAKE_LOCAL_AS1 = 12345
|
|
||||||
FAKE_LOCAL_AS2 = 23456
|
|
||||||
FAKE_ROUTER_ID = '1.1.1.1'
|
|
||||||
|
|
||||||
# Test variables for BGP Peer
|
|
||||||
FAKE_PEER_AS = 45678
|
|
||||||
FAKE_PEER_IP = '2.2.2.5'
|
|
||||||
FAKE_AUTH_TYPE = 'md5'
|
|
||||||
FAKE_PEER_PASSWORD = 'awesome'
|
|
||||||
|
|
||||||
# Test variables for Route
|
|
||||||
FAKE_ROUTE = '2.2.2.0/24'
|
|
||||||
FAKE_NEXTHOP = '5.5.5.5'
|
|
||||||
|
|
||||||
|
|
||||||
class TestRyuBgpDriver(base.BaseTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestRyuBgpDriver, self).setUp()
|
|
||||||
cfg.CONF.register_opts(bgp_config.BGP_PROTO_CONFIG_OPTS, 'BGP')
|
|
||||||
cfg.CONF.set_override('bgp_router_id', FAKE_ROUTER_ID, 'BGP')
|
|
||||||
self.ryu_bgp_driver = ryu_driver.RyuBgpDriver(cfg.CONF.BGP)
|
|
||||||
mock_ryu_speaker_p = mock.patch.object(bgpspeaker, 'BGPSpeaker')
|
|
||||||
self.mock_ryu_speaker = mock_ryu_speaker_p.start()
|
|
||||||
|
|
||||||
def test_add_new_bgp_speaker(self):
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertEqual(1,
|
|
||||||
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
|
||||||
self.mock_ryu_speaker.assert_called_once_with(
|
|
||||||
as_number=FAKE_LOCAL_AS1, router_id=FAKE_ROUTER_ID,
|
|
||||||
bgp_server_port=0,
|
|
||||||
best_path_change_handler=ryu_driver.best_path_change_cb,
|
|
||||||
peer_down_handler=ryu_driver.bgp_peer_down_cb,
|
|
||||||
peer_up_handler=ryu_driver.bgp_peer_up_cb)
|
|
||||||
|
|
||||||
def test_remove_bgp_speaker(self):
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertEqual(1,
|
|
||||||
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
|
||||||
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.ryu_bgp_driver.delete_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertEqual(0,
|
|
||||||
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
|
||||||
self.assertEqual(1, speaker.shutdown.call_count)
|
|
||||||
|
|
||||||
def test_add_bgp_peer_without_password(self):
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertEqual(1,
|
|
||||||
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
|
||||||
self.ryu_bgp_driver.add_bgp_peer(FAKE_LOCAL_AS1,
|
|
||||||
FAKE_PEER_IP,
|
|
||||||
FAKE_PEER_AS)
|
|
||||||
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
speaker.neighbor_add.assert_called_once_with(
|
|
||||||
address=FAKE_PEER_IP,
|
|
||||||
remote_as=FAKE_PEER_AS,
|
|
||||||
password=None,
|
|
||||||
connect_mode=CONNECT_MODE_ACTIVE)
|
|
||||||
|
|
||||||
def test_add_bgp_peer_with_password(self):
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertEqual(1,
|
|
||||||
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
|
||||||
self.ryu_bgp_driver.add_bgp_peer(FAKE_LOCAL_AS1,
|
|
||||||
FAKE_PEER_IP,
|
|
||||||
FAKE_PEER_AS,
|
|
||||||
FAKE_AUTH_TYPE,
|
|
||||||
FAKE_PEER_PASSWORD)
|
|
||||||
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
speaker.neighbor_add.assert_called_once_with(
|
|
||||||
address=FAKE_PEER_IP,
|
|
||||||
remote_as=FAKE_PEER_AS,
|
|
||||||
password=FAKE_PEER_PASSWORD,
|
|
||||||
connect_mode=CONNECT_MODE_ACTIVE)
|
|
||||||
|
|
||||||
def test_remove_bgp_peer(self):
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertEqual(1,
|
|
||||||
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
|
||||||
self.ryu_bgp_driver.delete_bgp_peer(FAKE_LOCAL_AS1, FAKE_PEER_IP)
|
|
||||||
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
speaker.neighbor_del.assert_called_once_with(address=FAKE_PEER_IP)
|
|
||||||
|
|
||||||
def test_advertise_route(self):
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertEqual(1,
|
|
||||||
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
|
||||||
self.ryu_bgp_driver.advertise_route(FAKE_LOCAL_AS1,
|
|
||||||
FAKE_ROUTE,
|
|
||||||
FAKE_NEXTHOP)
|
|
||||||
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
speaker.prefix_add.assert_called_once_with(prefix=FAKE_ROUTE,
|
|
||||||
next_hop=FAKE_NEXTHOP)
|
|
||||||
|
|
||||||
def test_withdraw_route(self):
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertEqual(1,
|
|
||||||
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
|
||||||
self.ryu_bgp_driver.withdraw_route(FAKE_LOCAL_AS1, FAKE_ROUTE)
|
|
||||||
speaker = self.ryu_bgp_driver.cache.get_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
speaker.prefix_del.assert_called_once_with(prefix=FAKE_ROUTE)
|
|
||||||
|
|
||||||
def test_add_same_bgp_speakers_twice(self):
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertRaises(bgp_driver_exc.BgpSpeakerAlreadyScheduled,
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker, FAKE_LOCAL_AS1)
|
|
||||||
|
|
||||||
def test_add_different_bgp_speakers_when_one_already_added(self):
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertRaises(bgp_driver_exc.BgpSpeakerMaxScheduled,
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker,
|
|
||||||
FAKE_LOCAL_AS2)
|
|
||||||
|
|
||||||
def test_add_bgp_speaker_with_invalid_asnum_paramtype(self):
|
|
||||||
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker, '12345')
|
|
||||||
|
|
||||||
def test_add_bgp_speaker_with_invalid_asnum_range(self):
|
|
||||||
self.assertRaises(bgp_driver_exc.InvalidParamRange,
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker, -1)
|
|
||||||
self.assertRaises(bgp_driver_exc.InvalidParamRange,
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker, 65536)
|
|
||||||
|
|
||||||
def test_add_bgp_peer_with_invalid_paramtype(self):
|
|
||||||
# Test with an invalid asnum data-type
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
|
||||||
self.ryu_bgp_driver.add_bgp_peer,
|
|
||||||
FAKE_LOCAL_AS1, FAKE_PEER_IP, '12345')
|
|
||||||
# Test with an invalid auth-type and an invalid password
|
|
||||||
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
|
||||||
self.ryu_bgp_driver.add_bgp_peer,
|
|
||||||
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS,
|
|
||||||
'sha-1', 1234)
|
|
||||||
# Test with an invalid auth-type and a valid password
|
|
||||||
self.assertRaises(bgp_driver_exc.InvaildAuthType,
|
|
||||||
self.ryu_bgp_driver.add_bgp_peer,
|
|
||||||
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS,
|
|
||||||
'hmac-md5', FAKE_PEER_PASSWORD)
|
|
||||||
# Test with none auth-type and a valid password
|
|
||||||
self.assertRaises(bgp_driver_exc.InvaildAuthType,
|
|
||||||
self.ryu_bgp_driver.add_bgp_peer,
|
|
||||||
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS,
|
|
||||||
'none', FAKE_PEER_PASSWORD)
|
|
||||||
# Test with none auth-type and an invalid password
|
|
||||||
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
|
||||||
self.ryu_bgp_driver.add_bgp_peer,
|
|
||||||
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS,
|
|
||||||
'none', 1234)
|
|
||||||
# Test with a valid auth-type and no password
|
|
||||||
self.assertRaises(bgp_driver_exc.PasswordNotSpecified,
|
|
||||||
self.ryu_bgp_driver.add_bgp_peer,
|
|
||||||
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS,
|
|
||||||
FAKE_AUTH_TYPE, None)
|
|
||||||
|
|
||||||
def test_add_bgp_peer_with_invalid_asnum_range(self):
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertRaises(bgp_driver_exc.InvalidParamRange,
|
|
||||||
self.ryu_bgp_driver.add_bgp_peer,
|
|
||||||
FAKE_LOCAL_AS1, FAKE_PEER_IP, -1)
|
|
||||||
self.assertRaises(bgp_driver_exc.InvalidParamRange,
|
|
||||||
self.ryu_bgp_driver.add_bgp_peer,
|
|
||||||
FAKE_LOCAL_AS1, FAKE_PEER_IP, 65536)
|
|
||||||
|
|
||||||
def test_add_bgp_peer_without_adding_speaker(self):
|
|
||||||
self.assertRaises(bgp_driver_exc.BgpSpeakerNotAdded,
|
|
||||||
self.ryu_bgp_driver.add_bgp_peer,
|
|
||||||
FAKE_LOCAL_AS1, FAKE_PEER_IP, FAKE_PEER_AS)
|
|
||||||
|
|
||||||
def test_remove_bgp_peer_with_invalid_paramtype(self):
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
|
||||||
self.ryu_bgp_driver.delete_bgp_peer,
|
|
||||||
FAKE_LOCAL_AS1, 12345)
|
|
||||||
|
|
||||||
def test_remove_bgp_peer_without_adding_speaker(self):
|
|
||||||
self.assertRaises(bgp_driver_exc.BgpSpeakerNotAdded,
|
|
||||||
self.ryu_bgp_driver.delete_bgp_peer,
|
|
||||||
FAKE_LOCAL_AS1, FAKE_PEER_IP)
|
|
||||||
|
|
||||||
def test_advertise_route_with_invalid_paramtype(self):
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
|
||||||
self.ryu_bgp_driver.advertise_route,
|
|
||||||
FAKE_LOCAL_AS1, 12345, FAKE_NEXTHOP)
|
|
||||||
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
|
||||||
self.ryu_bgp_driver.advertise_route,
|
|
||||||
FAKE_LOCAL_AS1, FAKE_ROUTE, 12345)
|
|
||||||
|
|
||||||
def test_advertise_route_without_adding_speaker(self):
|
|
||||||
self.assertRaises(bgp_driver_exc.BgpSpeakerNotAdded,
|
|
||||||
self.ryu_bgp_driver.advertise_route,
|
|
||||||
FAKE_LOCAL_AS1, FAKE_ROUTE, FAKE_NEXTHOP)
|
|
||||||
|
|
||||||
def test_withdraw_route_with_invalid_paramtype(self):
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
|
||||||
self.ryu_bgp_driver.withdraw_route,
|
|
||||||
FAKE_LOCAL_AS1, 12345)
|
|
||||||
self.assertRaises(bgp_driver_exc.InvalidParamType,
|
|
||||||
self.ryu_bgp_driver.withdraw_route,
|
|
||||||
FAKE_LOCAL_AS1, 12345)
|
|
||||||
|
|
||||||
def test_withdraw_route_without_adding_speaker(self):
|
|
||||||
self.assertRaises(bgp_driver_exc.BgpSpeakerNotAdded,
|
|
||||||
self.ryu_bgp_driver.withdraw_route,
|
|
||||||
FAKE_LOCAL_AS1, FAKE_ROUTE)
|
|
||||||
|
|
||||||
def test_add_multiple_bgp_speakers(self):
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertEqual(1,
|
|
||||||
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
|
||||||
self.assertRaises(bgp_driver_exc.BgpSpeakerMaxScheduled,
|
|
||||||
self.ryu_bgp_driver.add_bgp_speaker,
|
|
||||||
FAKE_LOCAL_AS2)
|
|
||||||
self.assertRaises(bgp_driver_exc.BgpSpeakerNotAdded,
|
|
||||||
self.ryu_bgp_driver.delete_bgp_speaker,
|
|
||||||
FAKE_LOCAL_AS2)
|
|
||||||
self.assertEqual(1,
|
|
||||||
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
|
||||||
self.ryu_bgp_driver.delete_bgp_speaker(FAKE_LOCAL_AS1)
|
|
||||||
self.assertEqual(0,
|
|
||||||
self.ryu_bgp_driver.cache.get_hosted_bgp_speakers_count())
|
|
@ -1,48 +0,0 @@
|
|||||||
# 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.
|
|
||||||
|
|
||||||
from neutron.services.bgp.driver import utils
|
|
||||||
from neutron.tests import base
|
|
||||||
|
|
||||||
FAKE_LOCAL_AS = 12345
|
|
||||||
FAKE_RYU_SPEAKER = {}
|
|
||||||
|
|
||||||
|
|
||||||
class TestBgpMultiSpeakerCache(base.BaseTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestBgpMultiSpeakerCache, self).setUp()
|
|
||||||
self.expected_cache = {FAKE_LOCAL_AS: FAKE_RYU_SPEAKER}
|
|
||||||
self.bs_cache = utils.BgpMultiSpeakerCache()
|
|
||||||
|
|
||||||
def test_put_bgp_speaker(self):
|
|
||||||
self.bs_cache.put_bgp_speaker(FAKE_LOCAL_AS, FAKE_RYU_SPEAKER)
|
|
||||||
self.assertEqual(self.expected_cache, self.bs_cache.cache)
|
|
||||||
|
|
||||||
def test_remove_bgp_speaker(self):
|
|
||||||
self.bs_cache.put_bgp_speaker(FAKE_LOCAL_AS, FAKE_RYU_SPEAKER)
|
|
||||||
self.assertEqual(1, len(self.bs_cache.cache))
|
|
||||||
self.bs_cache.remove_bgp_speaker(FAKE_LOCAL_AS)
|
|
||||||
self.assertEqual(0, len(self.bs_cache.cache))
|
|
||||||
|
|
||||||
def test_get_bgp_speaker(self):
|
|
||||||
self.bs_cache.put_bgp_speaker(FAKE_LOCAL_AS, FAKE_RYU_SPEAKER)
|
|
||||||
self.assertEqual(
|
|
||||||
FAKE_RYU_SPEAKER,
|
|
||||||
self.bs_cache.get_bgp_speaker(FAKE_LOCAL_AS))
|
|
||||||
|
|
||||||
def test_get_hosted_bgp_speakers_count(self):
|
|
||||||
self.bs_cache.put_bgp_speaker(FAKE_LOCAL_AS, FAKE_RYU_SPEAKER)
|
|
||||||
self.assertEqual(1, self.bs_cache.get_hosted_bgp_speakers_count())
|
|
@ -1,224 +0,0 @@
|
|||||||
# 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))
|
|
@ -40,7 +40,6 @@ scripts =
|
|||||||
|
|
||||||
[entry_points]
|
[entry_points]
|
||||||
console_scripts =
|
console_scripts =
|
||||||
neutron-bgp-dragent = neutron.cmd.eventlet.agents.bgp_dragent:main
|
|
||||||
neutron-db-manage = neutron.db.migration.cli:main
|
neutron-db-manage = neutron.db.migration.cli:main
|
||||||
neutron-debug = neutron.debug.shell:main
|
neutron-debug = neutron.debug.shell:main
|
||||||
neutron-dhcp-agent = neutron.cmd.eventlet.agents.dhcp:main
|
neutron-dhcp-agent = neutron.cmd.eventlet.agents.dhcp:main
|
||||||
@ -77,7 +76,6 @@ neutron.service_plugins =
|
|||||||
neutron.services.loadbalancer.plugin.LoadBalancerPlugin = neutron_lbaas.services.loadbalancer.plugin:LoadBalancerPlugin
|
neutron.services.loadbalancer.plugin.LoadBalancerPlugin = neutron_lbaas.services.loadbalancer.plugin:LoadBalancerPlugin
|
||||||
neutron.services.vpn.plugin.VPNDriverPlugin = neutron_vpnaas.services.vpn.plugin:VPNDriverPlugin
|
neutron.services.vpn.plugin.VPNDriverPlugin = neutron_vpnaas.services.vpn.plugin:VPNDriverPlugin
|
||||||
qos = neutron.services.qos.qos_plugin:QoSPlugin
|
qos = neutron.services.qos.qos_plugin:QoSPlugin
|
||||||
bgp = neutron.services.bgp.bgp_plugin:BgpPlugin
|
|
||||||
tag = neutron.services.tag.tag_plugin:TagPlugin
|
tag = neutron.services.tag.tag_plugin:TagPlugin
|
||||||
flavors = neutron.services.flavors.flavors_plugin:FlavorsPlugin
|
flavors = neutron.services.flavors.flavors_plugin:FlavorsPlugin
|
||||||
auto_allocate = neutron.services.auto_allocate.plugin:Plugin
|
auto_allocate = neutron.services.auto_allocate.plugin:Plugin
|
||||||
@ -134,7 +132,6 @@ oslo.config.opts =
|
|||||||
neutron = neutron.opts:list_opts
|
neutron = neutron.opts:list_opts
|
||||||
neutron.agent = neutron.opts:list_agent_opts
|
neutron.agent = neutron.opts:list_agent_opts
|
||||||
neutron.base.agent = neutron.opts:list_base_agent_opts
|
neutron.base.agent = neutron.opts:list_base_agent_opts
|
||||||
neutron.bgp.agent = neutron.services.bgp.common.opts:list_bgp_agent_opts
|
|
||||||
neutron.db = neutron.opts:list_db_opts
|
neutron.db = neutron.opts:list_db_opts
|
||||||
neutron.dhcp.agent = neutron.opts:list_dhcp_agent_opts
|
neutron.dhcp.agent = neutron.opts:list_dhcp_agent_opts
|
||||||
neutron.extensions = neutron.opts:list_extension_opts
|
neutron.extensions = neutron.opts:list_extension_opts
|
||||||
|
Loading…
Reference in New Issue
Block a user