Add OpenContrail driver

That version uses the key/value store exposed by the OpenContrail
API but that's not efficient. The BGP/VPN resources and sub-association
need to be added to the OpenContrail data model.
That first PoC will be delivered to with the Liberty release but will be
replaced by a final one for Mitaka and next releases without any
migration script or method from the first PoC version.
Implements: blueprint opencontrail-bgpvpn-driver
Change-Id: I3795dd7b24afdac1cd5134c41b6b32ef8a888287
This commit is contained in:
Edouard Thuleau
2015年11月20日 09:57:11 +00:00
parent e435e9e2e0
commit 91b9246f87

View File

@@ -3,4 +3,3 @@ NETWORKING_BGPVPN_CONF="$NEUTRON_CONF_DIR/networking_bgpvpn.conf"
BGPVPN_PLUGIN_CLASS="networking_bgpvpn.neutron.services.plugin.BGPVPNPlugin"
NETWORKING_BGPVPN_DRIVER=${NETWORKING_BGPVPN_DRIVER:-BGPVPN:Dummy:networking_bgpvpn.neutron.services.service_drivers.driver_api.BGPVPNDriver:default}

View File

@@ -0,0 +1,49 @@
How to use OpenContrail driver for the BGPVPN plugin?
-------------------------------------------------------------------------------
The **OpenContrail** driver for the BGPVPN service plugin is designed to work
jointly with the `OpenContrail SDN controller`_.
OpenContrail proposes a similar script that devstack to deploy a dev and test
environment named `contrail installer`_
* Clone that OpenContrail installer script:
```
git clone git@github.com:Juniper/contrail-installer
```
* Compile and run OpenContrail:
```
cd ~/contrail-installer
cp samples/localrc-all localrc (edit localrc as needed)
./contrail.sh build
./contrail.sh install
./contrail.sh configure
./contrail.sh start
```
* Then clone devstack:
```
git clone git@github.com:openstack-dev/devstack
```
* A glue file is needed in the interim till it is upstreamed to devstack:
```
cp ~/contrail-installer/devstack/lib/neutron_plugins/opencontrail lib/neutron_plugins/
```
* Use a sample ``localrc``:
```
cp ~/contrail-installer/devstack/samples/localrc-all localrc
```
* add the following to enable the OpenContrail driver for the BGPVPN service plugin::
NETWORKING_BGPVPN_DRIVER="BGPVPN:OpenContrail:networking_bgpvpn.neutron.services.service_drivers.opencontrail.opencontrail.OpenContrailBGPVPNDriver:default"
* Run stack.sh
```
./stack.sh
```
.. _OpenContrail SDN controller : https://github.com/Juniper/contrail-controller
.. _contrail installer : https://github.com/Juniper/contrail-installer

View File

@@ -136,7 +136,7 @@ The BGPVPN service plugin support or will support the following drivers:
* *bagpipe*, the reference driver working jointly with the *openvswitch* ML2 mechanism driver (see :doc:`bagpipe/index`)
* *opencontrail*, for [OpenContrail]_ (work in progress)
* *opencontrail*, for [OpenContrail]_ (see :doc:`opencontrail/index`)
* *opendaylight*, for [OpenDaylight]_ (work in progress)
@@ -146,6 +146,7 @@ The BGPVPN service plugin support or will support the following drivers:
:maxdepth: 1
bagpipe/index
opencontrail/index
==========
References

View File

