Add BGP Dynamic Routing DB Model and Basic CRUD
This patch enables basic CRUD on BGP dynamic routing entities bgp_speaker and bgp_peer, as well as bgp_speaker-bgp_peer and bgp_speaker-network bindings. An admin user can create BgpSpeakers and configure peering entities (BgpPeers) for BgpSpeakers. BgpSpeaker to BgpPeer association is n-to-n. An admin user can also associate networks with BgpSpeakers. Relationship between BgpSpeaker and Network is 1-to-n. This patch provides BGP-related functionality only to the admin users. Partially-Implements: blueprint bgp-dynamic-routing Co-Authored-By: Ryan Tidwell <ryan.tidwell@hpe.com> Co-Authored-By: Jaume Devesa <devvesa@gmail.com> Co-Authored-By: vikram.choudhary <vikram.choudhary@huawei.com> Change-Id: I2412c1689683da9d7ec884a4cea506d4eed99453
This commit is contained in:
parent
dbe49ce353
commit
9d1d6a08db
7
devstack/lib/bgp
Normal file
7
devstack/lib/bgp
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
function configure_bgp_service_plugin {
|
||||||
|
_neutron_service_plugin_class_add "bgp"
|
||||||
|
}
|
||||||
|
|
||||||
|
function configure_bgp {
|
||||||
|
configure_bgp_service_plugin
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
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
|
||||||
@ -15,6 +16,9 @@ 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
|
||||||
;;
|
;;
|
||||||
post-config)
|
post-config)
|
||||||
if is_service_enabled q-agt; then
|
if is_service_enabled q-agt; then
|
||||||
|
@ -205,5 +205,23 @@
|
|||||||
"create_flavor_service_profile": "rule:admin_only",
|
"create_flavor_service_profile": "rule:admin_only",
|
||||||
"delete_flavor_service_profile": "rule:admin_only",
|
"delete_flavor_service_profile": "rule:admin_only",
|
||||||
"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",
|
||||||
|
"remove_gateway_network": "rule:admin_only",
|
||||||
|
|
||||||
|
"get_advertised_routes":"rule:admin_only"
|
||||||
}
|
}
|
||||||
|
380
neutron/db/bgp_db.py
Normal file
380
neutron/db/bgp_db.py
Normal file
@ -0,0 +1,380 @@
|
|||||||
|
# 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 oslo_db import exception as oslo_db_exc
|
||||||
|
from oslo_log import log as logging
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import orm
|
||||||
|
from sqlalchemy.orm import exc as sa_exc
|
||||||
|
|
||||||
|
from neutron.api.v2 import attributes as attr
|
||||||
|
from neutron.common import exceptions as n_exc
|
||||||
|
from neutron.db import common_db_mixin as common_db
|
||||||
|
from neutron.db import model_base
|
||||||
|
from neutron.db import models_v2
|
||||||
|
from neutron.extensions import bgp as bgp_ext
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class BgpSpeakerPeerBinding(model_base.BASEV2):
|
||||||
|
|
||||||
|
"""Represents a mapping between BGP speaker and BGP peer"""
|
||||||
|
|
||||||
|
__tablename__ = 'bgp_speaker_peer_bindings'
|
||||||
|
|
||||||
|
bgp_speaker_id = sa.Column(sa.String(length=36),
|
||||||
|
sa.ForeignKey('bgp_speakers.id',
|
||||||
|
ondelete='CASCADE'),
|
||||||
|
nullable=False,
|
||||||
|
primary_key=True)
|
||||||
|
bgp_peer_id = sa.Column(sa.String(length=36),
|
||||||
|
sa.ForeignKey('bgp_peers.id',
|
||||||
|
ondelete='CASCADE'),
|
||||||
|
nullable=False,
|
||||||
|
primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
class BgpSpeakerNetworkBinding(model_base.BASEV2):
|
||||||
|
|
||||||
|
"""Represents a mapping between a network and BGP speaker"""
|
||||||
|
|
||||||
|
__tablename__ = 'bgp_speaker_network_bindings'
|
||||||
|
|
||||||
|
bgp_speaker_id = sa.Column(sa.String(length=36),
|
||||||
|
sa.ForeignKey('bgp_speakers.id',
|
||||||
|
ondelete='CASCADE'),
|
||||||
|
nullable=False,
|
||||||
|
primary_key=True)
|
||||||
|
network_id = sa.Column(sa.String(length=36),
|
||||||
|
sa.ForeignKey('networks.id',
|
||||||
|
ondelete='CASCADE'),
|
||||||
|
nullable=False,
|
||||||
|
primary_key=True)
|
||||||
|
ip_version = sa.Column(sa.Integer, nullable=False, autoincrement=False,
|
||||||
|
primary_key=True)
|
||||||
|
|
||||||
|
|
||||||
|
class BgpSpeaker(model_base.BASEV2,
|
||||||
|
model_base.HasId,
|
||||||
|
model_base.HasTenant):
|
||||||
|
|
||||||
|
"""Represents a BGP speaker"""
|
||||||
|
|
||||||
|
__tablename__ = 'bgp_speakers'
|
||||||
|
|
||||||
|
name = sa.Column(sa.String(attr.NAME_MAX_LEN), nullable=False)
|
||||||
|
local_as = sa.Column(sa.Integer, nullable=False, autoincrement=False)
|
||||||
|
advertise_floating_ip_host_routes = sa.Column(sa.Boolean, nullable=False)
|
||||||
|
advertise_tenant_networks = sa.Column(sa.Boolean, nullable=False)
|
||||||
|
peers = orm.relationship(BgpSpeakerPeerBinding,
|
||||||
|
backref='bgp_speaker_peer_bindings',
|
||||||
|
cascade='all, delete, delete-orphan',
|
||||||
|
lazy='joined')
|
||||||
|
networks = orm.relationship(BgpSpeakerNetworkBinding,
|
||||||
|
backref='bgp_speaker_network_bindings',
|
||||||
|
cascade='all, delete, delete-orphan',
|
||||||
|
lazy='joined')
|
||||||
|
ip_version = sa.Column(sa.Integer, nullable=False, autoincrement=False)
|
||||||
|
|
||||||
|
|
||||||
|
class BgpPeer(model_base.BASEV2,
|
||||||
|
model_base.HasId,
|
||||||
|
model_base.HasTenant):
|
||||||
|
|
||||||
|
"""Represents a BGP routing peer."""
|
||||||
|
|
||||||
|
__tablename__ = 'bgp_peers'
|
||||||
|
|
||||||
|
name = sa.Column(sa.String(attr.NAME_MAX_LEN), nullable=False)
|
||||||
|
peer_ip = sa.Column(sa.String(64),
|
||||||
|
nullable=False)
|
||||||
|
remote_as = sa.Column(sa.Integer, nullable=False, autoincrement=False)
|
||||||
|
auth_type = sa.Column(sa.String(16), nullable=False)
|
||||||
|
password = sa.Column(sa.String(255), nullable=True)
|
||||||
|
|
||||||
|
|
||||||
|
class BgpDbMixin(common_db.CommonDbMixin):
|
||||||
|
|
||||||
|
def create_bgp_speaker(self, context, bgp_speaker):
|
||||||
|
uuid = uuidutils.generate_uuid()
|
||||||
|
self._save_bgp_speaker(context, bgp_speaker, uuid)
|
||||||
|
return self.get_bgp_speaker(context, uuid)
|
||||||
|
|
||||||
|
def get_bgp_speakers(self, context, filters=None, fields=None,
|
||||||
|
sorts=None, limit=None, marker=None,
|
||||||
|
page_reverse=False):
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
return self._get_collection(context, BgpSpeaker,
|
||||||
|
self._make_bgp_speaker_dict,
|
||||||
|
filters=filters, fields=fields,
|
||||||
|
sorts=sorts, limit=limit,
|
||||||
|
page_reverse=page_reverse)
|
||||||
|
|
||||||
|
def get_bgp_speaker(self, context, bgp_speaker_id, fields=None):
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
bgp_speaker = self._get_bgp_speaker(context, bgp_speaker_id)
|
||||||
|
return self._make_bgp_speaker_dict(bgp_speaker, fields)
|
||||||
|
|
||||||
|
def update_bgp_speaker(self, context, bgp_speaker_id, bgp_speaker):
|
||||||
|
bp = bgp_speaker[bgp_ext.BGP_SPEAKER_BODY_KEY_NAME]
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
bgp_speaker_db = self._get_bgp_speaker(context, bgp_speaker_id)
|
||||||
|
bgp_speaker_db.update(bp)
|
||||||
|
|
||||||
|
bgp_speaker_dict = self._make_bgp_speaker_dict(bgp_speaker_db)
|
||||||
|
return bgp_speaker_dict
|
||||||
|
|
||||||
|
def _save_bgp_speaker(self, context, bgp_speaker, uuid):
|
||||||
|
ri = bgp_speaker[bgp_ext.BGP_SPEAKER_BODY_KEY_NAME]
|
||||||
|
ri['tenant_id'] = context.tenant_id
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
res_keys = ['local_as', 'tenant_id', 'name', 'ip_version',
|
||||||
|
'advertise_floating_ip_host_routes',
|
||||||
|
'advertise_tenant_networks']
|
||||||
|
res = dict((k, ri[k]) for k in res_keys)
|
||||||
|
res['id'] = uuid
|
||||||
|
bgp_speaker_db = BgpSpeaker(**res)
|
||||||
|
context.session.add(bgp_speaker_db)
|
||||||
|
|
||||||
|
def add_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info):
|
||||||
|
bgp_peer_id = self._get_id_for(bgp_peer_info, 'bgp_peer_id')
|
||||||
|
self._save_bgp_speaker_peer_binding(context,
|
||||||
|
bgp_speaker_id,
|
||||||
|
bgp_peer_id)
|
||||||
|
return {'bgp_peer_id': bgp_peer_id}
|
||||||
|
|
||||||
|
def remove_bgp_peer(self, context, bgp_speaker_id, bgp_peer_info):
|
||||||
|
bgp_peer_id = self._get_id_for(bgp_peer_info, 'bgp_peer_id')
|
||||||
|
self._remove_bgp_speaker_peer_binding(context,
|
||||||
|
bgp_speaker_id,
|
||||||
|
bgp_peer_id)
|
||||||
|
return {'bgp_peer_id': bgp_peer_id}
|
||||||
|
|
||||||
|
def add_gateway_network(self, context, bgp_speaker_id, network_info):
|
||||||
|
network_id = self._get_id_for(network_info, 'network_id')
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
try:
|
||||||
|
self._save_bgp_speaker_network_binding(context,
|
||||||
|
bgp_speaker_id,
|
||||||
|
network_id)
|
||||||
|
except oslo_db_exc.DBDuplicateEntry:
|
||||||
|
raise bgp_ext.BgpSpeakerNetworkBindingError(
|
||||||
|
network_id=network_id,
|
||||||
|
bgp_speaker_id=bgp_speaker_id)
|
||||||
|
return {'network_id': network_id}
|
||||||
|
|
||||||
|
def remove_gateway_network(self, context, bgp_speaker_id, network_info):
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
network_id = self._get_id_for(network_info, 'network_id')
|
||||||
|
self._remove_bgp_speaker_network_binding(context,
|
||||||
|
bgp_speaker_id,
|
||||||
|
network_id)
|
||||||
|
return {'network_id': network_id}
|
||||||
|
|
||||||
|
def delete_bgp_speaker(self, context, bgp_speaker_id):
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
bgp_speaker_db = self._get_bgp_speaker(context, bgp_speaker_id)
|
||||||
|
context.session.delete(bgp_speaker_db)
|
||||||
|
|
||||||
|
def create_bgp_peer(self, context, bgp_peer):
|
||||||
|
ri = bgp_peer[bgp_ext.BGP_PEER_BODY_KEY_NAME]
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
res_keys = ['tenant_id', 'name', 'remote_as', 'peer_ip',
|
||||||
|
'auth_type', 'password']
|
||||||
|
res = dict((k, ri[k]) for k in res_keys)
|
||||||
|
res['id'] = uuidutils.generate_uuid()
|
||||||
|
bgp_peer_db = BgpPeer(**res)
|
||||||
|
context.session.add(bgp_peer_db)
|
||||||
|
peer = self._make_bgp_peer_dict(bgp_peer_db)
|
||||||
|
peer.pop('password')
|
||||||
|
return peer
|
||||||
|
|
||||||
|
def get_bgp_peers(self, context, fields=None, filters=None, sorts=None,
|
||||||
|
limit=None, marker=None, page_reverse=False):
|
||||||
|
return self._get_collection(context, BgpPeer,
|
||||||
|
self._make_bgp_peer_dict,
|
||||||
|
filters=filters, fields=fields,
|
||||||
|
sorts=sorts, limit=limit,
|
||||||
|
page_reverse=page_reverse)
|
||||||
|
|
||||||
|
def get_bgp_peer(self, context, bgp_peer_id, fields=None):
|
||||||
|
bgp_peer_db = self._get_bgp_peer(context, bgp_peer_id)
|
||||||
|
return self._make_bgp_peer_dict(bgp_peer_db, fields=fields)
|
||||||
|
|
||||||
|
def delete_bgp_peer(self, context, bgp_peer_id):
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
bgp_peer_db = self._get_bgp_peer(context, bgp_peer_id)
|
||||||
|
context.session.delete(bgp_peer_db)
|
||||||
|
|
||||||
|
def update_bgp_peer(self, context, bgp_peer_id, bgp_peer):
|
||||||
|
bp = bgp_peer[bgp_ext.BGP_PEER_BODY_KEY_NAME]
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
bgp_peer_db = self._get_bgp_peer(context, bgp_peer_id)
|
||||||
|
if ((bp['password'] is not None) and
|
||||||
|
(bgp_peer_db['auth_type'] == 'none')):
|
||||||
|
raise bgp_ext.BgpPeerNotAuthenticated(bgp_peer_id=bgp_peer_id)
|
||||||
|
bgp_peer_db.update(bp)
|
||||||
|
|
||||||
|
bgp_peer_dict = self._make_bgp_peer_dict(bgp_peer_db)
|
||||||
|
return bgp_peer_dict
|
||||||
|
|
||||||
|
def _get_bgp_speaker(self, context, bgp_speaker_id):
|
||||||
|
try:
|
||||||
|
return self._get_by_id(context, BgpSpeaker,
|
||||||
|
bgp_speaker_id)
|
||||||
|
except sa_exc.NoResultFound:
|
||||||
|
raise bgp_ext.BgpSpeakerNotFound(id=bgp_speaker_id)
|
||||||
|
|
||||||
|
def get_advertised_routes(self, context, bgp_speaker_id):
|
||||||
|
return self._make_advertised_routes_dict([])
|
||||||
|
|
||||||
|
def _get_id_for(self, resource, id_name):
|
||||||
|
try:
|
||||||
|
return resource.get(id_name)
|
||||||
|
except AttributeError:
|
||||||
|
msg = _("%s must be specified") % id_name
|
||||||
|
raise n_exc.BadRequest(resource=bgp_ext.BGP_SPEAKER_RESOURCE_NAME,
|
||||||
|
msg=msg)
|
||||||
|
|
||||||
|
def _get_bgp_peers_by_bgp_speaker_binding(self, context, bgp_speaker_id):
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
query = context.session.query(BgpPeer)
|
||||||
|
query = query.filter(
|
||||||
|
BgpSpeakerPeerBinding.bgp_speaker_id == bgp_speaker_id,
|
||||||
|
BgpSpeakerPeerBinding.bgp_peer_id == BgpPeer.id)
|
||||||
|
return query.all()
|
||||||
|
|
||||||
|
def _save_bgp_speaker_peer_binding(self, context, bgp_speaker_id,
|
||||||
|
bgp_peer_id):
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
try:
|
||||||
|
bgp_speaker = self._get_by_id(context, BgpSpeaker,
|
||||||
|
bgp_speaker_id)
|
||||||
|
except sa_exc.NoResultFound:
|
||||||
|
raise bgp_ext.BgpSpeakerNotFound(id=bgp_speaker_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
bgp_peer = self._get_by_id(context, BgpPeer,
|
||||||
|
bgp_peer_id)
|
||||||
|
except sa_exc.NoResultFound:
|
||||||
|
raise bgp_ext.BgpPeerNotFound(id=bgp_peer_id)
|
||||||
|
|
||||||
|
peers = self._get_bgp_peers_by_bgp_speaker_binding(context,
|
||||||
|
bgp_speaker_id)
|
||||||
|
self._validate_peer_ips(bgp_speaker_id, peers, bgp_peer)
|
||||||
|
binding = BgpSpeakerPeerBinding(bgp_speaker_id=bgp_speaker.id,
|
||||||
|
bgp_peer_id=bgp_peer.id)
|
||||||
|
context.session.add(binding)
|
||||||
|
|
||||||
|
def _validate_peer_ips(self, bgp_speaker_id, current_peers, new_peer):
|
||||||
|
for peer in current_peers:
|
||||||
|
if peer.peer_ip == new_peer.peer_ip:
|
||||||
|
raise bgp_ext.DuplicateBgpPeerIpException(
|
||||||
|
bgp_peer_id=new_peer.id,
|
||||||
|
peer_ip=new_peer.peer_ip,
|
||||||
|
bgp_speaker_id=bgp_speaker_id)
|
||||||
|
|
||||||
|
def _remove_bgp_speaker_peer_binding(self, context, bgp_speaker_id,
|
||||||
|
bgp_peer_id):
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
|
||||||
|
try:
|
||||||
|
binding = self._get_bgp_speaker_peer_binding(context,
|
||||||
|
bgp_speaker_id,
|
||||||
|
bgp_peer_id)
|
||||||
|
except sa_exc.NoResultFound:
|
||||||
|
raise bgp_ext.BgpSpeakerPeerNotAssociated(
|
||||||
|
bgp_peer_id=bgp_peer_id,
|
||||||
|
bgp_speaker_id=bgp_speaker_id)
|
||||||
|
context.session.delete(binding)
|
||||||
|
|
||||||
|
def _save_bgp_speaker_network_binding(self,
|
||||||
|
context,
|
||||||
|
bgp_speaker_id,
|
||||||
|
network_id):
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
try:
|
||||||
|
bgp_speaker = self._get_by_id(context, BgpSpeaker,
|
||||||
|
bgp_speaker_id)
|
||||||
|
except sa_exc.NoResultFound:
|
||||||
|
raise bgp_ext.BgpSpeakerNotFound(id=bgp_speaker_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
network = self._get_by_id(context, models_v2.Network,
|
||||||
|
network_id)
|
||||||
|
except sa_exc.NoResultFound:
|
||||||
|
raise n_exc.NetworkNotFound(net_id=network_id)
|
||||||
|
|
||||||
|
binding = BgpSpeakerNetworkBinding(
|
||||||
|
bgp_speaker_id=bgp_speaker.id,
|
||||||
|
network_id=network.id,
|
||||||
|
ip_version=bgp_speaker.ip_version)
|
||||||
|
context.session.add(binding)
|
||||||
|
|
||||||
|
def _remove_bgp_speaker_network_binding(self, context,
|
||||||
|
bgp_speaker_id, network_id):
|
||||||
|
with context.session.begin(subtransactions=True):
|
||||||
|
|
||||||
|
try:
|
||||||
|
binding = self._get_bgp_speaker_network_binding(
|
||||||
|
context,
|
||||||
|
bgp_speaker_id,
|
||||||
|
network_id)
|
||||||
|
except sa_exc.NoResultFound:
|
||||||
|
raise bgp_ext.BgpSpeakerNetworkNotAssociated(
|
||||||
|
network_id=network_id,
|
||||||
|
bgp_speaker_id=bgp_speaker_id)
|
||||||
|
context.session.delete(binding)
|
||||||
|
|
||||||
|
def _make_bgp_speaker_dict(self, bgp_speaker, fields=None):
|
||||||
|
attrs = {'id', 'local_as', 'tenant_id', 'name', 'ip_version',
|
||||||
|
'advertise_floating_ip_host_routes',
|
||||||
|
'advertise_tenant_networks'}
|
||||||
|
peer_bindings = bgp_speaker['peers']
|
||||||
|
network_bindings = bgp_speaker['networks']
|
||||||
|
res = dict((k, bgp_speaker[k]) for k in attrs)
|
||||||
|
res['peers'] = [x.bgp_peer_id for x in peer_bindings]
|
||||||
|
res['networks'] = [x.network_id for x in network_bindings]
|
||||||
|
return self._fields(res, fields)
|
||||||
|
|
||||||
|
def _make_advertised_routes_dict(self, routes):
|
||||||
|
return {'advertised_routes': list(routes)}
|
||||||
|
|
||||||
|
def _get_bgp_peer(self, context, bgp_peer_id):
|
||||||
|
try:
|
||||||
|
return self._get_by_id(context, BgpPeer, bgp_peer_id)
|
||||||
|
except sa_exc.NoResultFound:
|
||||||
|
raise bgp_ext.BgpPeerNotFound(id=bgp_peer_id)
|
||||||
|
|
||||||
|
def _get_bgp_speaker_peer_binding(self, context,
|
||||||
|
bgp_speaker_id, bgp_peer_id):
|
||||||
|
query = self._model_query(context, BgpSpeakerPeerBinding)
|
||||||
|
return query.filter(
|
||||||
|
BgpSpeakerPeerBinding.bgp_speaker_id == bgp_speaker_id,
|
||||||
|
BgpSpeakerPeerBinding.bgp_peer_id == bgp_peer_id).one()
|
||||||
|
|
||||||
|
def _get_bgp_speaker_network_binding(self, context,
|
||||||
|
bgp_speaker_id, network_id):
|
||||||
|
query = self._model_query(context, BgpSpeakerNetworkBinding)
|
||||||
|
return query.filter(bgp_speaker_id == bgp_speaker_id,
|
||||||
|
network_id == network_id).one()
|
||||||
|
|
||||||
|
def _make_bgp_peer_dict(self, bgp_peer, fields=None):
|
||||||
|
attrs = ['tenant_id', 'id', 'name', 'peer_ip', 'remote_as',
|
||||||
|
'auth_type', 'password']
|
||||||
|
res = dict((k, bgp_peer[k]) for k in attrs)
|
||||||
|
return self._fields(res, fields)
|
@ -0,0 +1,105 @@
|
|||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""add dynamic routing model data
|
||||||
|
|
||||||
|
Revision ID: 15be73214821
|
||||||
|
Create Date: 2015-07-29 13:16:08.604175
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '15be73214821'
|
||||||
|
down_revision = '19f26505c74f'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'bgp_speakers',
|
||||||
|
sa.Column('id', sa.String(length=36),
|
||||||
|
nullable=False),
|
||||||
|
sa.Column('name', sa.String(length=255),
|
||||||
|
nullable=False),
|
||||||
|
sa.Column('local_as', sa.Integer, nullable=False,
|
||||||
|
autoincrement=False),
|
||||||
|
sa.Column('ip_version', sa.Integer, nullable=False,
|
||||||
|
autoincrement=False),
|
||||||
|
sa.Column('tenant_id',
|
||||||
|
sa.String(length=255),
|
||||||
|
nullable=True,
|
||||||
|
index=True),
|
||||||
|
sa.Column('advertise_floating_ip_host_routes', sa.Boolean(),
|
||||||
|
nullable=False),
|
||||||
|
sa.Column('advertise_tenant_networks', sa.Boolean(),
|
||||||
|
nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'bgp_peers',
|
||||||
|
sa.Column('id', sa.String(length=36),
|
||||||
|
nullable=False),
|
||||||
|
sa.Column('name', sa.String(length=255), nullable=False),
|
||||||
|
sa.Column('auth_type', sa.String(length=16), nullable=False),
|
||||||
|
sa.Column('password', sa.String(length=255), nullable=True),
|
||||||
|
sa.Column('peer_ip',
|
||||||
|
sa.String(length=64),
|
||||||
|
nullable=False),
|
||||||
|
sa.Column('remote_as', sa.Integer, nullable=False,
|
||||||
|
autoincrement=False),
|
||||||
|
sa.Column('tenant_id',
|
||||||
|
sa.String(length=255),
|
||||||
|
nullable=True,
|
||||||
|
index=True),
|
||||||
|
sa.PrimaryKeyConstraint('id')
|
||||||
|
)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'bgp_speaker_network_bindings',
|
||||||
|
sa.Column('bgp_speaker_id',
|
||||||
|
sa.String(length=36),
|
||||||
|
nullable=False),
|
||||||
|
sa.Column('network_id',
|
||||||
|
sa.String(length=36),
|
||||||
|
nullable=True),
|
||||||
|
sa.Column('ip_version', sa.Integer, nullable=False,
|
||||||
|
autoincrement=False),
|
||||||
|
sa.ForeignKeyConstraint(['bgp_speaker_id'],
|
||||||
|
['bgp_speakers.id'],
|
||||||
|
ondelete='CASCADE'),
|
||||||
|
sa.ForeignKeyConstraint(['network_id'],
|
||||||
|
['networks.id'],
|
||||||
|
ondelete='CASCADE'),
|
||||||
|
sa.PrimaryKeyConstraint('network_id', 'bgp_speaker_id', 'ip_version')
|
||||||
|
)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'bgp_speaker_peer_bindings',
|
||||||
|
sa.Column('bgp_speaker_id',
|
||||||
|
sa.String(length=36),
|
||||||
|
nullable=False),
|
||||||
|
sa.Column('bgp_peer_id',
|
||||||
|
sa.String(length=36),
|
||||||
|
nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['bgp_speaker_id'], ['bgp_speakers.id'],
|
||||||
|
ondelete='CASCADE'),
|
||||||
|
sa.ForeignKeyConstraint(['bgp_peer_id'], ['bgp_peers.id'],
|
||||||
|
ondelete='CASCADE'),
|
||||||
|
sa.PrimaryKeyConstraint('bgp_speaker_id', 'bgp_peer_id')
|
||||||
|
)
|
@ -25,6 +25,7 @@ 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 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
|
||||||
|
194
neutron/extensions/bgp.py
Normal file
194
neutron/extensions/bgp.py
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
# 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.api import extensions
|
||||||
|
from neutron.api.v2 import attributes as attr
|
||||||
|
from neutron.api.v2 import resource_helper as rh
|
||||||
|
from neutron.common import exceptions
|
||||||
|
|
||||||
|
BGP_EXT_ALIAS = 'bgp'
|
||||||
|
BGP_SPEAKER_RESOURCE_NAME = 'bgp-speaker'
|
||||||
|
BGP_SPEAKER_BODY_KEY_NAME = 'bgp_speaker'
|
||||||
|
BGP_PEER_BODY_KEY_NAME = 'bgp_peer'
|
||||||
|
|
||||||
|
bgp_supported_auth_types = ['none', 'md5']
|
||||||
|
|
||||||
|
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': (1, 65535)},
|
||||||
|
'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': attr.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': attr.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': (1, 65535)},
|
||||||
|
'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_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 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)
|
0
neutron/services/bgp/__init__.py
Normal file
0
neutron/services/bgp/__init__.py
Normal file
39
neutron/services/bgp/bgp_plugin.py
Normal file
39
neutron/services/bgp/bgp_plugin.py
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
# 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 neutron.db import bgp_db
|
||||||
|
from neutron.extensions import bgp as bgp_ext
|
||||||
|
from neutron.services import service_base
|
||||||
|
|
||||||
|
PLUGIN_NAME = bgp_ext.BGP_EXT_ALIAS + '_svc_plugin'
|
||||||
|
|
||||||
|
|
||||||
|
class BgpPlugin(service_base.ServicePluginBase,
|
||||||
|
bgp_db.BgpDbMixin):
|
||||||
|
|
||||||
|
supported_extension_aliases = [bgp_ext.BGP_EXT_ALIAS]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(BgpPlugin, self).__init__()
|
||||||
|
|
||||||
|
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.")
|
173
neutron/tests/api/test_bgp_speaker_extensions.py
Normal file
173
neutron/tests/api/test_bgp_speaker_extensions.py
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
# 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 tempest import config
|
||||||
|
from tempest import test
|
||||||
|
from tempest_lib import exceptions as lib_exc
|
||||||
|
|
||||||
|
from neutron.tests.api import base
|
||||||
|
from 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
|
||||||
|
def resource_setup(cls):
|
||||||
|
super(BgpSpeakerTestJSONBase, cls).resource_setup()
|
||||||
|
if not test.is_extension_enabled('bgp_speaker', 'network'):
|
||||||
|
msg = "BGP Speaker extension is not enabled."
|
||||||
|
raise cls.skipException(msg)
|
||||||
|
|
||||||
|
cls.ext_net_id = CONF.network.public_network_id
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
@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)
|
||||||
|
|
||||||
|
@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)
|
53
neutron/tests/api/test_bgp_speaker_extensions_negative.py
Normal file
53
neutron/tests/api/test_bgp_speaker_extensions_negative.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# 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 tempest_lib import exceptions as lib_exc
|
||||||
|
|
||||||
|
from neutron.tests.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', 'smoke'])
|
||||||
|
@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', 'smoke'])
|
||||||
|
@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', 'smoke'])
|
||||||
|
@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', 'smoke'])
|
||||||
|
@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')
|
@ -205,5 +205,23 @@
|
|||||||
"create_flavor_service_profile": "rule:admin_only",
|
"create_flavor_service_profile": "rule:admin_only",
|
||||||
"delete_flavor_service_profile": "rule:admin_only",
|
"delete_flavor_service_profile": "rule:admin_only",
|
||||||
"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",
|
||||||
|
"remove_gateway_network": "rule:admin_only",
|
||||||
|
|
||||||
|
"get_advertised_routes":"rule:admin_only"
|
||||||
}
|
}
|
||||||
|
@ -48,6 +48,8 @@ class NetworkClientJSON(service_client.ServiceClient):
|
|||||||
# 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': '',
|
||||||
@ -220,6 +222,109 @@ class NetworkClientJSON(service_client.ServiceClient):
|
|||||||
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 = json.dumps(update_body)
|
||||||
|
resp, body = self.put(uri, update_body)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
body = json.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 = json.dumps(update_body)
|
||||||
|
resp, body = self.put(uri, update_body)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
body = json.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 = json.dumps(update_body)
|
||||||
|
resp, body = self.put(uri, update_body)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
body = json.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 = json.dumps(update_body)
|
||||||
|
resp, body = self.put(uri, update_body)
|
||||||
|
self.expected_success(200, resp.status)
|
||||||
|
body = json.loads(body)
|
||||||
|
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]
|
||||||
|
331
neutron/tests/unit/db/test_bgp_db.py
Normal file
331
neutron/tests/unit/db/test_bgp_db.py
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
# 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 contextlib
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
from neutron.common import exceptions as n_exc
|
||||||
|
from neutron.extensions import bgp
|
||||||
|
from neutron import manager
|
||||||
|
from neutron.plugins.common import constants as p_const
|
||||||
|
from neutron.services.bgp import bgp_plugin
|
||||||
|
from neutron.tests.unit.plugins.ml2 import test_plugin
|
||||||
|
|
||||||
|
_uuid = uuidutils.generate_uuid
|
||||||
|
|
||||||
|
ADVERTISE_FIPS_KEY = 'advertise_floating_ip_host_routes'
|
||||||
|
|
||||||
|
|
||||||
|
class BgpEntityCreationMixin(object):
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def bgp_speaker(self, ip_version, local_as, name='my-speaker',
|
||||||
|
advertise_fip_host_routes=True,
|
||||||
|
advertise_tenant_networks=True,
|
||||||
|
networks=None, peers=None):
|
||||||
|
data = {'ip_version': ip_version,
|
||||||
|
ADVERTISE_FIPS_KEY: advertise_fip_host_routes,
|
||||||
|
'advertise_tenant_networks': advertise_tenant_networks,
|
||||||
|
'local_as': local_as, 'name': name}
|
||||||
|
bgp_speaker = self.bgp_plugin.create_bgp_speaker(self.context,
|
||||||
|
{'bgp_speaker': data})
|
||||||
|
bgp_speaker_id = bgp_speaker['id']
|
||||||
|
|
||||||
|
if networks:
|
||||||
|
for network_id in networks:
|
||||||
|
self.bgp_plugin.add_gateway_network(
|
||||||
|
self.context,
|
||||||
|
bgp_speaker_id,
|
||||||
|
{'network_id': network_id})
|
||||||
|
if peers:
|
||||||
|
for peer_id in peers:
|
||||||
|
self.bgp_plugin.add_bgp_peer(self.context, bgp_speaker_id,
|
||||||
|
{'bgp_peer_id': peer_id})
|
||||||
|
|
||||||
|
yield self.bgp_plugin.get_bgp_speaker(self.context, bgp_speaker_id)
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def bgp_peer(self, tenant_id=_uuid(), remote_as='4321',
|
||||||
|
peer_ip="192.168.1.1", auth_type="md5",
|
||||||
|
password="my-secret", name="my-peer"):
|
||||||
|
data = {'peer_ip': peer_ip, 'tenant_id': tenant_id,
|
||||||
|
'remote_as': remote_as, 'auth_type': auth_type,
|
||||||
|
'password': password, 'name': name}
|
||||||
|
bgp_peer = self.bgp_plugin.create_bgp_peer(self.context,
|
||||||
|
{'bgp_peer': data})
|
||||||
|
yield bgp_peer
|
||||||
|
self.bgp_plugin.delete_bgp_peer(self.context, bgp_peer['id'])
|
||||||
|
|
||||||
|
|
||||||
|
class BgpTests(test_plugin.Ml2PluginV2TestCase,
|
||||||
|
BgpEntityCreationMixin):
|
||||||
|
#FIXME(tidwellr) Lots of duplicated setup code, try to streamline
|
||||||
|
fmt = 'json'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(BgpTests, self).setUp()
|
||||||
|
self.l3plugin = manager.NeutronManager.get_service_plugins().get(
|
||||||
|
p_const.L3_ROUTER_NAT)
|
||||||
|
self.bgp_plugin = bgp_plugin.BgpPlugin()
|
||||||
|
self.plugin = manager.NeutronManager.get_plugin()
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def subnetpool_with_address_scope(self, ip_version, prefixes=None,
|
||||||
|
shared=False, admin=True,
|
||||||
|
name='test-pool', is_default_pool=False,
|
||||||
|
tenant_id=None, **kwargs):
|
||||||
|
if not tenant_id:
|
||||||
|
tenant_id = _uuid()
|
||||||
|
|
||||||
|
scope_data = {'tenant_id': tenant_id, 'ip_version': ip_version,
|
||||||
|
'shared': shared, 'name': name + '-scope'}
|
||||||
|
address_scope = self.plugin.create_address_scope(
|
||||||
|
self.context,
|
||||||
|
{'address_scope': scope_data})
|
||||||
|
address_scope_id = address_scope['id']
|
||||||
|
pool_data = {'tenant_id': tenant_id, 'shared': shared, 'name': name,
|
||||||
|
'address_scope_id': address_scope_id,
|
||||||
|
'prefixes': prefixes, 'is_default': is_default_pool}
|
||||||
|
for key in kwargs:
|
||||||
|
pool_data[key] = kwargs[key]
|
||||||
|
|
||||||
|
yield self.plugin.create_subnetpool(self.context,
|
||||||
|
{'subnetpool': pool_data})
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def floatingip_from_address_scope_assoc(self, prefixes,
|
||||||
|
address_scope_id,
|
||||||
|
ext_prefixlen=24,
|
||||||
|
int_prefixlen=24):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def test_add_duplicate_bgp_peer_ip(self):
|
||||||
|
peer_ip = '192.168.1.10'
|
||||||
|
with self.bgp_peer(peer_ip=peer_ip) as peer1,\
|
||||||
|
self.bgp_peer(peer_ip=peer_ip) as peer2,\
|
||||||
|
self.subnetpool_with_address_scope(4,
|
||||||
|
prefixes=['8.0.0.0/8']) as sp:
|
||||||
|
|
||||||
|
with self.bgp_speaker(sp['ip_version'], 1234,
|
||||||
|
peers=[peer1['id']]) as speaker:
|
||||||
|
self.assertRaises(bgp.DuplicateBgpPeerIpException,
|
||||||
|
self.bgp_plugin.add_bgp_peer,
|
||||||
|
self.context, speaker['id'],
|
||||||
|
{'bgp_peer_id': peer2['id']})
|
||||||
|
|
||||||
|
def test_bgpspeaker_create(self):
|
||||||
|
with self.subnetpool_with_address_scope(4,
|
||||||
|
prefixes=['8.0.0.0/8']) as sp:
|
||||||
|
speaker_name = 'test-speaker'
|
||||||
|
expected_values = [('ip_version', sp['ip_version']),
|
||||||
|
('name', speaker_name)]
|
||||||
|
with self.bgp_speaker(sp['ip_version'], 1234,
|
||||||
|
name=speaker_name) as bgp_speaker:
|
||||||
|
for k, v in expected_values:
|
||||||
|
self.assertEqual(v, bgp_speaker[k])
|
||||||
|
|
||||||
|
def test_bgp_speaker_list(self):
|
||||||
|
with self.subnetpool_with_address_scope(4,
|
||||||
|
prefixes=['8.0.0.0/8']) as sp1,\
|
||||||
|
self.subnetpool_with_address_scope(4,
|
||||||
|
prefixes=['9.0.0.0/8']) as sp2:
|
||||||
|
with self.bgp_speaker(sp1['ip_version'], 1234,
|
||||||
|
name='speaker1'),\
|
||||||
|
self.bgp_speaker(sp2['ip_version'], 4321,
|
||||||
|
name='speaker2'):
|
||||||
|
speakers = self.bgp_plugin.get_bgp_speakers(self.context)
|
||||||
|
self.assertEqual(2, len(speakers))
|
||||||
|
|
||||||
|
def test_bgp_speaker_update_local_as(self):
|
||||||
|
local_as_1 = 1234
|
||||||
|
local_as_2 = 4321
|
||||||
|
with self.subnetpool_with_address_scope(4,
|
||||||
|
prefixes=['8.0.0.0/8']) as sp:
|
||||||
|
with self.bgp_speaker(sp['ip_version'], local_as_1) as speaker:
|
||||||
|
self.assertEqual(local_as_1, speaker['local_as'])
|
||||||
|
new_speaker = self.bgp_plugin.update_bgp_speaker(
|
||||||
|
self.context,
|
||||||
|
speaker['id'],
|
||||||
|
{'bgp_speaker':
|
||||||
|
{'local_as': local_as_2}})
|
||||||
|
self.assertEqual(local_as_2, new_speaker['local_as'])
|
||||||
|
|
||||||
|
def test_bgp_speaker_show_non_existent(self):
|
||||||
|
self.assertRaises(bgp.BgpSpeakerNotFound,
|
||||||
|
self.bgp_plugin.get_bgp_speaker,
|
||||||
|
self.context, _uuid())
|
||||||
|
|
||||||
|
def test_create_bgp_peer(self):
|
||||||
|
args = {'tenant_id': _uuid(),
|
||||||
|
'remote_as': '1111',
|
||||||
|
'peer_ip': '10.10.10.10',
|
||||||
|
'auth_type': 'md5'}
|
||||||
|
with self.bgp_peer(tenant_id=args['tenant_id'],
|
||||||
|
remote_as=args['remote_as'],
|
||||||
|
peer_ip=args['peer_ip'],
|
||||||
|
auth_type='md5',
|
||||||
|
password='my-secret') as peer:
|
||||||
|
self.assertIsNone(peer.get('password'))
|
||||||
|
for key in args:
|
||||||
|
self.assertEqual(args[key], peer[key])
|
||||||
|
|
||||||
|
def test_bgp_peer_show_non_existent(self):
|
||||||
|
self.assertRaises(bgp.BgpPeerNotFound,
|
||||||
|
self.bgp_plugin.get_bgp_peer,
|
||||||
|
self.context,
|
||||||
|
'unreal-bgp-peer-id')
|
||||||
|
|
||||||
|
def test_associate_bgp_peer(self):
|
||||||
|
with self.bgp_peer() as peer,\
|
||||||
|
self.subnetpool_with_address_scope(4,
|
||||||
|
prefixes=['8.0.0.0/8']) as sp:
|
||||||
|
with self.bgp_speaker(sp['ip_version'], 1234) as speaker:
|
||||||
|
self.bgp_plugin.add_bgp_peer(self.context, speaker['id'],
|
||||||
|
{'bgp_peer_id': peer['id']})
|
||||||
|
new_speaker = self.bgp_plugin.get_bgp_speaker(self.context,
|
||||||
|
speaker['id'])
|
||||||
|
self.assertIn('peers', new_speaker)
|
||||||
|
self.assertIn(peer['id'], new_speaker['peers'])
|
||||||
|
self.assertEqual(1, len(new_speaker['peers']))
|
||||||
|
|
||||||
|
def test_remove_bgp_peer(self):
|
||||||
|
with self.bgp_peer() as peer,\
|
||||||
|
self.subnetpool_with_address_scope(4,
|
||||||
|
prefixes=['8.0.0.0/8']) as sp:
|
||||||
|
with self.bgp_speaker(sp['ip_version'], 1234,
|
||||||
|
peers=[peer['id']]) as speaker:
|
||||||
|
self.bgp_plugin.remove_bgp_peer(self.context, speaker['id'],
|
||||||
|
{'bgp_peer_id': peer['id']})
|
||||||
|
new_speaker = self.bgp_plugin.get_bgp_speaker(self.context,
|
||||||
|
speaker['id'])
|
||||||
|
self.assertIn('peers', new_speaker)
|
||||||
|
self.assertTrue(not new_speaker['peers'])
|
||||||
|
|
||||||
|
def test_remove_unassociated_bgp_peer(self):
|
||||||
|
with self.bgp_peer() as peer,\
|
||||||
|
self.subnetpool_with_address_scope(4,
|
||||||
|
prefixes=['8.0.0.0/8']) as sp:
|
||||||
|
with self.bgp_speaker(sp['ip_version'], 1234) as speaker:
|
||||||
|
self.assertRaises(bgp.BgpSpeakerPeerNotAssociated,
|
||||||
|
self.bgp_plugin.remove_bgp_peer,
|
||||||
|
self.context,
|
||||||
|
speaker['id'],
|
||||||
|
{'bgp_peer_id': peer['id']})
|
||||||
|
|
||||||
|
def test_remove_non_existent_bgp_peer(self):
|
||||||
|
bgp_peer_id = "imaginary"
|
||||||
|
with self.subnetpool_with_address_scope(4,
|
||||||
|
prefixes=['8.0.0.0/8']) as sp:
|
||||||
|
with self.bgp_speaker(sp['ip_version'], 1234) as speaker:
|
||||||
|
self.assertRaises(bgp.BgpSpeakerPeerNotAssociated,
|
||||||
|
self.bgp_plugin.remove_bgp_peer,
|
||||||
|
self.context,
|
||||||
|
speaker['id'],
|
||||||
|
{'bgp_peer_id': bgp_peer_id})
|
||||||
|
|
||||||
|
def test_add_non_existent_bgp_peer(self):
|
||||||
|
bgp_peer_id = "imaginary"
|
||||||
|
with self.subnetpool_with_address_scope(4,
|
||||||
|
prefixes=['8.0.0.0/8']) as sp:
|
||||||
|
with self.bgp_speaker(sp['ip_version'], 1234) as speaker:
|
||||||
|
self.assertRaises(bgp.BgpPeerNotFound,
|
||||||
|
self.bgp_plugin.add_bgp_peer,
|
||||||
|
self.context,
|
||||||
|
speaker['id'],
|
||||||
|
{'bgp_peer_id': bgp_peer_id})
|
||||||
|
|
||||||
|
def test_add_gateway_network(self):
|
||||||
|
with self.subnetpool_with_address_scope(4,
|
||||||
|
prefixes=['8.0.0.0/8']) as sp:
|
||||||
|
with self.bgp_speaker(sp['ip_version'], 1234) as speaker,\
|
||||||
|
self.network() as network:
|
||||||
|
network_id = network['network']['id']
|
||||||
|
self.bgp_plugin.add_gateway_network(self.context,
|
||||||
|
speaker['id'],
|
||||||
|
{'network_id': network_id})
|
||||||
|
new_speaker = self.bgp_plugin.get_bgp_speaker(self.context,
|
||||||
|
speaker['id'])
|
||||||
|
self.assertEqual(1, len(new_speaker['networks']))
|
||||||
|
self.assertTrue(network_id in new_speaker['networks'])
|
||||||
|
|
||||||
|
def test_create_bgp_speaker_with_network(self):
|
||||||
|
with self.subnetpool_with_address_scope(4,
|
||||||
|
prefixes=['8.0.0.0/8']) as sp:
|
||||||
|
network = self.plugin.create_network(self.context,
|
||||||
|
{'network':
|
||||||
|
{'name': 'test-net',
|
||||||
|
'tenant_id': _uuid(),
|
||||||
|
'admin_state_up': True,
|
||||||
|
'shared': True}})
|
||||||
|
with self.bgp_speaker(sp['ip_version'], 1234,
|
||||||
|
networks=[network['id']]) as speaker:
|
||||||
|
self.assertEqual(1, len(speaker['networks']))
|
||||||
|
self.assertTrue(network['id'] in speaker['networks'])
|
||||||
|
|
||||||
|
def test_remove_gateway_network(self):
|
||||||
|
with self.network() as network,\
|
||||||
|
self.subnetpool_with_address_scope(4,
|
||||||
|
prefixes=['8.0.0.0/8']) as sp:
|
||||||
|
network_id = network['network']['id']
|
||||||
|
with self.bgp_speaker(sp['ip_version'], 1234,
|
||||||
|
networks=[network_id]) as speaker:
|
||||||
|
self.bgp_plugin.remove_gateway_network(
|
||||||
|
self.context,
|
||||||
|
speaker['id'],
|
||||||
|
{'network_id': network_id})
|
||||||
|
new_speaker = self.bgp_plugin.get_bgp_speaker(self.context,
|
||||||
|
speaker['id'])
|
||||||
|
self.assertEqual(0, len(new_speaker['networks']))
|
||||||
|
|
||||||
|
def test_add_non_existent_gateway_network(self):
|
||||||
|
network_id = "imaginary"
|
||||||
|
with self.subnetpool_with_address_scope(4,
|
||||||
|
prefixes=['8.0.0.0/8']) as sp:
|
||||||
|
with self.bgp_speaker(sp['ip_version'], 1234) as speaker:
|
||||||
|
self.assertRaises(n_exc.NetworkNotFound,
|
||||||
|
self.bgp_plugin.add_gateway_network,
|
||||||
|
self.context, speaker['id'],
|
||||||
|
{'network_id': network_id})
|
||||||
|
|
||||||
|
def test_remove_non_existent_gateway_network(self):
|
||||||
|
network_id = "imaginary"
|
||||||
|
with self.subnetpool_with_address_scope(4,
|
||||||
|
prefixes=['8.0.0.0/8']) as sp:
|
||||||
|
with self.bgp_speaker(sp['ip_version'], 1234) as speaker:
|
||||||
|
self.assertRaises(bgp.BgpSpeakerNetworkNotAssociated,
|
||||||
|
self.bgp_plugin.remove_gateway_network,
|
||||||
|
self.context, speaker['id'],
|
||||||
|
{'network_id': network_id})
|
||||||
|
|
||||||
|
def test_add_gateway_network_two_bgp_speakers_same_scope(self):
|
||||||
|
with self.subnetpool_with_address_scope(4,
|
||||||
|
prefixes=['8.0.0.0/8']) as sp:
|
||||||
|
with self.bgp_speaker(sp['ip_version'], 1234) as speaker1,\
|
||||||
|
self.bgp_speaker(sp['ip_version'], 4321) as speaker2,\
|
||||||
|
self.network() as network:
|
||||||
|
network_id = network['network']['id']
|
||||||
|
self.bgp_plugin.add_gateway_network(self.context,
|
||||||
|
speaker1['id'],
|
||||||
|
{'network_id': network_id})
|
||||||
|
self.bgp_plugin.add_gateway_network(self.context,
|
||||||
|
speaker2['id'],
|
||||||
|
{'network_id': network_id})
|
||||||
|
speaker1 = self.bgp_plugin.get_bgp_speaker(self.context,
|
||||||
|
speaker1['id'])
|
||||||
|
speaker2 = self.bgp_plugin.get_bgp_speaker(self.context,
|
||||||
|
speaker2['id'])
|
||||||
|
for speaker in [speaker1, speaker2]:
|
||||||
|
self.assertEqual(1, len(speaker['networks']))
|
||||||
|
self.assertEqual(network_id,
|
||||||
|
speaker['networks'][0])
|
@ -75,6 +75,7 @@ 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
|
||||||
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
|
||||||
neutron.qos.notification_drivers =
|
neutron.qos.notification_drivers =
|
||||||
|
Loading…
Reference in New Issue
Block a user