summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/cli/osc/v2/networking-bgpvpn.rst3
-rw-r--r--neutronclient/osc/v2/networking_bgpvpn/bgpvpn.py2
-rw-r--r--neutronclient/osc/v2/networking_bgpvpn/constants.py4
-rw-r--r--neutronclient/osc/v2/networking_bgpvpn/port_association.py315
-rw-r--r--neutronclient/osc/v2/networking_bgpvpn/resource_association.py79
-rw-r--r--neutronclient/osc/v2/networking_bgpvpn/router_association.py2
-rw-r--r--neutronclient/tests/unit/osc/v2/networking_bgpvpn/fakes.py25
-rw-r--r--neutronclient/tests/unit/osc/v2/networking_bgpvpn/test_bgpvpn.py2
-rw-r--r--neutronclient/tests/unit/osc/v2/networking_bgpvpn/test_resource_association.py31
-rw-r--r--neutronclient/v2_0/client.py31
-rw-r--r--releasenotes/notes/support-bgpvpn-route-control-aeda3e698486f73b.yaml6
-rw-r--r--setup.cfg6
12 files changed, 499 insertions, 7 deletions
diff --git a/doc/source/cli/osc/v2/networking-bgpvpn.rst b/doc/source/cli/osc/v2/networking-bgpvpn.rst
index 371e472..46ee47e 100644
--- a/doc/source/cli/osc/v2/networking-bgpvpn.rst
+++ b/doc/source/cli/osc/v2/networking-bgpvpn.rst
@@ -32,3 +32,6 @@ Network v2
.. autoprogram-cliff:: openstack.neutronclient.v2
:command: bgpvpn router association *
+
+.. autoprogram-cliff:: openstack.neutronclient.v2
+ :command: bgpvpn port association *
diff --git a/neutronclient/osc/v2/networking_bgpvpn/bgpvpn.py b/neutronclient/osc/v2/networking_bgpvpn/bgpvpn.py
index 99d9c4e..63b4291 100644
--- a/neutronclient/osc/v2/networking_bgpvpn/bgpvpn.py
+++ b/neutronclient/osc/v2/networking_bgpvpn/bgpvpn.py
@@ -40,6 +40,7 @@ _attr_map = (
nc_osc_utils.LIST_LONG_ONLY),
('networks', 'Associated Networks', nc_osc_utils.LIST_LONG_ONLY),
('routers', 'Associated Routers', nc_osc_utils.LIST_LONG_ONLY),
+ ('ports', 'Associated Ports', nc_osc_utils.LIST_LONG_ONLY),
('vni', 'VNI', nc_osc_utils.LIST_LONG_ONLY),
)
_formatters = {
@@ -49,6 +50,7 @@ _formatters = {
'route_distinguishers': format_columns.ListColumn,
'networks': format_columns.ListColumn,
'routers': format_columns.ListColumn,
+ 'ports': format_columns.ListColumn,
}
diff --git a/neutronclient/osc/v2/networking_bgpvpn/constants.py b/neutronclient/osc/v2/networking_bgpvpn/constants.py
index 775721e..7de1329 100644
--- a/neutronclient/osc/v2/networking_bgpvpn/constants.py
+++ b/neutronclient/osc/v2/networking_bgpvpn/constants.py
@@ -24,3 +24,7 @@ NETWORK_ASSOCS = '%ss' % NETWORK_ASSOC
ROUTER_RESOURCE_NAME = 'router'
ROUTER_ASSOC = '%s_association' % ROUTER_RESOURCE_NAME
ROUTER_ASSOCS = '%ss' % ROUTER_ASSOC
+
+PORT_RESOURCE_NAME = 'port'
+PORT_ASSOC = '%s_association' % PORT_RESOURCE_NAME
+PORT_ASSOCS = '%ss' % PORT_ASSOC
diff --git a/neutronclient/osc/v2/networking_bgpvpn/port_association.py b/neutronclient/osc/v2/networking_bgpvpn/port_association.py
new file mode 100644
index 0000000..75d5c0f
--- /dev/null
+++ b/neutronclient/osc/v2/networking_bgpvpn/port_association.py
@@ -0,0 +1,315 @@
+# Copyright (c) 2017 Juniper networks Inc.
+# 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 logging
+
+from osc_lib.cli import format_columns
+from osc_lib.cli import parseractions
+
+from neutronclient._i18n import _
+from neutronclient.osc import utils as nc_osc_utils
+from neutronclient.osc.v2.networking_bgpvpn import constants
+from neutronclient.osc.v2.networking_bgpvpn import resource_association
+
+LOG = logging.getLogger(__name__)
+
+
+class BgpvpnPortAssoc(object):
+ _assoc_res_name = constants.PORT_RESOURCE_NAME
+ _resource = constants.PORT_ASSOC
+ _resource_plural = constants.PORT_ASSOCS
+
+ _attr_map = (
+ ('id', 'ID', nc_osc_utils.LIST_BOTH),
+ ('tenant_id', 'Project', nc_osc_utils.LIST_LONG_ONLY),
+ ('%s_id' % _assoc_res_name, '%s ID' % _assoc_res_name.capitalize(),
+ nc_osc_utils.LIST_BOTH),
+ ('prefix_routes', 'Prefix Routes (BGP LOCAL_PREF)',
+ nc_osc_utils.LIST_LONG_ONLY),
+ ('bgpvpn_routes', 'BGP VPN Routes (BGP LOCAL_PREF)',
+ nc_osc_utils.LIST_LONG_ONLY),
+ ('advertise_fixed_ips', "Advertise Port's Fixed IPs",
+ nc_osc_utils.LIST_LONG_ONLY),
+ )
+ _formatters = {
+ 'prefix_routes': format_columns.ListColumn,
+ 'bgpvpn_routes': format_columns.ListColumn,
+ }
+
+ def _transform_resource(self, data):
+ """Transforms BGP VPN port association routes property
+
+ That permits to easily format the command output with ListColumn
+ formater and separate the two route types.
+
+ {'routes':
+ [
+ {
+ 'type': 'prefix',
+ 'local_pref': 100,
+ 'prefix': '8.8.8.0/27',
+ },
+ {
+ 'type': 'prefix',
+ 'local_pref': 42,
+ 'prefix': '80.50.30.0/28',
+ },
+ {
+ 'type': 'bgpvpn',
+ 'local_pref': 50,
+ 'bgpvpn': '157d72a9-9968-48e7-8087-6c9a9bc7a181',
+ },
+ {
+ 'type': 'bgpvpn',
+ 'bgpvpn': 'd5c7aaab-c7e8-48b3-85ca-a115c00d3603',
+ },
+ ],
+ }
+
+ to
+
+ {
+ 'prefix_routes': [
+ '8.8.8.0/27 (100)',
+ '80.50.30.0/28 (42)',
+ ],
+ 'bgpvpn_routes': [
+ '157d72a9-9968-48e7-8087-6c9a9bc7a181 (50)',
+ 'd5c7aaab-c7e8-48b3-85ca-a115c00d3603',
+ ],
+ }
+ """
+ for route in data.get('routes', []):
+ local_pref = ''
+ if route.get('local_pref'):
+ local_pref = ' (%d)' % route.get('local_pref')
+ if route['type'] == 'prefix':
+ data.setdefault('prefix_routes', []).append(
+ '%s%s' % (route['prefix'], local_pref)
+ )
+ elif route['type'] == 'bgpvpn':
+ data.setdefault('bgpvpn_routes', []).append(
+ '%s%s' % (route['bgpvpn_id'], local_pref)
+ )
+ else:
+ LOG.warning("Unknown route type %s (%s).", route['type'],
+ route)
+ data.pop('routes', None)
+
+ def _get_common_parser(self, parser):
+ """Adds to parser arguments common to create, set and unset commands.
+
+ :params ArgumentParser parser: argparse object contains all command's
+ arguments
+ """
+ ADVERTISE_ROUTE = _("Fixed IPs of the port will be advertised to the "
+ "BGP VPN%s") % (
+ _(' (default)') if self._action == 'create'
+ else "")
+ NOT_ADVERTISE_ROUTE = _("Fixed IPs of the port will not be advertised "
+ "to the BGP VPN")
+
+ LOCAL_PREF_VALUE = _(". Optionally, can control the value of the BGP "
+ "LOCAL_PREF of the routes that will be "
+ "advertised")
+
+ ADD_PREFIX_ROUTE = _("Add prefix route in CIDR notation%s") %\
+ LOCAL_PREF_VALUE
+ REMOVE_PREFIX_ROUTE = _("Remove prefix route in CIDR notation")
+ REPEAT_PREFIX_ROUTE = _("repeat option for multiple prefix routes")
+
+ ADD_BGVPVPN_ROUTE = _("Add BGP VPN route for route leaking%s") %\
+ LOCAL_PREF_VALUE
+ REMOVE_BGPVPN_ROUTE = _("Remove BGP VPN route")
+ REPEAT_BGPVPN_ROUTE = _("repeat option for multiple BGP VPN routes")
+
+ group_advertise_fixed_ips = parser.add_mutually_exclusive_group()
+ group_advertise_fixed_ips.add_argument(
+ '--advertise-fixed-ips',
+ action='store_true',
+ help=NOT_ADVERTISE_ROUTE if self._action == 'unset'
+ else ADVERTISE_ROUTE,
+ )
+ group_advertise_fixed_ips.add_argument(
+ '--no-advertise-fixed-ips',
+ action='store_true',
+ help=ADVERTISE_ROUTE if self._action == 'unset'
+ else NOT_ADVERTISE_ROUTE,
+ )
+
+ if self._action in ['create', 'set']:
+ parser.add_argument(
+ '--prefix-route',
+ metavar="prefix=<cidr>[,local_pref=<integer>]",
+ dest='prefix_routes',
+ action=parseractions.MultiKeyValueAction,
+ required_keys=['prefix'],
+ optional_keys=['local_pref'],
+ help="%s (%s)" % (ADD_PREFIX_ROUTE, REPEAT_PREFIX_ROUTE),
+ )
+ parser.add_argument(
+ '--bgpvpn-route',
+ metavar="bgpvpn=<BGP VPN ID or name>[,local_pref=<integer>]",
+ dest='bgpvpn_routes',
+ action=parseractions.MultiKeyValueAction,
+ required_keys=['bgpvpn'],
+ optional_keys=['local_pref'],
+ help="%s (%s)" % (ADD_BGVPVPN_ROUTE, REPEAT_BGPVPN_ROUTE),
+ )
+ else:
+ parser.add_argument(
+ '--prefix-route',
+ metavar="<cidr>",
+ dest='prefix_routes',
+ action='append',
+ help="%s (%s)" % (REMOVE_PREFIX_ROUTE, REPEAT_PREFIX_ROUTE),
+ )
+ parser.add_argument(
+ '--bgpvpn-route',
+ metavar="<BGP VPN ID or name>",
+ dest='bgpvpn_routes',
+ action='append',
+ help="%s (%s)" % (REMOVE_BGPVPN_ROUTE, REPEAT_BGPVPN_ROUTE),
+ )
+ if self._action != 'create':
+ parser.add_argument(
+ '--no-prefix-route' if self._action == 'set' else
+ '--all-prefix-routes',
+ dest='purge_prefix_route',
+ action='store_true',
+ help=_('Empty prefix route list'),
+ )
+ parser.add_argument(
+ '--no-bgpvpn-route' if self._action == 'set' else
+ '--all-bgpvpn-routes',
+ dest='purge_bgpvpn_route',
+ action='store_true',
+ help=_('Empty BGP VPN route list'),
+ )
+
+ def _args2body(self, bgpvpn_id, args):
+ client = self.app.client_manager.neutronclient
+ attrs = {}
+
+ if self._action != 'create':
+ assoc = client.find_resource_by_id(
+ self._resource,
+ args.resource_association_id,
+ cmd_resource='bgpvpn_%s_assoc' % self._assoc_res_name,
+ parent_id=bgpvpn_id)
+ else:
+ assoc = {'routes': []}
+
+ if args.advertise_fixed_ips:
+ attrs['advertise_fixed_ips'] = self._action != 'unset'
+ elif args.no_advertise_fixed_ips:
+ attrs['advertise_fixed_ips'] = self._action == 'unset'
+
+ prefix_routes = None
+ if 'purge_prefix_route' in args and args.purge_prefix_route:
+ prefix_routes = []
+ else:
+ prefix_routes = {r['prefix']: r.get('local_pref')
+ for r in assoc['routes']
+ if r['type'] == 'prefix'}
+ if args.prefix_routes:
+ if self._action in ['create', 'set']:
+ prefix_routes.update({r['prefix']: r.get('local_pref')
+ for r in args.prefix_routes})
+ elif self._action == 'unset':
+ for prefix in args.prefix_routes:
+ prefix_routes.pop(prefix, None)
+
+ bgpvpn_routes = None
+ if 'purge_bgpvpn_route' in args and args.purge_bgpvpn_route:
+ bgpvpn_routes = []
+ else:
+ bgpvpn_routes = {r['bgpvpn_id']: r.get('local_pref')
+ for r in assoc['routes']
+ if r['type'] == 'bgpvpn'}
+ if args.bgpvpn_routes:
+ if self._action == 'unset':
+ routes = [
+ {'bgpvpn': bgpvpn} for bgpvpn in args.bgpvpn_routes
+ ]
+ else:
+ routes = args.bgpvpn_routes
+ args_bgpvpn_routes = {
+ client.find_resource(constants.BGPVPN, r['bgpvpn'])['id']:
+ r.get('local_pref')
+ for r in routes
+ }
+ if self._action in ['create', 'set']:
+ bgpvpn_routes.update(args_bgpvpn_routes)
+ elif self._action == 'unset':
+ for bgpvpn_id in args_bgpvpn_routes:
+ bgpvpn_routes.pop(bgpvpn_id, None)
+
+ if prefix_routes is not None and not prefix_routes:
+ attrs.setdefault('routes', [])
+ elif prefix_routes is not None:
+ for prefix, local_pref in prefix_routes.items():
+ route = {
+ 'type': 'prefix',
+ 'prefix': prefix,
+ }
+ if local_pref:
+ route['local_pref'] = int(local_pref)
+ attrs.setdefault('routes', []).append(route)
+ if bgpvpn_routes is not None and not bgpvpn_routes:
+ attrs.setdefault('routes', [])
+ elif bgpvpn_routes is not None:
+ for bgpvpn_id, local_pref in bgpvpn_routes.items():
+ route = {
+ 'type': 'bgpvpn',
+ 'bgpvpn_id': bgpvpn_id,
+ }
+ if local_pref:
+ route['local_pref'] = int(local_pref)
+ attrs.setdefault('routes', []).append(route)
+
+ return {self._resource: attrs}
+
+
+class CreateBgpvpnPortAssoc(BgpvpnPortAssoc,
+ resource_association.CreateBgpvpnResAssoc):
+ _description = _("Create a BGP VPN port association")
+
+
+class SetBgpvpnPortAssoc(BgpvpnPortAssoc,
+ resource_association.SetBgpvpnResAssoc):
+ _description = _("Set BGP VPN port association properties")
+
+
+class UnsetBgpvpnPortAssoc(BgpvpnPortAssoc,
+ resource_association.UnsetBgpvpnResAssoc):
+ _description = _("Unset BGP VPN port association properties")
+
+
+class DeleteBgpvpnPortAssoc(BgpvpnPortAssoc,
+ resource_association.DeleteBgpvpnResAssoc):
+ _description = _("Delete a BGP VPN port association(s) for a given BGP "
+ "VPN")
+
+
+class ListBgpvpnPortAssoc(BgpvpnPortAssoc,
+ resource_association.ListBgpvpnResAssoc):
+ _description = _("List BGP VPN port associations for a given BGP VPN")
+
+
+class ShowBgpvpnPortAssoc(BgpvpnPortAssoc,
+ resource_association.ShowBgpvpnResAssoc):
+ _description = _("Show information of a given BGP VPN port association")
diff --git a/neutronclient/osc/v2/networking_bgpvpn/resource_association.py b/neutronclient/osc/v2/networking_bgpvpn/resource_association.py
index 485803c..4784100 100644
--- a/neutronclient/osc/v2/networking_bgpvpn/resource_association.py
+++ b/neutronclient/osc/v2/networking_bgpvpn/resource_association.py
@@ -16,6 +16,7 @@
import logging
+from osc_lib.cli import parseractions
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils as osc_utils
@@ -29,6 +30,7 @@ LOG = logging.getLogger(__name__)
class CreateBgpvpnResAssoc(command.ShowOne):
"""Create a BGP VPN resource association"""
+ _action = 'create'
def get_parser(self, prog_name):
parser = super(CreateBgpvpnResAssoc, self).get_parser(prog_name)
@@ -45,6 +47,11 @@ class CreateBgpvpnResAssoc(command.ShowOne):
help=(_("%s to associate the BGP VPN (name or ID)") %
self._assoc_res_name.capitalize()),
)
+
+ get_common_parser = getattr(self, '_get_common_parser', None)
+ if callable(get_common_parser):
+ get_common_parser(parser)
+
return parser
def take_action(self, parsed_args):
@@ -66,7 +73,16 @@ class CreateBgpvpnResAssoc(command.ShowOne):
parsed_args.project_domain,
).id
body[self._resource]['tenant_id'] = project_id
+
+ arg2body = getattr(self, '_args2body', None)
+ if callable(arg2body):
+ body[self._resource].update(
+ arg2body(bgpvpn['id'], parsed_args)[self._resource])
+
obj = create_method(bgpvpn['id'], body)[self._resource]
+ transform = getattr(self, '_transform_resource', None)
+ if callable(transform):
+ transform(obj)
columns, display_columns = nc_osc_utils.get_columns(obj,
self._attr_map)
data = osc_utils.get_dict_properties(obj, columns,
@@ -74,6 +90,48 @@ class CreateBgpvpnResAssoc(command.ShowOne):
return display_columns, data
+class SetBgpvpnResAssoc(command.Command):
+ """Set BGP VPN resource association properties"""
+ _action = 'set'
+
+ def get_parser(self, prog_name):
+ parser = super(SetBgpvpnResAssoc, self).get_parser(prog_name)
+ parser.add_argument(
+ 'resource_association_id',
+ metavar="<%s association ID>" % self._assoc_res_name,
+ help=(_("%s association ID to update") %
+ self._assoc_res_name.capitalize()),
+ )
+ parser.add_argument(
+ 'bgpvpn',
+ metavar="<bgpvpn>",
+ help=(_("BGP VPN the %s association belongs to (name or ID)") %
+ self._assoc_res_name),
+ )
+
+ get_common_parser = getattr(self, '_get_common_parser', None)
+ if callable(get_common_parser):
+ get_common_parser(parser)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ client = self.app.client_manager.neutronclient
+ update_method = getattr(
+ client, 'update_bgpvpn_%s_assoc' % self._assoc_res_name)
+ bgpvpn = client.find_resource(constants.BGPVPN, parsed_args.bgpvpn)
+ arg2body = getattr(self, '_args2body', None)
+ if callable(arg2body):
+ body = arg2body(bgpvpn['id'], parsed_args)
+ update_method(bgpvpn['id'], parsed_args.resource_association_id,
+ body)
+
+
+class UnsetBgpvpnResAssoc(SetBgpvpnResAssoc):
+ """Unset BGP VPN resource association properties"""
+ _action = 'unset'
+
+
class DeleteBgpvpnResAssoc(command.Command):
"""Remove a BGP VPN resource association(s) for a given BGP VPN"""
@@ -89,7 +147,8 @@ class DeleteBgpvpnResAssoc(command.Command):
parser.add_argument(
'bgpvpn',
metavar="<bgpvpn>",
- help=_("BGP VPN the association belongs to (name or ID)"),
+ help=(_("BGP VPN the %s association belongs to (name or ID)") %
+ self._assoc_res_name),
)
return parser
@@ -137,6 +196,13 @@ class ListBgpvpnResAssoc(command.Lister):
action='store_true',
help=_("List additional fields in output"),
)
+ parser.add_argument(
+ '--property',
+ metavar="<key=value>",
+ help=_("Filter property to apply on returned BGP VPNs (repeat to "
+ "filter on multiple properties)"),
+ action=parseractions.KeyValueAction,
+ )
return parser
def take_action(self, parsed_args):
@@ -144,8 +210,14 @@ class ListBgpvpnResAssoc(command.Lister):
list_method = getattr(client,
'list_bgpvpn_%s_assocs' % self._assoc_res_name)
bgpvpn = client.find_resource(constants.BGPVPN, parsed_args.bgpvpn)
+ params = {}
+ if parsed_args.property:
+ params.update(parsed_args.property)
objs = list_method(bgpvpn['id'],
- retrieve_all=True)[self._resource_plural]
+ retrieve_all=True, **params)[self._resource_plural]
+ transform = getattr(self, '_transform_resource', None)
+ if callable(transform):
+ [transform(obj) for obj in objs]
headers, columns = nc_osc_utils.get_column_definitions(
self._attr_map, long_listing=parsed_args.long)
return (headers, (osc_utils.get_dict_properties(
@@ -181,6 +253,9 @@ class ShowBgpvpnResAssoc(command.ShowOne):
cmd_resource='bgpvpn_%s_assoc' % self._assoc_res_name,
parent_id=bgpvpn['id'])
obj = show_method(bgpvpn['id'], assoc['id'])[self._resource]
+ transform = getattr(self, '_transform_resource', None)
+ if callable(transform):
+ transform(obj)
columns, display_columns = nc_osc_utils.get_columns(obj,
self._attr_map)
data = osc_utils.get_dict_properties(obj, columns,
diff --git a/neutronclient/osc/v2/networking_bgpvpn/router_association.py b/neutronclient/osc/v2/networking_bgpvpn/router_association.py
index ee788d3..a51fbe6 100644
--- a/neutronclient/osc/v2/networking_bgpvpn/router_association.py
+++ b/neutronclient/osc/v2/networking_bgpvpn/router_association.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2016 Juniper Routerworks Inc.
+# Copyright (c) 2016 Juniper networks Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
diff --git a/neutronclient/tests/unit/osc/v2/networking_bgpvpn/fakes.py b/neutronclient/tests/unit/osc/v2/networking_bgpvpn/fakes.py
index 3d3ec59..0103904 100644
--- a/neutronclient/tests/unit/osc/v2/networking_bgpvpn/fakes.py
+++ b/neutronclient/tests/unit/osc/v2/networking_bgpvpn/fakes.py
@@ -27,10 +27,17 @@ from neutronclient.osc.v2.networking_bgpvpn.resource_association import\
from neutronclient.osc.v2.networking_bgpvpn.resource_association import\
ListBgpvpnResAssoc
from neutronclient.osc.v2.networking_bgpvpn.resource_association import\
+ SetBgpvpnResAssoc
+from neutronclient.osc.v2.networking_bgpvpn.resource_association import\
ShowBgpvpnResAssoc
+from neutronclient.osc.v2.networking_bgpvpn.resource_association import\
+ UnsetBgpvpnResAssoc
from neutronclient.tests.unit.osc.v2 import fakes as test_fakes
+_FAKE_PROJECT_ID = 'fake_project_id'
+
+
class TestNeutronClientBgpvpn(test_fakes.TestNeutronClientOSCV2):
def setUp(self):
@@ -38,11 +45,11 @@ class TestNeutronClientBgpvpn(test_fakes.TestNeutronClientOSCV2):
self.neutronclient.find_resource = mock.Mock(
side_effect=lambda resource, name_or_id, project_id=None,
cmd_resource=None, parent_id=None, fields=None:
- {'id': name_or_id})
+ {'id': name_or_id, 'tenant_id': _FAKE_PROJECT_ID})
self.neutronclient.find_resource_by_id = mock.Mock(
side_effect=lambda resource, resource_id, cmd_resource=None,
parent_id=None, fields=None:
- {'id': resource_id})
+ {'id': resource_id, 'tenant_id': _FAKE_PROJECT_ID})
nc_osc_utils.find_project = mock.Mock(
side_effect=lambda _, name_or_id, __: mock.Mock(id=name_or_id))
@@ -59,7 +66,7 @@ class FakeBgpvpn(object):
# Set default attributes.
bgpvpn_attrs = {
'id': 'fake_bgpvpn_id',
- 'tenant_id': 'fake_project_id',
+ 'tenant_id': _FAKE_PROJECT_ID,
'name': '',
'type': 'l3',
'route_targets': [],
@@ -68,11 +75,13 @@ class FakeBgpvpn(object):
'route_distinguishers': [],
'networks': [],
'routers': [],
+ 'ports': [],
'vni': 100,
}
# Overwrite default attributes.
bgpvpn_attrs.update(attrs)
+
return copy.deepcopy(bgpvpn_attrs)
@staticmethod
@@ -108,6 +117,14 @@ class CreateBgpvpnFakeResAssoc(BgpvpnFakeAssoc, CreateBgpvpnResAssoc):
pass
+class SetBgpvpnFakeResAssoc(BgpvpnFakeAssoc, SetBgpvpnResAssoc):
+ pass
+
+
+class UnsetBgpvpnFakeResAssoc(BgpvpnFakeAssoc, UnsetBgpvpnResAssoc):
+ pass
+
+
class DeleteBgpvpnFakeResAssoc(BgpvpnFakeAssoc, DeleteBgpvpnResAssoc):
pass
@@ -132,7 +149,7 @@ class FakeResource(object):
# Set default attributes.
res_attrs = {
'id': 'fake_resource_id',
- 'tenant_id': 'fake_project_id',
+ 'tenant_id': _FAKE_PROJECT_ID,
}
# Overwrite default attributes.
diff --git a/neutronclient/tests/unit/osc/v2/networking_bgpvpn/test_bgpvpn.py b/neutronclient/tests/unit/osc/v2/networking_bgpvpn/test_bgpvpn.py
index 71f5acc..bfa7104 100644
--- a/neutronclient/tests/unit/osc/v2/networking_bgpvpn/test_bgpvpn.py
+++ b/neutronclient/tests/unit/osc/v2/networking_bgpvpn/test_bgpvpn.py
@@ -126,6 +126,8 @@ class TestCreateBgpvpn(fakes.TestNeutronClientBgpvpn):
fake_bgpvpn_call.pop('id')
fake_bgpvpn_call.pop('networks')
fake_bgpvpn_call.pop('routers')
+ fake_bgpvpn_call.pop('ports')
+
self.neutronclient.create_bgpvpn.assert_called_once_with(
{constants.BGPVPN: fake_bgpvpn_call})
self.assertEqual(sorted_headers, cols)
diff --git a/neutronclient/tests/unit/osc/v2/networking_bgpvpn/test_resource_association.py b/neutronclient/tests/unit/osc/v2/networking_bgpvpn/test_resource_association.py
index 6978d85..49be9f4 100644
--- a/neutronclient/tests/unit/osc/v2/networking_bgpvpn/test_resource_association.py
+++ b/neutronclient/tests/unit/osc/v2/networking_bgpvpn/test_resource_association.py
@@ -81,6 +81,7 @@ class TestCreateResAssoc(fakes.TestNeutronClientBgpvpn):
fake_res_assoc_call = copy.deepcopy(fake_res_assoc)
fake_res_assoc_call.pop('id')
+
self.neutronclient.create_bgpvpn_fake_resource_assoc.\
assert_called_once_with(
fake_bgpvpn['id'],
@@ -89,6 +90,36 @@ class TestCreateResAssoc(fakes.TestNeutronClientBgpvpn):
self.assertEqual(_get_data(fake_res_assoc), data)
+class TestSetResAssoc(fakes.TestNeutronClientBgpvpn):
+ def setUp(self):
+ super(TestSetResAssoc, self).setUp()
+ self.cmd = fakes.SetBgpvpnFakeResAssoc(self.app, self.namespace)
+
+ def test_set_resource_association(self):
+ fake_bgpvpn = fakes.FakeBgpvpn.create_one_bgpvpn()
+ fake_res = fakes.FakeResource.create_one_resource()
+ fake_res_assoc = fakes.FakeResAssoc.create_one_resource_association(
+ fake_res)
+ self.neutronclient.update_bgpvpn_fake_resource_assoc = mock.Mock(
+ return_value={fakes.BgpvpnFakeAssoc._resource: fake_res_assoc})
+ arglist = [
+ fake_res_assoc['id'],
+ fake_bgpvpn['id'],
+ ]
+ verifylist = [
+ ('resource_association_id', fake_res_assoc['id']),
+ ('bgpvpn', fake_bgpvpn['id']),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.neutronclient.update_bgpvpn_fake_resource_assoc.\
+ assert_not_called()
+ self.assertIsNone(result)
+
+
class TestDeleteResAssoc(fakes.TestNeutronClientBgpvpn):
def setUp(self):
super(TestDeleteResAssoc, self).setUp()
diff --git a/neutronclient/v2_0/client.py b/neutronclient/v2_0/client.py
index 412e1b8..cb84b9f 100644
--- a/neutronclient/v2_0/client.py
+++ b/neutronclient/v2_0/client.py
@@ -651,6 +651,8 @@ class Client(ClientBase):
bgpvpn_router_associations_path = "/bgpvpn/bgpvpns/%s/router_associations"
bgpvpn_router_association_path =\
"/bgpvpn/bgpvpns/%s/router_associations/%s"
+ bgpvpn_port_associations_path = "/bgpvpn/bgpvpns/%s/port_associations"
+ bgpvpn_port_association_path = "/bgpvpn/bgpvpns/%s/port_associations/%s"
network_logs_path = "/log/logs"
network_log_path = "/log/logs/%s"
network_loggables_path = "/log/loggable-resources"
@@ -707,6 +709,7 @@ class Client(ClientBase):
'bgpvpns': 'bgpvpn',
'network_associations': 'network_association',
'router_associations': 'router_association',
+ 'port_associations': 'port_association',
'flow_classifiers': 'flow_classifier',
'port_pairs': 'port_pair',
'port_pair_groups': 'port_pair_group',
@@ -2193,6 +2196,34 @@ class Client(ClientBase):
return self.delete(
self.bgpvpn_router_association_path % (bgpvpn, router_assoc))
+ def list_bgpvpn_port_assocs(self, bgpvpn, retrieve_all=True, **_params):
+ """Fetches a list of port associations for a given BGP VPN."""
+ return self.list('port_associations',
+ self.bgpvpn_port_associations_path % bgpvpn,
+ retrieve_all, **_params)
+
+ def show_bgpvpn_port_assoc(self, bgpvpn, port_assoc, **_params):
+ """Fetches information of a certain BGP VPN's port association"""
+ return self.get(
+ self.bgpvpn_port_association_path % (bgpvpn, port_assoc),
+ params=_params)
+
+ def create_bgpvpn_port_assoc(self, bgpvpn, body=None):
+ """Creates a new BGP VPN port association"""
+ return self.post(self.bgpvpn_port_associations_path % bgpvpn,
+ body=body)
+
+ def update_bgpvpn_port_assoc(self, bgpvpn, port_assoc, body=None):
+ """Updates a BGP VPN port association"""
+ return self.put(
+ self.bgpvpn_port_association_path % (bgpvpn, port_assoc),
+ body=body)
+
+ def delete_bgpvpn_port_assoc(self, bgpvpn, port_assoc):
+ """Deletes the specified BGP VPN port association"""
+ return self.delete(
+ self.bgpvpn_port_association_path % (bgpvpn, port_assoc))
+
def create_sfc_port_pair(self, body=None):
"""Creates a new Port Pair."""
return self.post(self.sfc_port_pairs_path, body=body)
diff --git a/releasenotes/notes/support-bgpvpn-route-control-aeda3e698486f73b.yaml b/releasenotes/notes/support-bgpvpn-route-control-aeda3e698486f73b.yaml
new file mode 100644
index 0000000..2f7ed17
--- /dev/null
+++ b/releasenotes/notes/support-bgpvpn-route-control-aeda3e698486f73b.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add BGP VPN `port association <https://developer.openstack.org/api-ref/network/v2/index.html#port-associations>`_
+ support to the CLI, which are introduced for BGP VPN interconnections by the
+ ``bgpvpn-routes-control`` API extension.
diff --git a/setup.cfg b/setup.cfg
index eccd2e3..67a2a35 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -124,6 +124,12 @@ openstack.neutronclient.v2 =
bgpvpn_router_association_delete = neutronclient.osc.v2.networking_bgpvpn.router_association:DeleteBgpvpnRouterAssoc
bgpvpn_router_association_list = neutronclient.osc.v2.networking_bgpvpn.router_association:ListBgpvpnRouterAssoc
bgpvpn_router_association_show = neutronclient.osc.v2.networking_bgpvpn.router_association:ShowBgpvpnRouterAssoc
+ bgpvpn_port_association_create = neutronclient.osc.v2.networking_bgpvpn.port_association:CreateBgpvpnPortAssoc
+ bgpvpn_port_association_set = neutronclient.osc.v2.networking_bgpvpn.port_association:SetBgpvpnPortAssoc
+ bgpvpn_port_association_unset = neutronclient.osc.v2.networking_bgpvpn.port_association:UnsetBgpvpnPortAssoc
+ bgpvpn_port_association_delete = neutronclient.osc.v2.networking_bgpvpn.port_association:DeleteBgpvpnPortAssoc
+ bgpvpn_port_association_list = neutronclient.osc.v2.networking_bgpvpn.port_association:ListBgpvpnPortAssoc
+ bgpvpn_port_association_show = neutronclient.osc.v2.networking_bgpvpn.port_association:ShowBgpvpnPortAssoc
network_loggable_resources_list = neutronclient.osc.v2.logging.network_log:ListLoggableResource
network_log_create = neutronclient.osc.v2.logging.network_log:CreateNetworkLog