@@ -17,17 +17,18 @@ import sqlalchemy as sa
from oslo_utils import uuidutils
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.i18n import _
from neutron.i18n import _LI
from oslo_log import log
from sqlalchemy import orm
from sqlalchemy.orm import exc
from networking_bgpvpn.neutron.extensions import bgpvpn as bgpvpn_ext
from networking_bgpvpn.neutron.services.common import utils
LOG = log.getLogger(__name__)
@@ -66,32 +67,9 @@ class BGPVPN(model_base.BASEV2, models_v2.HasId, models_v2.HasTenant):
cascade='all, delete-orphan')
class BGPVPNNotFound(q_exc.NotFound):
message = _("BGPVPN %(id)s could not be found")
class BGPVPNNetAssocNotFound(q_exc.NotFound):
message = _("BGPVPN network association %(id)s could not be found"
" for BGPVPN %(bgpvpn_id)s")
class BGPVPNPluginDb(common_db_mixin.CommonDbMixin):
"""BGPVPN service plugin database class using SQLAlchemy models."""
def _rtrd_list2str(self, list):
"""Format Route Target list to string"""
if not list:
return ''
return ','.join(list)
def _rtrd_str2list(self, str):
"""Format Route Target string to list"""
if not str:
return []
return str.split(',')
def _get_bgpvpns_for_tenant(self, session, tenant_id, fields):
try:
qry = session.query(BGPVPN)
@@ -112,22 +90,22 @@ class BGPVPNPluginDb(common_db_mixin.CommonDbMixin):
'name': bgpvpn_db['name'],
'type': bgpvpn_db['type'],
'route_targets':
self._rtrd_str2list(bgpvpn_db['route_targets']),
utils.rtrd_str2list(bgpvpn_db['route_targets']),
'route_distinguishers':
self._rtrd_str2list(bgpvpn_db['route_distinguishers']),
utils.rtrd_str2list(bgpvpn_db['route_distinguishers']),
'import_targets':
self._rtrd_str2list(bgpvpn_db['import_targets']),
utils.rtrd_str2list(bgpvpn_db['import_targets']),
'export_targets':
self._rtrd_str2list(bgpvpn_db['export_targets']),
utils.rtrd_str2list(bgpvpn_db['export_targets']),
'auto_aggregate': bgpvpn_db['auto_aggregate']
}
return self._fields(res, fields)
def create_bgpvpn(self, context, bgpvpn):
rt = self._rtrd_list2str(bgpvpn['route_targets'])
i_rt = self._rtrd_list2str(bgpvpn['import_targets'])
e_rt = self._rtrd_list2str(bgpvpn['export_targets'])
rd = self._rtrd_list2str(bgpvpn.get('route_distinguishers', ''))
rt = utils.rtrd_list2str(bgpvpn['route_targets'])
i_rt = utils.rtrd_list2str(bgpvpn['import_targets'])
e_rt = utils.rtrd_list2str(bgpvpn['export_targets'])
rd = utils.rtrd_list2str(bgpvpn.get('route_distinguishers', ''))
with context.session.begin(subtransactions=True):
bgpvpn_db = BGPVPN(
@@ -146,15 +124,14 @@ class BGPVPNPluginDb(common_db_mixin.CommonDbMixin):
return self._make_bgpvpn_dict(bgpvpn_db)
def get_bgpvpns(self, context, filters=None, fields=None):
return self._get_collection(context, BGPVPN,
self._make_bgpvpn_dict,
return self._get_collection(context, BGPVPN, self._make_bgpvpn_dict,
filters=filters, fields=fields)
def _get_bgpvpn(self, context, id):
try:
return self._get_by_id(context, BGPVPN, id)
except exc.NoResultFound:
raise BGPVPNNotFound(id=id)
raise bgpvpn_ext.BGPVPNNotFound(id=id)
def get_bgpvpn(self, context, id, fields=None):
bgpvpn_db = self._get_bgpvpn(context, id)
@@ -167,17 +144,16 @@ class BGPVPNPluginDb(common_db_mixin.CommonDbMixin):
if bgpvpn:
# Format Route Target lists to string
if 'route_targets' in bgpvpn:
rt = self._rtrd_list2str(bgpvpn['route_targets'])
rt = utils.rtrd_list2str(bgpvpn['route_targets'])
bgpvpn['route_targets'] = rt
if 'import_targets' in bgpvpn:
i_rt = self._rtrd_list2str(bgpvpn['import_targets'])
i_rt = utils.rtrd_list2str(bgpvpn['import_targets'])
bgpvpn['import_targets'] = i_rt
if 'export_targets' in bgpvpn:
e_rt = self._rtrd_list2str(bgpvpn['export_targets'])
e_rt = utils.rtrd_list2str(bgpvpn['export_targets'])
bgpvpn['export_targets'] = e_rt
if 'route_distinguishers' in bgpvpn:
rd = self._rtrd_list2str(
bgpvpn['route_distinguishers'])
rd = utils.rtrd_list2str(bgpvpn['route_distinguishers'])
bgpvpn['route_distinguishers'] = rd
bgpvpn_db.update(bgpvpn)
@@ -216,7 +192,8 @@ class BGPVPNPluginDb(common_db_mixin.CommonDbMixin):
BGPVPNNetAssociation.bgpvpn_id == bgpvpn_id
).one()
except exc.NoResultFound:
raise BGPVPNNetAssocNotFound(id=assoc_id, bgpvpn_id=bgpvpn_id)
raise bgpvpn_ext.BGPVPNNetAssocNotFound(id=assoc_id,
bgpvpn_id=bgpvpn_id)
def create_net_assoc(self, context, bgpvpn_id, net_assoc):
with context.session.begin(subtransactions=True):

View File

@@ -22,6 +22,7 @@ from neutron.api import extensions
from neutron.api.v2 import attributes as attr
from neutron.api.v2 import base
from neutron.api.v2 import resource_helper
from neutron.common import exceptions as n_exc
from neutron import manager
from neutron.plugins.common import constants as n_const
from neutron.services.service_base import ServicePluginBase
@@ -42,6 +43,24 @@ extensions.append_api_extensions_path(bgpvpn_ext.__path__)
n_const.EXT_TO_SERVICE_MAPPING['bgpvpn'] = constants.BGPVPN
class BGPVPNNotFound(n_exc.NotFound):
message = _("BGPVPN %(id)s could not be found")
class BGPVPNNetAssocNotFound(n_exc.NotFound):
message = _("BGPVPN network association %(id)s could not be found "
"for BGPVPN %(bgpvpn_id)s")
class BGPVPNTypeNotSupported(n_exc.BadRequest):
message = _("BGPVPN %(driver)s driver does not support %(type)s type")
class BGPVPNRDNotSupported(n_exc.BadRequest):
message = _("BGPVPN %(driver)s driver does not support to manually set "
"route distinguisher")
def _validate_rt_list(data, valid_values=None):
if not isinstance(data, list):
msg = _("'%s' is not a list") % data

View File

@@ -0,0 +1,103 @@
# Copyright (c) 2015 Cloudwatt.
# 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 six
def rtrd_list2str(list):
"""Format Route Target list to string"""
if not list:
return ''
if isinstance(list, str):
return list
return ','.join(list)
def rtrd_str2list(str):
"""Format Route Target string to list"""
if not str:
return []
if isinstance(str, list):
return str
return str.split(',')
def filter_resource(resource, filters=None):
if not filters:
filters = {}
for key, value in six.iteritems(filters):
if key in resource.keys():
if isinstance(value, list):
if resource[key] not in value:
return False
elif resource[key] != value:
return False
return True
def filter_fields(resource, fields):
if fields:
return dict(((key, item) for key, item in resource.items()
if key in fields))
return resource
def make_bgpvpn_dict(bgpvpn, fields=None):
res = {
'id': bgpvpn['id'],
'tenant_id': bgpvpn['tenant_id'],
'name': bgpvpn['name'],
'type': bgpvpn['type'],
'route_targets': rtrd_str2list(bgpvpn['route_targets']),
'import_targets': rtrd_str2list(bgpvpn['import_targets']),
'export_targets': rtrd_str2list(bgpvpn['export_targets']),
'route_distinguishers': rtrd_str2list(bgpvpn['route_distinguishers']),
'auto_aggregate': bgpvpn['auto_aggregate'],
'networks': bgpvpn.get('networks', []),
}
return filter_fields(res, fields)
def make_net_assoc_dict(id, tenant_id, bgpvpn_id, network_id, fields=None):
res = {'id': id,
'tenant_id': tenant_id,
'bgpvpn_id': bgpvpn_id,
'network_id': network_id}
return filter_fields(res, fields)
def get_bgpvpn_differences(current_dict, old_dict):
"""Compare 2 BGP VPN
- added elements (route_targets, import_targets or export_targets)
- removed elements (route_targets, import_targets or export_targets)
- changed values for keys in both dictionaries (network_id,
route_targets, import_targets or export_targets)
"""
set_current = set(current_dict.keys())
set_old = set(old_dict.keys())
intersect = set_current.intersection(set_old)
added = set_current - intersect
removed = set_old - intersect
changed = set(
key for key in intersect if old_dict[key] != current_dict[key]
)
return (added, removed, changed)

View File

@@ -26,6 +26,7 @@ from neutron.common import constants as const
from neutron.db import models_v2
from oslo_log import log as logging
from networking_bgpvpn.neutron.services.common import utils
from networking_bgpvpn.neutron.services.service_drivers import driver_api
from networking_bagpipe.agent.bgpvpn import rpc_client
@@ -193,26 +194,6 @@ class BaGPipeBGPVPNDriver(driver_api.BGPVPNDriver):
return bgpvpn_network_info
def _get_bgpvpn_differences(self, current_dict, old_dict):
"""Compare 2 BGPVPNs
- added elements (route_targets, import_targets or export_targets)
- removed elements (route_targets, import_targets or export_targets)
- changed values for keys in both dictionaries (network_id,
route_targets, import_targets or export_targets)
"""
set_current = set(current_dict.keys())
set_old = set(old_dict.keys())
intersect = set_current.intersection(set_old)
added = set_current - intersect
removed = set_old - intersect
changed = set(
key for key in intersect if old_dict[key] != current_dict[key]
)
return (added, removed, changed)
def delete_bgpvpn_postcommit(self, context, bgpvpn):
for net_id in bgpvpn['networks']:
if get_network_ports(context, net_id):
@@ -223,7 +204,7 @@ class BaGPipeBGPVPNDriver(driver_api.BGPVPNDriver):
def update_bgpvpn_postcommit(self, context, old_bgpvpn, bgpvpn):
(added_keys, removed_keys, changed_keys) = (
self._get_bgpvpn_differences(bgpvpn, old_bgpvpn))
utils.get_bgpvpn_differences(bgpvpn, old_bgpvpn))
for net_id in bgpvpn['networks']:
if (get_network_ports(context, net_id)):
if ((key in added_keys for key in ('route_targets',

View File

@@ -24,7 +24,7 @@ class BGPVPNDriverBase(object):
"""BGPVPNDriver interface for driver
That driver interface does not persist BGPVPN data in any database. The
driver need to do it by itself.
driver needs to do it by itself.
"""
def __init__(self, service_plugin):

View File

@@ -0,0 +1,59 @@
# Copyright (C) 2015 Cloudwatt
# 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 neutron.common import exceptions as n_exc
# OpenContrail client API exceptions
class OpenContrailAPIFailed(n_exc.NeutronException):
message = _("Could not reach OpenContrail API server : %(url)s"
"Exception: %(excption)s.")
class OpenContrailAPIError(n_exc.NeutronException):
message = _('OpenContrail API returned %(status)s%(reason)s')
class OpenContrailAPINotSupported(n_exc.BadRequest):
message = _('OpenContrail API client cannot %(action)s on %(resource)s')
class OpenContrailAPIBadFqName(n_exc.BadRequest):
message = _("Bad fq_name for forming a fq_name to ID request")
class OpenContrailAPIBadUUID(n_exc.BadRequest):
message = _("Bad UUID for forming a UUID to fq_name request")
class OpenContrailAPIBadKVAttributes(n_exc.BadRequest):
message = _("Bad attributes for forming a key/value store request")
class OpenContrailAPINotAuthorized(n_exc.NotAuthorized):
pass
class OpenContrailAPINotFound(n_exc.NotFound):
message = _("%(resource)s%(id)s does not exist")
class OpenContrailAPIConflict(n_exc.Conflict):
message = _("OpenContrail API conflict: %(reason)s")
class OpenContrailAPIBadRequest(n_exc.BadRequest):
message = _("OpenContrail API bad request: %(reason)s")

View File

@@ -0,0 +1,392 @@
# Copyright (c) 2015 Cloudwatt.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_log import log
from oslo_utils import uuidutils
import json
from neutron.common import exceptions as n_exc
from neutron.i18n import _LI
from networking_bgpvpn.neutron.extensions import bgpvpn as bgpvpn_ext
from networking_bgpvpn.neutron.services.common import constants
from networking_bgpvpn.neutron.services.common import utils
from networking_bgpvpn.neutron.services.service_drivers import driver_api
from networking_bgpvpn.neutron.services.service_drivers.opencontrail import \
exceptions as oc_exc
from networking_bgpvpn.neutron.services.service_drivers.opencontrail import \
opencontrail_client
OPENCONTRAIL_BGPVPN_DRIVER_NAME = 'OpenContrail'
LOG = log.getLogger(__name__)
class OpenContrailBGPVPNDriver(driver_api.BGPVPNDriverBase):
"""BGP VPN Service Driver class for OpenContrail."""
def __init__(self, service_plugin):
super(OpenContrailBGPVPNDriver, self).__init__(service_plugin)
LOG.debug("OpenContrailBGPVPNDriver service_plugin : %s",
service_plugin)
def _get_opencontrail_api_client(self, context):
return opencontrail_client.OpenContrailAPIBaseClient()
def _locate_rt(self, oc_client, rt_fq_name):
try:
rt_uuid = oc_client.fqname_to_id('route-target', rt_fq_name)
except oc_exc.OpenContrailAPINotFound:
body = {
'route-target': {
"fq_name": [':'.join(rt_fq_name)]
}
}
rt_uuid = oc_client.create('Route Target', body)['uuid']
return rt_uuid
def _clean_route_targets(self, oc_client, rts_fq_name):
for rt_fq_name in rts_fq_name:
try:
rt_uuid = oc_client.fqname_to_id('route-target', rt_fq_name)
except oc_exc.OpenContrailAPINotFound:
continue
rt = oc_client.show('Route Target', rt_uuid)
if 'routing_instance_back_refs' not in rt.keys():
# rt not use anymore, remove it
rt = oc_client.remove('Route Target', rt_uuid)
def _update_rt_ri_association(self, oc_client, operation, ri_id,
rt_fq_name, import_export=None):
rt_uuid = self._locate_rt(oc_client, rt_fq_name)
kwargs = {
"operation": operation,
"resource_type": "routing-instance",
"resource_uuid": ri_id,
"ref_type": "route-target",
"ref_fq_name": rt_fq_name,
"ref_uuid": rt_uuid,
"attributes": {
"import_export": import_export
}
}
oc_client.ref_update(**kwargs)
if operation == 'DELETE':
self._clean_route_targets(oc_client, [rt_fq_name])
def _get_ri_id_of_network(self, oc_client, network_id):
try:
network = oc_client.show('Virtual Network', network_id)
ri_fq_name = network['fq_name'] + [network['fq_name'][-1]]
for ri_ref in network.get('routing_instances', []):
if ri_ref['to'] == ri_fq_name:
return ri_ref['uuid']
except (oc_exc.OpenContrailAPINotFound, IndexError):
raise n_exc.NetworkNotFound(net_id=network_id)
raise n_exc.NetworkNotFound(net_id=network_id)
def _set_bgpvpn_association(self, oc_client, operation, bgpvpn,
networks=[]):
for network_id in networks:
try:
net_ri_id = self._get_ri_id_of_network(oc_client, network_id)
except n_exc.NetworkNotFound:
LOG.info(_LI("Network %s not found, cleanup route targets"),
network_id)
rts_fq_name = (bgpvpn['route_targets'] +
bgpvpn['import_targets'] +
bgpvpn['export_targets'])
rts_fq_name = [['target'] + rt.split(':') for rt in
rts_fq_name]
self._clean_route_targets(oc_client, rts_fq_name)
return bgpvpn
if bgpvpn['type'] == constants.BGPVPN_L3:
for rt in bgpvpn['route_targets']:
rt_fq_name = ['target'] + rt.split(':')
self._update_rt_ri_association(oc_client, operation,
net_ri_id, rt_fq_name)
for rt in bgpvpn['import_targets']:
rt_fq_name = ['target'] + rt.split(':')
self._update_rt_ri_association(oc_client, operation,
net_ri_id, rt_fq_name,
import_export="import")
for rt in bgpvpn['export_targets']:
rt_fq_name = ['target'] + rt.split(':')
self._update_rt_ri_association(oc_client, operation,
net_ri_id, rt_fq_name,
import_export="export")
return bgpvpn
def create_bgpvpn(self, context, bgpvpn):
LOG.debug("create_bgpvpn_ called with %s" % bgpvpn)
# Only support l3 technique
if not bgpvpn['type']:
bgpvpn['type'] = constants.BGPVPN_L3
elif bgpvpn['type'] != constants.BGPVPN_L3:
raise bgpvpn_ext.BGPVPNTypeNotSupported(
driver=OPENCONTRAIL_BGPVPN_DRIVER_NAME, type=bgpvpn['type'])
# Does not support to set route distinguisher
if 'route_distinguishers' in bgpvpn and bgpvpn['route_distinguishers']:
raise bgpvpn_ext.BGPVPNRDNotSupported(
driver=OPENCONTRAIL_BGPVPN_DRIVER_NAME)
bgpvpn['id'] = uuidutils.generate_uuid()
oc_client = self._get_opencontrail_api_client(context)
oc_client.kv_store('STORE', key=bgpvpn['id'], value={'bgpvpn': bgpvpn})
return utils.make_bgpvpn_dict(bgpvpn)
def get_bgpvpns(self, context, filters=None, fields=None):
LOG.debug("get_bgpvpns called, fields = %s, filters = %s"
% (fields, filters))
oc_client = self._get_opencontrail_api_client(context)
bgpvpns = []
for kv_dict in oc_client.kv_store('RETRIEVE'):
try:
value = json.loads(kv_dict['value'])
except ValueError:
continue
if (isinstance(value, dict) and
'bgpvpn' in value and
utils.filter_resource(value['bgpvpn'], filters)):
bgpvpn = value['bgpvpn']
if not fields or 'networks' in fields:
bgpvpn['networks'] = \
[net_assoc['network_id'] for net_assoc in
self.get_net_assocs(context, bgpvpn['id'])]
bgpvpns.append(utils.make_bgpvpn_dict(bgpvpn, fields))
return bgpvpns
def _clean_bgpvpn_assoc(self, oc_client, bgpvpn_id):
for kv_dict in oc_client.kv_store('RETRIEVE'):
try:
value = json.loads(kv_dict['value'])
except ValueError:
continue
if (isinstance(value, dict) and
'bgpvpn_net_assoc' in value and
value['bgpvpn_net_assoc']['bgpvpn_id'] == bgpvpn_id):
assoc_id = value['bgpvpn_net_assoc']['id']
oc_client.kv_store('DELETE', key=assoc_id)
def get_bgpvpn(self, context, id, fields=None):
LOG.debug("get_bgpvpn called for id %s with fields = %s"
% (id, fields))
oc_client = self._get_opencontrail_api_client(context)
try:
bgpvpn = json.loads(oc_client.kv_store('RETRIEVE', key=id))
except (oc_exc.OpenContrailAPINotFound, ValueError):
raise bgpvpn.BGPVPNNotFound(id=id)
if (not isinstance(bgpvpn, dict) or 'bgpvpn' not in bgpvpn):
raise bgpvpn.BGPVPNNotFound(id=id)
bgpvpn = bgpvpn['bgpvpn']
if not fields or 'networks' in fields:
bgpvpn['networks'] = [net_assoc['network_id'] for net_assoc in
self.get_net_assocs(context, id)]
return utils.make_bgpvpn_dict(bgpvpn, fields)
def update_bgpvpn(self, context, id, new_bgpvpn):
LOG.debug("update_bgpvpn called with %s for %s" % (new_bgpvpn, id))
oc_client = self._get_opencontrail_api_client(context)
old_bgpvpn = self.get_bgpvpn(context, id)
networks = old_bgpvpn.get('networks', [])
bgpvpn = old_bgpvpn.copy()
bgpvpn.update(new_bgpvpn)
(added_keys, removed_keys, changed_keys) = \
utils.get_bgpvpn_differences(bgpvpn, old_bgpvpn)
if not (added_keys or removed_keys or changed_keys):
return utils.make_bgpvpn_dict(bgpvpn)
# Does not support to update route distinguisher
if 'route_distinguishers' in added_keys | removed_keys | changed_keys:
raise bgpvpn_ext.BGPVPNRDNotSupported(
driver=OPENCONTRAIL_BGPVPN_DRIVER_NAME)
rt_keys = set(['route_targets',
'import_targets',
'export_targets'])
if (rt_keys & added_keys or
rt_keys & changed_keys or
rt_keys & removed_keys):
self._set_bgpvpn_association(oc_client, 'DELETE', old_bgpvpn,
networks)
self._set_bgpvpn_association(oc_client, 'ADD', bgpvpn, networks)
oc_client.kv_store('STORE', key=id, value={'bgpvpn': bgpvpn})
return utils.make_bgpvpn_dict(bgpvpn)
def delete_bgpvpn(self, context, id):
LOG.debug("delete_bgpvpn called for id %s" % id)
bgpvpn = self.get_bgpvpn(context, id)
networks = bgpvpn.get('networks', [])
oc_client = self._get_opencontrail_api_client(context)
self._set_bgpvpn_association(oc_client, 'DELETE', bgpvpn, networks)
self._clean_bgpvpn_assoc(oc_client, id)
oc_client.kv_store('DELETE', key=id)
def create_net_assoc(self, context, bgpvpn_id, network_association):
LOG.debug("create_net_assoc called for bgpvpn %s with network %s"
% (bgpvpn_id, network_association['network_id']))
bgpvpn = self.get_bgpvpn(context, bgpvpn_id)
oc_client = self._get_opencontrail_api_client(context)
network_id = network_association['network_id']
if network_id not in bgpvpn.get('networks', []):
assoc_uuid = uuidutils.generate_uuid()
self._set_bgpvpn_association(oc_client, 'ADD', bgpvpn,
[network_id])
assoc_dict = utils.make_net_assoc_dict(
assoc_uuid, network_association['tenant_id'],
bgpvpn_id, network_association['network_id'])
oc_client.kv_store('STORE', key=assoc_uuid,
value={'bgpvpn_net_assoc': assoc_dict})
return assoc_dict
else:
# the tuple (bgpvpn_id, network_id) is necessarily unique
return self.get_net_assocs(context, bgpvpn_id,
filters={'network_id': network_id})[0]
def get_net_assoc(self, context, assoc_id, bgpvpn_id, fields=None):
LOG.debug("get_net_assoc called for %s for BGPVPN %s, with fields = %s"
% (assoc_id, bgpvpn_id, fields))
oc_client = self._get_opencontrail_api_client(context)
try:
net_assoc = json.loads(
oc_client.kv_store('RETRIEVE', key=assoc_id))
except (oc_exc.OpenContrailAPINotFound, ValueError):
raise bgpvpn_ext.BGPVPNNetAssocNotFound(id=assoc_id,
bgpvpn_id=bgpvpn_id)
if (not isinstance(net_assoc, dict) or
'bgpvpn_net_assoc' not in net_assoc):
raise bgpvpn_ext.BGPVPNNetAssocNotFound(id=assoc_id,
bgpvpn_id=bgpvpn_id)
net_assoc = net_assoc['bgpvpn_net_assoc']
if net_assoc['bgpvpn_id'] != bgpvpn_id:
raise bgpvpn_ext.BGPVPNNetAssocNotFound(id=assoc_id,
bgpvpn_id=bgpvpn_id)
# It the bgpvpn was deleted, the 'get_bgpvpn' will clean all related
# associations and replaces BGPVPNNotFound by a BGPVPNNetAssocNotFound
try:
get_fields = ['tenant_id', 'route_targets', 'import_targets',
'export_targets']
bgpvpn = self.get_bgpvpn(context, net_assoc['bgpvpn_id'],
fields=get_fields)
except bgpvpn.BGPVPNNotFound:
raise bgpvpn_ext.BGPVPNNetAssocNotFound(id=assoc_id,
bgpvpn_id=bgpvpn_id)
# If the network was delete all bgpvpn related association should be
# deleted also
try:
oc_client.id_to_fqname(net_assoc['network_id'])
except oc_exc.OpenContrailAPINotFound:
self._set_bgpvpn_association(oc_client, 'DELETE', bgpvpn,
[net_assoc['network_id']])
oc_client.kv_store('DELETE', key=assoc_id)
raise bgpvpn_ext.BGPVPNNetAssocNotFound(id=assoc_id,
bgpvpn_id=bgpvpn_id)
net_assoc = utils.make_net_assoc_dict(net_assoc['id'],
net_assoc['tenant_id'],
net_assoc['bgpvpn_id'],
net_assoc['network_id'],
fields)
return net_assoc
def get_net_assocs(self, context, bgpvpn_id, filters=None, fields=None):
LOG.debug("get_net_assocs called for bgpvpn %s, fields = %s, "
"filters = %s" % (bgpvpn_id, fields, filters))
oc_client = self._get_opencontrail_api_client(context)
get_fields = ['tenant_id', 'route_targets', 'import_targets',
'export_targets']
bgpvpn = self.get_bgpvpn(context, bgpvpn_id, fields=get_fields)
bgpvpn_net_assocs = []
for kv_dict in oc_client.kv_store('RETRIEVE'):
try:
value = json.loads(kv_dict['value'])
except ValueError:
continue
if (isinstance(value, dict) and
'bgpvpn_net_assoc' in value and
utils.filter_resource(value['bgpvpn_net_assoc'],
filters) and
value['bgpvpn_net_assoc']['bgpvpn_id'] == bgpvpn_id):
net_assoc = value['bgpvpn_net_assoc']
# If the network was delete all bgpvpn related association
# should be deleted also
try:
oc_client.id_to_fqname(net_assoc['network_id'])
except oc_exc.OpenContrailAPINotFound:
self._set_bgpvpn_association(oc_client, 'DELETE', bgpvpn,
[net_assoc['network_id']])
oc_client.kv_store('DELETE', key=net_assoc['id'])
continue
net_assoc = utils.make_net_assoc_dict(net_assoc['id'],
net_assoc['tenant_id'],
net_assoc['bgpvpn_id'],
net_assoc['network_id'],
fields)
bgpvpn_net_assocs.append(net_assoc)
return bgpvpn_net_assocs
def delete_net_assoc(self, context, assoc_id, bgpvpn_id):
LOG.debug("delete_net_assoc called for %s" % assoc_id)
net_assoc = self.get_net_assoc(context, assoc_id, bgpvpn_id)
fields = ['type', 'route_targets', 'import_targets', 'export_targets']
bgpvpn = self.get_bgpvpn(context, net_assoc['bgpvpn_id'],
fields=fields)
oc_client = self._get_opencontrail_api_client(context)
self._set_bgpvpn_association(oc_client, 'DELETE', bgpvpn,
[net_assoc['network_id']])
oc_client.kv_store('DELETE', key=assoc_id)
return net_assoc

View File

@@ -0,0 +1,356 @@
# Copyright (C) 2014 eNovance SAS <licensing@enovance.com>
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_config import cfg
from oslo_log import log
from oslo_utils import uuidutils
import json
import requests
import six
from six.moves import http_client as httplib
from six.moves.urllib import parse as urlparse
from networking_bgpvpn.neutron.services.service_drivers.opencontrail import \
exceptions as oc_exc
LOG = log.getLogger(__name__)
CONF = cfg.CONF
opencontrail_opts = [
cfg.IntOpt('request_timeout', default=30,
help='Timeout seconds for HTTP requests. Set it to None to '
'disable timeout.'),
]
CONF.register_opts(opencontrail_opts, 'APISERVER')
def get_auth_token():
DEFAULT_HEADERS = {
'Content-type': 'application/json; charset="UTF-8"',
'X-Contrail-Useragent': 'bgppn_opencontrail_client',
}
admin_user = cfg.CONF.keystone_authtoken.admin_user
admin_password = cfg.CONF.keystone_authtoken.admin_password
admin_tenant_name = cfg.CONF.keystone_authtoken.admin_tenant_name
auth_body = {
"auth": {
"passwordCredentials": {
"username": admin_user,
"password": admin_password
},
"tenantName": admin_tenant_name
}
}
try:
auth_type = CONF.keystone_authtoken.auth_type
except cfg.NoSuchOptError:
auth_type = "keystone"
if auth_type != "keystone":
return
try:
auth_url = cfg.CONF.keystone_authtoken.auth_url
except cfg.NoSuchOptError:
try:
auth_host = cfg.CONF.keystone_authtoken.auth_host
except cfg.NoSuchOptError:
auth_host = "127.0.0.1"
try:
auth_protocol = cfg.CONF.keystone_authtoken.auth_protocol
except cfg.NoSuchOptError:
auth_protocol = "http"
try:
auth_port = cfg.CONF.keystone_authtoken.auth_port
except cfg.NoSuchOptError:
auth_port = "35357"
auth_url = "%s://%s:%s" % (auth_protocol, auth_host, auth_port)
url = auth_url + '/v2.0/tokens'
response = requests.post(url, data=json.dumps(auth_body),
headers=DEFAULT_HEADERS)
if response.status_code == 200:
auth_content = json.loads(response.text)
return auth_content['access']['token']['id']
else:
raise RuntimeError("Authentication failure. Code: %d, reason: %s"
% (response.status_code, response.reason))
class RequestHandler(object):
"""Handles processing requests."""
def __init__(self):
self._host = CONF.APISERVER.api_server_ip
self._port = CONF.APISERVER.api_server_port
self._request_timeout = float(CONF.APISERVER.request_timeout)
self._api_url = 'http://' + self._host + ':' + str(self._port)
self._token = get_auth_token()
self._pool = requests.Session()
self._resource = ""
self._id = ""
def delete(self, url, body=None, headers=None, params=None):
return self._do_request("DELETE", url, body=body,
headers=headers, params=params)
def get(self, url, body=None, headers=None, params=None):
return self._do_request("GET", url, body=body,
headers=headers, params=params)
def post(self, url, body=None, headers=None, params=None):
return self._do_request("POST", url, body=body,
headers=headers, params=params)
def put(self, url, body=None, headers=None, params=None):
return self._do_request("PUT", url, body=body,
headers=headers, params=params)
def _do_request(self, method, url, body=None, headers=None,
params=None, retry_auth=True):
req_params = self._get_req_params(data=body)
if headers:
req_params['headers'].update(headers)
url = urlparse.urljoin(self._api_url, url)
if params and isinstance(params, dict):
url += '?' + urlparse.urlencode(params, doseq=1)
if url[-1] == '/':
url = url[:-1]
self._log_req(method, url, params, req_params)
try:
response = self._pool.request(method, url, **req_params)
except Exception as e:
raise oc_exc.OpenContrailAPIFailed(url=url, excption=e)
self._log_res(response)
if response.status_code == httplib.UNAUTHORIZED and retry_auth:
self._auth_token = get_auth_token()
return self._do_request(method, url, body=body, headers=headers,
params=params, retry_auth=False)
if response.status_code == httplib.UNAUTHORIZED and not retry_auth:
raise oc_exc.OpenContrailAPINotAuthorized
if response.status_code == httplib.NOT_FOUND:
raise oc_exc.OpenContrailAPINotFound(resource=self._resource,
id=self._id)
if response.status_code == httplib.CONFLICT:
raise oc_exc.OpenContrailAPIConflict(reason=response.reason)
if response.status_code == httplib.BAD_REQUEST:
raise oc_exc.OpenContrailAPIBadRequest(reason=response.reason)
if response.status_code is not httplib.OK:
raise oc_exc.OpenContrailAPIError(status=response.status_code,
reason=response.reason)
if response.content:
return response.json()
def _get_req_params(self, data=None):
req_params = {
'headers': {
'Content-type': 'application/json; charset="UTF-8"',
'Accept': "application/json",
'X-Auth-Token': self._token,
},
'allow_redirects': False,
'timeout': self._request_timeout,
}
if data:
req_params.update({'data': json.dumps(data)})
return req_params
@staticmethod
def _log_req(method, url, params, req_params):
if not CONF.debug:
return
curl_command = ['REQ: curl -i -X %s' % method]
if params and isinstance(params, dict):
url += '?' + urlparse.urlencode(params, doseq=1)
curl_command.append('"%s"' % url)
for name, value in six.iteritems(req_params['headers']):
curl_command.append('-H "%s: %s"' % (name, value))
if ('data' in req_params.keys()
and (isinstance(req_params['data'], dict)
or isinstance(req_params['data'], str))):
curl_command.append("-d '%s'" % (req_params['data']))
LOG.debug(''.join(curl_command))
@staticmethod
def _log_res(resp):
if CONF.debug:
dump = ['RES: \n', 'HTTP %.1f%s%s\n' % (resp.raw.version,
resp.status_code,
resp.reason)]
dump.extend('%s: %s\n' % (k, v)
for k, v in six.iteritems(resp.headers))
dump.append('\n')
if resp.content:
dump.extend([resp.content, '\n'])
LOG.debug(''.join(dump))
class OpenContrailAPIBaseClient(RequestHandler):
"""OpenContrail Base REST API Client."""
resource_path = {
'FQName to ID': '/fqname-to-id/',
'ID to FQName': '/id-to-fqname/',
'Ref Update': "/ref-update/",
'Virtual Network': '/virtual-networks/',
'Routing Instance': '/routing-instances/',
'Route Target': '/route-targets/',
'Key Value Store': '/useragent-kv/',
}
def list(self, resource, **params):
"""Fetches a list of resources."""
res = self.resource_path.get(resource, None)
if not res:
raise oc_exc.OpenContrailAPINotSupported(action='list',
resource=resource)
self._resource = resource
return self.get(res, params=params)
def show(self, resource, id, **params):
"""Fetches information of a certain resource."""
res = self.resource_path.get(resource, None)
if not res:
raise oc_exc.OpenContrailAPINotSupported(action='show',
resource=resource)
if res[-2:] == 's/':
res = res[:-2] + '/'
self._resource = resource
self._id = id
return self.get(res + id, params=params).popitem()[1]
def create(self, resource, body):
"""Creates a new resource."""
res = self.resource_path.get(resource, None)
if not res:
raise oc_exc.OpenContrailAPINotSupported(action='create',
resource=resource)
self._resource = resource
resp = self.post(res, body=body)
if resp:
return resp.popitem()[1]
def update(self, resource, id, body=None):
"""Updates a resource."""
res = self.resource_path.get(resource, None)
if not res:
raise oc_exc.OpenContrailAPINotSupported(action='update',
resource=resource)
if res[-2:] == 's/':
res = res[:-2] + '/'
self._resource = resource
self._id = id
return self.put(res + id, body=body)
def remove(self, resource, id):
"""Removes the specified resource."""
res = self.resource_path.get(resource, None)
if not res:
raise oc_exc.OpenContrailAPINotSupported(action='delete',
resource=resource)
if res[-2:] == 's/':
res = res[:-2] + '/'
self._resource = resource
self._id = id
return self.delete(res + id)
def fqname_to_id(self, resource, fq_name):
"""Get UUID resource from an OpenContrail fq_name"""
if not isinstance(fq_name, list):
raise oc_exc.OpenContrailAPIBadFqName
body = {'fq_name': fq_name, 'type': resource}
return self.create('FQName to ID', body)
def id_to_fqname(self, id):
"""Get fq_name resource from an OpenContrail UUID"""
if not uuidutils.is_uuid_like(id):
raise oc_exc.OpenContrailAPIBadUUID
body = {'uuid': id}
return self.create('ID to FQName', body)
def ref_update(self, operation, resource_uuid, resource_type, ref_type,
ref_fq_name, ref_uuid, attributes=None):
"""Updates a resource refference"""
body = {
"operation": operation,
"type": resource_type,
"uuid": resource_uuid,
"ref-type": ref_type,
"ref-fq-name": ref_fq_name,
"ref-uuid": ref_uuid,
}
if attributes and isinstance(attributes, dict):
body.update({'attr': attributes})
return self.create('Ref Update', body)
def kv_store(self, operation, key=None, value=None):
"""Use key/value store exposed by the OpenContrail API"""
body = {
'operation': operation,
'key': None,
}
if operation == 'RETRIEVE' and not key:
pass
elif operation in ['RETRIEVE', 'DELETE'] and key:
body.update({'key': key})
elif operation == 'STORE' and key and value:
body.update({'key': key, 'value': json.dumps(value)})
else:
raise oc_exc.OpenContrailAPIBadKVAttributes
return self.create('Key Value Store', body)

View File

@@ -15,9 +15,9 @@
from neutron import context
from networking_bgpvpn.neutron.db.bgpvpn_db import BGPVPNNetAssocNotFound
from networking_bgpvpn.neutron.db.bgpvpn_db import BGPVPNNotFound
from networking_bgpvpn.neutron.db.bgpvpn_db import BGPVPNPluginDb
from networking_bgpvpn.neutron.extensions.bgpvpn import BGPVPNNetAssocNotFound
from networking_bgpvpn.neutron.extensions.bgpvpn import BGPVPNNotFound
from networking_bgpvpn.tests.unit.services import test_plugin
Reference in New Issue
openstack/networking-bgpvpn
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.

The note is not visible to the blocked user.