Merge "Add commands to support BGP VPN route control new API extension"

This commit is contained in:
Zuul 2018-01-25 09:37:56 +00:00 committed by Gerrit Code Review
commit 95d64cef77
12 changed files with 499 additions and 7 deletions

View File

@ -32,3 +32,6 @@ Network v2
.. autoprogram-cliff:: openstack.neutronclient.v2
:command: bgpvpn router association *
.. autoprogram-cliff:: openstack.neutronclient.v2
:command: bgpvpn port association *

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,11 +26,18 @@ from neutronclient.osc.v2.networking_bgpvpn.resource_association import\
DeleteBgpvpnResAssoc
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.

View File

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

View File

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

View File

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

View File

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

View File

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