diff --git a/README.rst b/README.rst index 0e11927e..281842f9 100644 --- a/README.rst +++ b/README.rst @@ -12,4 +12,24 @@ API and Framework to interconnect bgpvpn to neutron networks Features -------- -* TODO +to be able to test this framework, you have to : + +-clone this repo and install the python package : +#git clone http://git.openstack.org/cgit/stackforge/networking-bgpvpn +#sudo python setup.py develop + +-run the latest devstack (and let it fetch latest openstack code) +with the following options : +Q_SERVICE_PLUGIN_CLASSES=networking_bgpvpn.neutron.services.bgpvpn.plugin.BGPVPNPlugin +[[post-config|/$NEUTRON_CONF]] +[service_providers] +service_provider=BGPVPN:BaGPipe:networking_bgpvpn.neutron.services.bgpvpn.service_drivers.dummy.dummyBGPVPNDriver:default + +-update the db with : +#/usr/local/bin/bgpvpn-db-manage --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/plugins/ml2/ml2_conf.ini upgrade head + +-bgpvpn-connection-create/update/delete/show/list commands will be available with the neutron client +for example : +#. openrc admin admin +#neutron bgpvpn-connection-create --route-targets 64512:1 +#neutron bgpvpn-connection-list \ No newline at end of file diff --git a/networking_bgpvpn/neutron/__init__.py b/networking_bgpvpn/neutron/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/networking_bgpvpn/neutron/db/__init__.py b/networking_bgpvpn/neutron/db/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/networking_bgpvpn/neutron/db/bgpvpn/__init__.py b/networking_bgpvpn/neutron/db/bgpvpn/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/networking_bgpvpn/neutron/db/bgpvpn/bgpvpn_db.py b/networking_bgpvpn/neutron/db/bgpvpn/bgpvpn_db.py new file mode 100644 index 00000000..814a8d90 --- /dev/null +++ b/networking_bgpvpn/neutron/db/bgpvpn/bgpvpn_db.py @@ -0,0 +1,248 @@ +# Copyright (c) 2015 Orange. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import sqlalchemy as sa + +from neutron.common import exceptions as q_exc +from neutron.db import common_db_mixin +from neutron.db import model_base +from neutron.db import models_v2 +from neutron.openstack.common import uuidutils +from oslo_log import log +from sqlalchemy import orm +from sqlalchemy.orm import exc + +from networking_bgpvpn.neutron.extensions.bgpvpn.bgpvpn import BGPVPNPluginBase + + +LOG = log.getLogger(__name__) + + +class BGPVPNConnection(model_base.BASEV2, + models_v2.HasId, + models_v2.HasTenant): + """Represents a BGPVPNConnection Object.""" + __tablename__ = 'bgpvpn_connections' + name = sa.Column(sa.String(255)) + type = sa.Column(sa.Enum("l2", "l3", + name="bgpvpn_type"), + nullable=False) + route_targets = sa.Column(sa.String(255), nullable=False) + import_targets = sa.Column(sa.String(255), nullable=False) + export_targets = sa.Column(sa.String(255), nullable=False) + auto_aggregate = sa.Column(sa.Boolean(), nullable=False) + network_id = sa.Column(sa.String(36), + sa.ForeignKey('networks.id')) + network = orm.relationship(models_v2.Network) + + +class BGPVPNConnectionNotFound(q_exc.NotFound): + message = _("BgpVpnConnection %(conn_id)s could not be found") + + +class BGPVPNConnectionMissingRouteTarget(q_exc.BadRequest): + message = _("BgpVpnConnection could not be created. Missing one of" + " route_targets, import_targets or export_targets attribute") + + +class BGPVPNNetworkInUse(q_exc.NetworkInUse): + message = _("Unable to complete operation on network %(network_id)s. " + "There are one or more BGP VPN connections associated" + " to the network.") + + +class BGPVPNPluginDb(BGPVPNPluginBase, + common_db_mixin.CommonDbMixin): + """BGP VPN service plugin database class using SQLAlchemy models.""" + + USER_READABLE_ATTRIBUTES = ['id', 'name', 'type', + 'network_id', 'tenant_id'] + USER_WRITABLE_ATTRIBUTES = ['name', 'network_id'] + + def _get_resource(self, context, model, id): + return self._get_by_id(context, model, id) + + def _rt_list2str(self, list): + """Format Route Target list to string""" + if not list: + return '' + + return ','.join(list) + + def _rt_str2list(self, str): + """Format Route Target string to list""" + if not str: + return [] + + return str.split(',') + + def _get_bgpvpn_connections_for_tenant(self, session, tenant_id, fields): + try: + qry = session.query(BGPVPNConnection) + bgpvpn_connections = qry.filter_by(tenant_id=tenant_id) + except exc.NoResultFound: + return + + return [self._make_bgpvpn_connection_dict(bvc, fields=fields) + for bvc in bgpvpn_connections] + + def _get_user_readable_fields(self, fields): + if fields is not None and fields: + return list(set(fields) & set(self.USER_READABLE_ATTRIBUTES)) + else: + return self.USER_READABLE_ATTRIBUTES + + def _make_bgpvpn_connection_dict(self, + bgpvpn_connection, + fields=None): + res = { + 'id': bgpvpn_connection['id'], + 'tenant_id': bgpvpn_connection['tenant_id'], + 'network_id': bgpvpn_connection['network_id'], + 'name': bgpvpn_connection['name'], + 'type': bgpvpn_connection['type'], + 'route_targets': + self._rt_str2list(bgpvpn_connection['route_targets']), + 'import_targets': + self._rt_str2list(bgpvpn_connection['import_targets']), + 'export_targets': + self._rt_str2list(bgpvpn_connection['export_targets']), + 'auto_aggregate': bgpvpn_connection['auto_aggregate'] + } + return self._fields(res, fields) + + def create_bgpvpn_connection(self, context, bgpvpn_connection): + bgpvpn_conn = bgpvpn_connection['bgpvpn_connection'] + + # Check that route_targets is not empty + if (not bgpvpn_conn['route_targets']): + raise BGPVPNConnectionMissingRouteTarget + else: + rt = self._rt_list2str(bgpvpn_conn['route_targets']) + i_rt = self._rt_list2str(bgpvpn_conn['import_targets']) + e_rt = self._rt_list2str(bgpvpn_conn['export_targets']) + + tenant_id = self._get_tenant_id_for_create(context, bgpvpn_conn) + + with context.session.begin(subtransactions=True): + bgpvpn_conn_db = BGPVPNConnection( + id=uuidutils.generate_uuid(), + tenant_id=tenant_id, + name=bgpvpn_conn['name'], + type=bgpvpn_conn['type'], + route_targets=rt, + import_targets=i_rt, + export_targets=e_rt, + network_id=bgpvpn_conn['network_id'], + auto_aggregate=bgpvpn_conn['auto_aggregate'] + ) + context.session.add(bgpvpn_conn_db) + + return self._make_bgpvpn_connection_dict(bgpvpn_conn_db) + + def get_bgpvpn_connections(self, context, filters=None, fields=None): + if context.is_admin: + return self._get_collection(context, BGPVPNConnection, + self._make_bgpvpn_connection_dict, + filters=filters, fields=fields) + else: + readable_fields = self._get_user_readable_fields(fields) + LOG.debug("get_bgpvpn_connections called for user, " + "readable fields = %s" % readable_fields) + return self._get_bgpvpn_connections_for_tenant(context.session, + context.tenant_id, + readable_fields) + + def _get_bgpvpn_connection(self, context, id): + try: + if context.is_admin: + return self._get_resource(context, BGPVPNConnection, id) + else: + qry = context.session.query(BGPVPNConnection) + return qry.filter_by(id=id, tenant_id=context.tenant_id).one() + except exc.NoResultFound: + raise BGPVPNConnectionNotFound(conn_id=id) + + def get_bgpvpn_connection(self, context, id, fields=None): + bgpvpn_connection_db = self._get_bgpvpn_connection(context, id) + LOG.debug("get_bgpvpn_connection called with fields = %s" % fields) + + if not context.is_admin: + fields = self._get_user_readable_fields(fields) + LOG.debug("get_bgpvpn_connection called for user," + "readable fields = %s" % fields) + + return self._make_bgpvpn_connection_dict(bgpvpn_connection_db, fields) + + def update_bgpvpn_connection(self, context, id, bgpvpn_connection): + bgpvpn_conn = bgpvpn_connection['bgpvpn_connection'] + fields = None + + LOG.debug("update_bgpvpn_connection called with %s for %s" + % (bgpvpn_connection, id)) + + with context.session.begin(subtransactions=True): + bgpvpn_connection_db = self._get_bgpvpn_connection(context, id) + + if bgpvpn_conn: + # Filter only user writable attributes + if not context.is_admin: + bgpvpn_conn = { + user_attr: bgpvpn_conn[user_attr] + for user_attr in self.USER_WRITABLE_ATTRIBUTES + if user_attr in bgpvpn_conn} + fields = self.USER_READABLE_ATTRIBUTES + else: + # Format Route Target list to string + rt = self._rt_list2str(bgpvpn_conn['route_targets']) + if 'route_targets' in bgpvpn_conn: + bgpvpn_conn['route_targets'] = rt + if 'import_targets' in bgpvpn_conn: + i_rt = self._rt_list2str(bgpvpn_conn['import_targets']) + bgpvpn_conn['import_targets'] = i_rt + if 'export_targets' in bgpvpn_conn: + e_rt = self._rt_list2str(bgpvpn_conn['export_targets']) + bgpvpn_conn['export_targets'] = e_rt + + bgpvpn_connection_db.update(bgpvpn_conn) + + return self._make_bgpvpn_connection_dict(bgpvpn_connection_db, fields) + + def delete_bgpvpn_connection(self, context, id): + with context.session.begin(subtransactions=True): + bgpvpn_connection_db = self._get_resource( + context, + BGPVPNConnection, + id) + + context.session.delete(bgpvpn_connection_db) + + return bgpvpn_connection_db + + def find_bgpvpn_connections_for_network(self, context, network_id): + LOG.debug("get_bgpvpn_connections_for_network() called for " + "network %s" % + network_id) + + try: + bgpvpn_connections = (context.session.query(BGPVPNConnection). + filter(BGPVPNConnection.network_id == + network_id). + all()) + except exc.NoResultFound: + return + + return [self._make_bgpvpn_connection_dict(bvc) + for bvc in bgpvpn_connections] diff --git a/networking_bgpvpn/neutron/db/migration/__init__.py b/networking_bgpvpn/neutron/db/migration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/networking_bgpvpn/neutron/db/migration/alembic.ini b/networking_bgpvpn/neutron/db/migration/alembic.ini new file mode 100644 index 00000000..c7875e74 --- /dev/null +++ b/networking_bgpvpn/neutron/db/migration/alembic.ini @@ -0,0 +1,59 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts +script_location = alembic_migrations + +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# max length of characters to apply to the +# "slug" field +#truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +sqlalchemy.url = + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/networking_bgpvpn/neutron/db/migration/alembic_migrations/README b/networking_bgpvpn/neutron/db/migration/alembic_migrations/README new file mode 100644 index 00000000..98e4f9c4 --- /dev/null +++ b/networking_bgpvpn/neutron/db/migration/alembic_migrations/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/networking_bgpvpn/neutron/db/migration/alembic_migrations/env.py b/networking_bgpvpn/neutron/db/migration/alembic_migrations/env.py new file mode 100644 index 00000000..caca6173 --- /dev/null +++ b/networking_bgpvpn/neutron/db/migration/alembic_migrations/env.py @@ -0,0 +1,86 @@ +# Copyright 2014 OpenStack Foundation +# +# 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 logging import config as logging_config + +from alembic import context +from neutron.db import model_base +from oslo_config import cfg +from oslo_db.sqlalchemy import session +import sqlalchemy as sa +from sqlalchemy import event + + +MYSQL_ENGINE = None +BGPVPN_VERSION_TABLE = 'alembic_version_bgpvpn' +config = context.config +neutron_config = config.neutron_config +logging_config.fileConfig(config.config_file_name) +target_metadata = model_base.BASEV2.metadata + + +def set_mysql_engine(): + try: + mysql_engine = neutron_config.command.mysql_engine + except cfg.NoSuchOptError: + mysql_engine = None + + global MYSQL_ENGINE + MYSQL_ENGINE = (mysql_engine or + model_base.BASEV2.__table_args__['mysql_engine']) + + +def run_migrations_offline(): + set_mysql_engine() + + kwargs = dict() + if neutron_config.database.connection: + kwargs['url'] = neutron_config.database.connection + else: + kwargs['dialect_name'] = neutron_config.database.engine + kwargs['version_table'] = BGPVPN_VERSION_TABLE + context.configure(**kwargs) + + with context.begin_transaction(): + context.run_migrations() + + +@event.listens_for(sa.Table, 'after_parent_attach') +def set_storage_engine(target, parent): + if MYSQL_ENGINE: + target.kwargs['mysql_engine'] = MYSQL_ENGINE + + +def run_migrations_online(): + set_mysql_engine() + engine = session.create_engine(neutron_config.database.connection) + + connection = engine.connect() + context.configure( + connection=connection, + target_metadata=target_metadata, + version_table=BGPVPN_VERSION_TABLE + ) + try: + with context.begin_transaction(): + context.run_migrations() + finally: + connection.close() + engine.dispose() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/networking_bgpvpn/neutron/db/migration/alembic_migrations/script.py.mako b/networking_bgpvpn/neutron/db/migration/alembic_migrations/script.py.mako new file mode 100644 index 00000000..95702017 --- /dev/null +++ b/networking_bgpvpn/neutron/db/migration/alembic_migrations/script.py.mako @@ -0,0 +1,22 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision} +Create Date: ${create_date} + +""" + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/HEAD b/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/HEAD new file mode 100644 index 00000000..9a3007fb --- /dev/null +++ b/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/HEAD @@ -0,0 +1 @@ +start_networking_bgpvpn diff --git a/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/start_networking_bgpvpn.py b/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/start_networking_bgpvpn.py new file mode 100644 index 00000000..fdf5ae10 --- /dev/null +++ b/networking_bgpvpn/neutron/db/migration/alembic_migrations/versions/start_networking_bgpvpn.py @@ -0,0 +1,48 @@ +# Copyright 2014 OpenStack Foundation +# +# 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 alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'start_networking_bgpvpn' +down_revision = None + +vpn_types = sa.Enum("l2", "l3", name="vpn_types") + + +def upgrade(active_plugins=None, options=None): + op.create_table( + u'bgpvpn_connections', + sa.Column(u'name', sa.String(255), nullable=True), + sa.Column(u'id', sa.String(length=36), nullable=False), + sa.Column(u'tenant_id', sa.String(length=255), nullable=True), + sa.Column(u'type', vpn_types, nullable=False), + sa.Column(u'route_targets', sa.String(255), nullable=False), + sa.Column(u'import_targets', sa.String(255), nullable=True), + sa.Column(u'export_targets', sa.String(255), nullable=True), + sa.Column(u'auto_aggregate', sa.Boolean(), nullable=False), + sa.Column(u'network_id', sa.String(36), nullable=True), + sa.ForeignKeyConstraint(['network_id'], [u'networks.id'], ), + sa.PrimaryKeyConstraint(u'id'), + mysql_default_charset=u'utf8', + mysql_engine='InnoDB' + ) + + +def downgrade(active_plugins=None, options=None): + op.drop_table(u'bgpvpn_connections') + vpn_types.drop(op.get_bind(), checkfirst=False) diff --git a/networking_bgpvpn/neutron/db/migration/cli.py b/networking_bgpvpn/neutron/db/migration/cli.py new file mode 100644 index 00000000..1799bc20 --- /dev/null +++ b/networking_bgpvpn/neutron/db/migration/cli.py @@ -0,0 +1,24 @@ +# 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.migration.cli import * # noqa + + +def main(): + config = alembic_config.Config( + os.path.join(os.path.dirname(__file__), 'alembic.ini')) + config.set_main_option('script_location', + ('networking_bgpvpn.neutron.db.migration:' + 'alembic_migrations')) + config.neutron_config = CONF + CONF() + CONF.command.func(config, CONF.command.name) diff --git a/networking_bgpvpn/neutron/extensions/__init__.py b/networking_bgpvpn/neutron/extensions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/networking_bgpvpn/neutron/extensions/bgpvpn/__init__.py b/networking_bgpvpn/neutron/extensions/bgpvpn/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/networking_bgpvpn/neutron/extensions/bgpvpn/bgpvpn.py b/networking_bgpvpn/neutron/extensions/bgpvpn/bgpvpn.py new file mode 100644 index 00000000..b2778a25 --- /dev/null +++ b/networking_bgpvpn/neutron/extensions/bgpvpn/bgpvpn.py @@ -0,0 +1,203 @@ +# Copyright (c) 2015 Orange. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import abc + +import six + +from networking_bgpvpn.neutron.extensions import bgpvpn as bgpvpn_ext +from neutron.api import extensions +from neutron.api.v2 import attributes as attr +from neutron.api.v2 import resource_helper +from neutron.plugins.common import constants +from neutron.services.service_base import ServicePluginBase +from oslo_log import log + + +LOG = log.getLogger(__name__) + +BGPVPN_L3 = 'l3' +BGPVPN_L2 = 'l2' +BGPVPN_TYPES = [BGPVPN_L3, BGPVPN_L2] + + +# Regular expression to validate Route Target list format +# [":",":", ...] with asn and nn in range 0-65535 +RT_REGEX = ('^((?:0|[1-9]\d{0,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]' + '\d|6553[0-5]):(?:0|[1-9]\d{0,3}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d' + '{2}|655[0-2]\d|6553[0-5]))$') + +extensions.append_api_extensions_path(bgpvpn_ext.__path__) +constants.BGPVPN = "BGPVPN" +constants.ALLOWED_SERVICES.append(constants.BGPVPN) +constants.COMMON_PREFIXES["BGPVPN"] = "/bgpvpn" + + +def _validate_rt_list(data, valid_values=None): + if not isinstance(data, list): + msg = _("'%s' is not a list") % data + LOG.debug(msg) + return msg + + for item in data: + msg = attr._validate_regex(item, RT_REGEX) + if msg: + LOG.debug(msg) + return msg + + if len(set(data)) != len(data): + msg = _("Duplicate items in the list: '%s'") % ', '.join(data) + LOG.debug(msg) + return msg + + +def _validate_rt_list_or_none(data, valid_values=None): + if not data: + return _validate_rt_list(data, valid_values=valid_values) + +validators = {'type:route_target_list': _validate_rt_list, + 'type:route_target_list_or_none': _validate_rt_list_or_none} +attr.validators.update(validators) + +RESOURCE_ATTRIBUTE_MAP = { + 'bgpvpn_connections': { + 'id': {'allow_post': False, 'allow_put': False, + 'validate': {'type:uuid': None}, + 'is_visible': True, + 'primary_key': True}, + 'tenant_id': {'allow_post': True, 'allow_put': False, + 'validate': {'type:string': None}, + 'required_by_policy': True, + 'is_visible': True}, + 'network_id': {'allow_post': True, 'allow_put': True, + 'default': None, + 'validate': {'type:uuid_or_none': None}, + 'is_visible': True}, + 'name': {'allow_post': True, 'allow_put': True, + 'default': '', + 'validate': {'type:string': None}, + 'is_visible': True}, + 'type': {'allow_post': True, 'allow_put': False, + 'default': BGPVPN_L3, + 'validate': {'type:values': BGPVPN_TYPES}, + 'is_visible': True}, + 'route_targets': {'allow_post': True, 'allow_put': True, + 'default': [], + 'convert_to': attr.convert_to_list, + 'validate': {'type:route_target_list': None}, + 'is_visible': True}, + 'import_targets': {'allow_post': True, 'allow_put': True, + 'default': None, + 'convert_to': attr.convert_to_list, + 'validate': {'type:route_target_list_or_none': + None}, + 'is_visible': True}, + 'export_targets': {'allow_post': True, 'allow_put': True, + 'default': None, + 'convert_to': attr.convert_to_list, + 'validate': {'type:route_target_list_or_none': + None}, + 'is_visible': True}, + 'auto_aggregate': {'allow_post': True, 'allow_put': True, + 'default': True, + 'validate': {'type:boolean': None}, + 'convert_to': attr.convert_to_boolean, + 'is_visible': True}, + }, +} + + +class Bgpvpn(extensions.ExtensionDescriptor): + + @classmethod + def get_name(cls): + return "BGPVPN Connection extension" + + @classmethod + def get_alias(cls): + return "bgpvpn" + + @classmethod + def get_description(cls): + return "Extension for BGPVPN Connection service" + + @classmethod + def get_namespace(cls): + return "http://wiki.openstack.org/Neutron/bgpvpn/API_1.0" + + @classmethod + def get_updated(cls): + return "2014-06-10T17:00:00-00:00" + + @classmethod + def get_resources(cls): + plural_mappings = resource_helper.build_plural_mappings( + {}, RESOURCE_ATTRIBUTE_MAP) + plural_mappings['route_targets'] = 'route_target' + plural_mappings['import_targets'] = 'import_target' + plural_mappings['export_targets'] = 'export_target' + attr.PLURALS.update(plural_mappings) + return resource_helper.build_resource_info(plural_mappings, + RESOURCE_ATTRIBUTE_MAP, + constants.BGPVPN, + register_quota=True, + translate_name=True) + + @classmethod + def get_plugin_interface(cls): + return BGPVPNPluginBase + + def update_attributes_map(self, attributes): + super(Bgpvpn, self).update_attributes_map( + attributes, extension_attrs_map=RESOURCE_ATTRIBUTE_MAP) + + def get_extended_resources(self, version): + if version == "2.0": + return RESOURCE_ATTRIBUTE_MAP + else: + return {} + + +@six.add_metaclass(abc.ABCMeta) +class BGPVPNPluginBase(ServicePluginBase): + + def get_plugin_name(self): + return constants.BGPVPN + + def get_plugin_type(self): + return constants.BGPVPN + + def get_plugin_description(self): + return 'BGP VPN service plugin' + + @abc.abstractmethod + def create_bgpvpn_connection(self, context, bgpvpn_connection): + pass + + @abc.abstractmethod + def get_bgpvpn_connections(self, context, filters=None, fields=None): + pass + + @abc.abstractmethod + def get_bgpvpn_connection(self, context, id, fields=None): + pass + + @abc.abstractmethod + def update_bgpvpn_connection(self, context, id, bgpvpn_connection): + pass + + @abc.abstractmethod + def delete_bgpvpn_connection(self, context, id): + pass diff --git a/networking_bgpvpn/neutron/services/__init__.py b/networking_bgpvpn/neutron/services/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/networking_bgpvpn/neutron/services/bgpvpn/__init__.py b/networking_bgpvpn/neutron/services/bgpvpn/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/networking_bgpvpn/neutron/services/bgpvpn/plugin.py b/networking_bgpvpn/neutron/services/bgpvpn/plugin.py new file mode 100644 index 00000000..c02272b0 --- /dev/null +++ b/networking_bgpvpn/neutron/services/bgpvpn/plugin.py @@ -0,0 +1,89 @@ +# Copyright (c) 2015 Orange. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from networking_bgpvpn.neutron.db.bgpvpn import bgpvpn_db +from neutron.i18n import _LI +from neutron.plugins.common import constants +from neutron.services import service_base +from oslo_log import log + +LOG = log.getLogger(__name__) + + +class BGPVPNPlugin(bgpvpn_db.BGPVPNPluginDb): + supported_extension_aliases = ["bgpvpn"] + + def __init__(self): + super(BGPVPNPlugin, self).__init__() + # Load the service driver from neutron.conf. + drivers, default_provider = service_base.load_drivers( + constants.BGPVPN, self) + LOG.info(_LI("BGP VPN Service Plugin using Service Driver: %s"), + default_provider) + self.bgpvpn_driver = drivers[default_provider] + + def get_plugin_type(self): + return constants.BGPVPN + + def get_plugin_description(self): + return "Neutron BGP VPN connection Service Plugin" + + def prevent_bgpvpn_network_deletion(self, context, network_id): + LOG.debug('Prevent BGP VPN network deletion') + if (super(BGPVPNPlugin, self). + get_bgpvpn_connections(context, + filters={'network_id': [network_id]})): + raise bgpvpn_db.BGPVPNNetworkInUse(network_id=network_id) + else: + LOG.debug('Network %(network_id)s can be deleted') + + def create_bgpvpn_connection(self, context, bgpvpn_connection): + bgpvpn_connection = super( + BGPVPNPlugin, self).create_bgpvpn_connection(context, + bgpvpn_connection) + + self.bgpvpn_driver.create_bgpvpn_connection(context, + bgpvpn_connection) + return bgpvpn_connection + + def delete_bgpvpn_connection(self, context, bgpvpn_conn_id): + bgpvpn_connection = super( + BGPVPNPlugin, self).delete_bgpvpn_connection(context, + bgpvpn_conn_id) + + self.bgpvpn_driver.delete_bgpvpn_connection(context, + bgpvpn_connection) + + def update_bgpvpn_connection(self, + context, bgpvpn_conn_id, + bgpvpn_connection): + old_bgpvpn_connection = self.get_bgpvpn_connection(context, + bgpvpn_conn_id) + + bgpvpn_connection = super( + BGPVPNPlugin, self).update_bgpvpn_connection(context, + bgpvpn_conn_id, + bgpvpn_connection) + + self.bgpvpn_driver.update_bgpvpn_connection(context, + old_bgpvpn_connection, + bgpvpn_connection) + return bgpvpn_connection + + def notify_port_updated(self, context, port): + self.bgpvpn_driver.notify_port_updated(context, port) + + def remove_port_from_bgpvpn_agent(self, context, port): + self.bgpvpn_driver.remove_port_from_bgpvpn_agent(context, port) diff --git a/networking_bgpvpn/neutron/services/bgpvpn/service_drivers/__init__.py b/networking_bgpvpn/neutron/services/bgpvpn/service_drivers/__init__.py new file mode 100644 index 00000000..c4b8757c --- /dev/null +++ b/networking_bgpvpn/neutron/services/bgpvpn/service_drivers/__init__.py @@ -0,0 +1,49 @@ +# Copyright (c) 2015 Orange. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import abc +import six + + +@six.add_metaclass(abc.ABCMeta) +class BGPVPNDriver(object): + + def __init__(self, service_plugin): + self.service_plugin = service_plugin + + @property + def service_type(self): + pass + + @abc.abstractmethod + def create_bgpvpn_connection(self, context, bgpvpn_connection): + pass + + @abc.abstractmethod + def update_bgpvpn_connection(self, context, old_bgpvpn_connection, + bgpvpn_connection): + pass + + @abc.abstractmethod + def delete_bgpvpn_connection(self, context, bgpvpn_connection): + pass + + @abc.abstractmethod + def notify_port_updated(self, context, port): + pass + + @abc.abstractmethod + def remove_port_from_bgpvpn_agent(self, context, port): + pass diff --git a/networking_bgpvpn/neutron/services/bgpvpn/service_drivers/dummy.py b/networking_bgpvpn/neutron/services/bgpvpn/service_drivers/dummy.py new file mode 100644 index 00000000..0f3f355b --- /dev/null +++ b/networking_bgpvpn/neutron/services/bgpvpn/service_drivers/dummy.py @@ -0,0 +1,43 @@ +# Copyright (c) 2015 Orange. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from networking_bgpvpn.neutron.services.bgpvpn import service_drivers +from oslo_log import log + +LOG = log.getLogger(__name__) + + +class dummyBGPVPNDriver(service_drivers.BGPVPNDriver): + """dummy BGP VPN connection Service Driver class.""" + + def __init__(self, service_plugin): + super(dummyBGPVPNDriver, self).__init__(service_plugin) + LOG.debug("dummyBGPVPNDriver service_plugin : %s", service_plugin) + + def create_bgpvpn_connection(self, context, bgpvpn_connection): + pass + + def update_bgpvpn_connection(self, context, old_bgpvpn_connection, + bgpvpn_connection): + pass + + def delete_bgpvpn_connection(self, context, bgpvpn_connection): + pass + + def notify_port_updated(self, context, port): + pass + + def remove_port_from_bgpvpn_agent(self, context, port): + pass diff --git a/networking_bgpvpn/neutronclient/__init__.py b/networking_bgpvpn/neutronclient/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/networking_bgpvpn/neutronclient/neutron/__init__.py b/networking_bgpvpn/neutronclient/neutron/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/networking_bgpvpn/neutronclient/neutron/v2_0/__init__.py b/networking_bgpvpn/neutronclient/neutron/v2_0/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/networking_bgpvpn/neutronclient/neutron/v2_0/bgpvpn/__init__.py b/networking_bgpvpn/neutronclient/neutron/v2_0/bgpvpn/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/networking_bgpvpn/neutronclient/neutron/v2_0/bgpvpn/bgpvpn_connection.py b/networking_bgpvpn/neutronclient/neutron/v2_0/bgpvpn/bgpvpn_connection.py new file mode 100644 index 00000000..26e857c4 --- /dev/null +++ b/networking_bgpvpn/neutronclient/neutron/v2_0/bgpvpn/bgpvpn_connection.py @@ -0,0 +1,110 @@ +# Copyright (c) 2015 Orange. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +from neutronclient.common import extension +from neutronclient.i18n import _ +from neutronclient.neutron import v2_0 as neutronv20 + + +class BGPVPNConnection(extension.NeutronClientExtension): + resource = 'bgpvpn_connection' + path = 'bgpvpn-connections' + resource_plural = '%ss' % resource + object_path = '/bgpvpn/%s' % path + resource_path = '/bgpvpn/%s/%%s' % path + versions = ['2.0'] + + +class BGPVPNConnectionCreate(extension.ClientExtensionCreate, + BGPVPNConnection): + shell_command = 'bgpvpn-connection-create' + + def add_known_arguments(self, parser): + parser.add_argument( + '--name', + help=_('Name of the BGP VPN connection')) + parser.add_argument( + '--type', + default='l3', choices=['l2', 'l3'], + help=_('BGP VPN connection type selection between L3VPN (l3) and ' + 'EVPN (l2), default:l3')) + parser.add_argument( + '--route-targets', + help=_('Route Targets list to import/export for this BGP ' + 'VPN connection. Usage: -- --route-targets ' + 'list=true : : ...')) + parser.add_argument( + '--import-targets', + help=_('List of additional Route Targets to import from.' + ' Usage: -- --import-targets list=true ' + ': : ...')) + parser.add_argument( + '--export-targets', + help=_('List of additional Route Targets to export to. Usage: -- ' + '--export-targets list=true : : ...')) + parser.add_argument( + '--network-id', metavar='NETWORK', + default=None, + help=_('Id of the network associated with this ' + 'BGP VPN connection')) + parser.add_argument( + '--no-aggregate', + dest='auto_aggregate', action='store_false', + help=_('Disable auto aggregation (only for ' + 'L3VPN connection type)')) + + def args2body(self, parsed_args): + body = { + self.resource: {}, + } + + if parsed_args.network_id: + _network_id = neutronv20.find_resourceid_by_name_or_id( + self.get_client(), 'network', + parsed_args.network_id) + body[self.resource]['network_id'] = _network_id + + neutronv20.update_dict(parsed_args, body[self.resource], + ['name', 'tenant_id', 'type', 'route_targets', + 'import_targets', 'export_targets', + 'auto_aggregate']) + + return body + + +class BGPVPNConnectionUpdate(extension.ClientExtensionUpdate, + BGPVPNConnection): + shell_command = 'bgpvpn-connection-update' + + +class BGPVPNConnectionDelete(extension.ClientExtensionDelete, + BGPVPNConnection): + shell_command = 'bgpvpn-connection-delete' + + +class BGPVPNConnectionList(extension.ClientExtensionList, + BGPVPNConnection): + shell_command = 'bgpvpn-connection-list' + list_columns = [ + 'id', 'name', 'type', 'route_targets', 'import_targets', + 'export_targets', 'network_id', 'auto_aggregate', 'tenant_id'] + pagination_support = True + sorting_support = True + + +class BGPVPNConnectionShow(extension.ClientExtensionShow, + BGPVPNConnection): + shell_command = 'bgpvpn-connection-show' diff --git a/setup.cfg b/setup.cfg index 058b77a0..27db00bc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -24,6 +24,12 @@ classifier = packages = networking_bgpvpn +[entry_points] +console_scripts= + bgpvpn-db-manage = networking_bgpvpn.neutron.db.migration.cli:main +neutronclient.extension= + bgpvpn_connection = networking_bgpvpn.neutronclient.neutron.v2_0.bgpvpn.bgpvpn_connection + [build_sphinx] source-dir = doc/source build-dir = doc/build

AltStyle によって変換されたページ (->オリジナル) /