Browse Source

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
changes/26/309326/1
Ryan Tidwell 6 years ago committed by Carl Baldwin
parent
commit
9d1d6a08db
  1. 7
      devstack/lib/bgp
  2. 4
      devstack/plugin.sh
  3. 20
      etc/policy.json
  4. 380
      neutron/db/bgp_db.py
  5. 105
      neutron/db/migration/alembic_migrations/versions/mitaka/expand/15be73214821_add_bgp_model_data.py
  6. 1
      neutron/db/migration/models/head.py
  7. 194
      neutron/extensions/bgp.py
  8. 0
      neutron/services/bgp/__init__.py
  9. 39
      neutron/services/bgp/bgp_plugin.py
  10. 173
      neutron/tests/api/test_bgp_speaker_extensions.py
  11. 53
      neutron/tests/api/test_bgp_speaker_extensions_negative.py
  12. 20
      neutron/tests/etc/policy.json
  13. 105
      neutron/tests/tempest/services/network/json/network_client.py
  14. 331
      neutron/tests/unit/db/test_bgp_db.py
  15. 1
      setup.cfg

7
devstack/lib/bgp

@ -0,0 +1,7 @@
function configure_bgp_service_plugin {
_neutron_service_plugin_class_add "bgp"
}
function configure_bgp {
configure_bgp_service_plugin
}

4
devstack/plugin.sh

@ -1,5 +1,6 @@
LIBDIR=$DEST/neutron/devstack/lib
source $LIBDIR/bgp
source $LIBDIR/flavors
source $LIBDIR/l2_agent
source $LIBDIR/l2_agent_sriovnicswitch
@ -15,6 +16,9 @@ if [[ "$1" == "stack" ]]; then
if is_service_enabled q-qos; then
configure_qos
fi
if is_service_enabled q-bgp; then
configure_bgp
fi
;;
post-config)
if is_service_enabled q-agt; then

20
etc/policy.json

@ -205,5 +205,23 @@
"create_flavor_service_profile": "rule:admin_only",
"delete_flavor_service_profile": "rule:admin_only",
"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

@ -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)

105
neutron/db/migration/alembic_migrations/versions/mitaka/expand/15be73214821_add_bgp_model_data.py

@ -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')
)

1
neutron/db/migration/models/head.py

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

194
neutron/extensions/bgp.py

@ -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

39
neutron/services/bgp/bgp_plugin.py

@ -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

@ -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

@ -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')

20
neutron/tests/etc/policy.json

@ -205,5 +205,23 @@
"create_flavor_service_profile": "rule:admin_only",
"delete_flavor_service_profile": "rule:admin_only",
"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"
}

105
neutron/tests/tempest/services/network/json/network_client.py

@ -48,6 +48,8 @@ class NetworkClientJSON(service_client.ServiceClient):
# the following map is used to construct proper URI
# for the given neutron resource
service_resource_prefix_map = {
'bgp-peers': '',
'bgp-speakers': '',
'networks': '',
'subnets': '',
'subnetpools': '',
@ -220,6 +222,109 @@ class NetworkClientJSON(service_client.ServiceClient):
self.expected_success(200, resp.status)
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
def create_bulk_network(self, names, shared=False):
network_list = [{'name': name, 'shared': shared} for name in names]

331
neutron/tests/unit/db/test_bgp_db.py

@ -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: