Browse Source
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: I2412c1689683da9d7ec884a4cea506d4eed99453changes/26/309326/1
15 changed files with 1431 additions and 2 deletions
@ -0,0 +1,7 @@
|
||||
function configure_bgp_service_plugin { |
||||
_neutron_service_plugin_class_add "bgp" |
||||
} |
||||
|
||||
function configure_bgp { |
||||
configure_bgp_service_plugin |
||||
} |
@ -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') |
||||
) |
@ -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,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.") |
@ -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) |
@ -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') |
@ -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: |
||||