Add common utilities for OSC plugin implementation
This patch introduces two utility modules. * osc/utils.py for OSC plugin general stuff which are planned to move to osc-lib potentially * osc/v2/utils.py for Networking v2.0 API related methods Co-Authored-By: Yushiro FURUKAWA <y.furukawa_2@jp.fujitsu.com> Change-Id: Ia36687843c56ebde0d3705ef11435ec7f5cff9d8
This commit is contained in:
parent
a686743940
commit
0b7ee51ae8
|
@ -0,0 +1,195 @@
|
|||
# Copyright 2016 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""This module should contain OSC plugin generic methods.
|
||||
|
||||
Methods in this module are candidates adopted to osc-lib.
|
||||
|
||||
Stuffs specific to neutronclient OSC plugin should not be added
|
||||
to this module. They should go to neutronclient.osc.v2.utils.
|
||||
"""
|
||||
|
||||
import operator
|
||||
|
||||
from keystoneclient import exceptions as identity_exc
|
||||
from keystoneclient.v3 import domains
|
||||
from keystoneclient.v3 import projects
|
||||
from osc_lib import utils
|
||||
|
||||
from neutronclient._i18n import _
|
||||
|
||||
|
||||
LIST_BOTH = 'both'
|
||||
LIST_SHORT_ONLY = 'short_only'
|
||||
LIST_LONG_ONLY = 'long_only'
|
||||
|
||||
|
||||
def get_column_definitions(attr_map, long_listing):
|
||||
"""Return table headers and column names for a listing table.
|
||||
|
||||
:param attr_map: a list of table entry definitions.
|
||||
Each entry should be a tuple consisting of
|
||||
(API attribute name, header name, listing mode). For example:
|
||||
(('id', 'ID', LIST_BOTH),
|
||||
('name', 'Name', LIST_BOTH),
|
||||
('tenant_id', 'Project', LIST_LONG_ONLY))
|
||||
The third field of each tuple must be one of LIST_BOTH,
|
||||
LIST_LONG_ONLY (a corresponding column is shown only in a long mode), or
|
||||
LIST_SHORT_ONLY (a corresponding column is shown only in a short mode).
|
||||
:param long_listing: A boolean value which indicates a long listing
|
||||
or not. In most cases, parsed_args.long is passed to this argument.
|
||||
:return: A tuple of a list of table headers and a list of column names.
|
||||
"""
|
||||
|
||||
if long_listing:
|
||||
headers = [hdr for col, hdr, listing_mode in attr_map
|
||||
if listing_mode in (LIST_BOTH, LIST_LONG_ONLY)]
|
||||
columns = [col for col, hdr, listing_mode in attr_map
|
||||
if listing_mode in (LIST_BOTH, LIST_LONG_ONLY)]
|
||||
else:
|
||||
headers = [hdr for col, hdr, listing_mode in attr_map if listing_mode
|
||||
if listing_mode in (LIST_BOTH, LIST_SHORT_ONLY)]
|
||||
columns = [col for col, hdr, listing_mode in attr_map if listing_mode
|
||||
if listing_mode in (LIST_BOTH, LIST_SHORT_ONLY)]
|
||||
|
||||
return headers, columns
|
||||
|
||||
|
||||
def get_columns(item, attr_map=None):
|
||||
"""Return pair of resource attributes and corresponding display names.
|
||||
|
||||
Assume the following item and attr_map are passed.
|
||||
item: {'id': 'myid', 'name': 'myname',
|
||||
'foo': 'bar', 'tenant_id': 'mytenan'}
|
||||
attr_map:
|
||||
(('id', 'ID', LIST_BOTH),
|
||||
('name', 'Name', LIST_BOTH),
|
||||
('tenant_id', 'Project', LIST_LONG_ONLY))
|
||||
|
||||
This method returns:
|
||||
|
||||
(('id', 'name', 'tenant_id', 'foo'), # attributes
|
||||
('ID', 'Name', 'Project', 'foo') # display names
|
||||
|
||||
Both tuples of attributes and display names are sorted by display names
|
||||
in the alphabetical order.
|
||||
Attributes not found in a given attr_map are kept as-is.
|
||||
|
||||
:param item: a dictionary which represents a resource.
|
||||
Keys of the dictionary are expected to be attributes of the resource.
|
||||
Values are not referred to by this method.
|
||||
:param attr_map: a list of mapping from attribute to display name.
|
||||
The same format is used as for get_column_definitions attr_map.
|
||||
:return: A pair of tuple of attributes and tuple of display names.
|
||||
"""
|
||||
attr_map = attr_map or tuple([])
|
||||
_attr_map_dict = dict((col, hdr) for col, hdr, listing_mode in attr_map)
|
||||
|
||||
columns = [(column, _attr_map_dict.get(column, column))
|
||||
for column in item.keys()]
|
||||
columns = sorted(columns, key=operator.itemgetter(1))
|
||||
return (tuple(col[0] for col in columns),
|
||||
tuple(col[1] for col in columns))
|
||||
|
||||
|
||||
# TODO(amotoki): Use osc-lib version once osc-lib provides this.
|
||||
def add_project_owner_option_to_parser(parser):
|
||||
"""Register project and project domain options.
|
||||
|
||||
:param parser: argparse.Argument parser object.
|
||||
|
||||
"""
|
||||
parser.add_argument(
|
||||
'--project',
|
||||
metavar='<project>',
|
||||
help=_("Owner's project (name or ID)")
|
||||
)
|
||||
# Borrowed from openstackclient.identity.common
|
||||
# as it is not exposed officially.
|
||||
parser.add_argument(
|
||||
'--project-domain',
|
||||
metavar='<project-domain>',
|
||||
help=_('Domain the project belongs to (name or ID). '
|
||||
'This can be used in case collisions between project names '
|
||||
'exist.'),
|
||||
)
|
||||
|
||||
|
||||
# The following methods are borrowed from openstackclient.identity.common
|
||||
# as it is not exposed officially.
|
||||
# TODO(amotoki): Use osc-lib version once osc-lib provides this.
|
||||
|
||||
|
||||
def find_domain(identity_client, name_or_id):
|
||||
return _find_identity_resource(identity_client.domains, name_or_id,
|
||||
domains.Domain)
|
||||
|
||||
|
||||
def find_project(identity_client, name_or_id, domain_name_or_id=None):
|
||||
domain_id = _get_domain_id_if_requested(identity_client, domain_name_or_id)
|
||||
if not domain_id:
|
||||
return _find_identity_resource(identity_client.projects, name_or_id,
|
||||
projects.Project)
|
||||
else:
|
||||
return _find_identity_resource(identity_client.projects, name_or_id,
|
||||
projects.Project, domain_id=domain_id)
|
||||
|
||||
|
||||
def _get_domain_id_if_requested(identity_client, domain_name_or_id):
|
||||
if not domain_name_or_id:
|
||||
return None
|
||||
domain = find_domain(identity_client, domain_name_or_id)
|
||||
return domain.id
|
||||
|
||||
|
||||
def _find_identity_resource(identity_client_manager, name_or_id,
|
||||
resource_type, **kwargs):
|
||||
"""Find a specific identity resource.
|
||||
|
||||
Using keystoneclient's manager, attempt to find a specific resource by its
|
||||
name or ID. If Forbidden to find the resource (a common case if the user
|
||||
does not have permission), then return the resource by creating a local
|
||||
instance of keystoneclient's Resource.
|
||||
|
||||
The parameter identity_client_manager is a keystoneclient manager,
|
||||
for example: keystoneclient.v3.users or keystoneclient.v3.projects.
|
||||
|
||||
The parameter resource_type is a keystoneclient resource, for example:
|
||||
keystoneclient.v3.users.User or keystoneclient.v3.projects.Project.
|
||||
|
||||
:param identity_client_manager: the manager that contains the resource
|
||||
:type identity_client_manager: `keystoneclient.base.CrudManager`
|
||||
:param name_or_id: the resources's name or ID
|
||||
:type name_or_id: string
|
||||
:param resource_type: class that represents the resource type
|
||||
:type resource_type: `keystoneclient.base.Resource`
|
||||
|
||||
:returns: the resource in question
|
||||
:rtype: `keystoneclient.base.Resource`
|
||||
|
||||
"""
|
||||
|
||||
try:
|
||||
identity_resource = utils.find_resource(identity_client_manager,
|
||||
name_or_id, **kwargs)
|
||||
if identity_resource is not None:
|
||||
return identity_resource
|
||||
except identity_exc.Forbidden:
|
||||
pass
|
||||
|
||||
return resource_type(None, {'id': name_or_id, 'name': name_or_id})
|
||||
|
||||
|
||||
# The above are borrowed from openstackclient.identity.common.
|
||||
# DO NOT ADD original methods in neutronclient repo to the above area.
|
|
@ -20,12 +20,11 @@ import logging
|
|||
from osc_lib.cli import parseractions
|
||||
from osc_lib.command import command
|
||||
from osc_lib import exceptions
|
||||
from osc_lib import utils
|
||||
|
||||
# TODO(abhiraut): Switch to neutronclients identity utils
|
||||
from openstackclient.identity import common as identity_common
|
||||
from osc_lib import utils as osc_utils
|
||||
|
||||
from neutronclient._i18n import _
|
||||
from neutronclient.osc import utils as nc_osc_utils
|
||||
from neutronclient.osc.v2 import utils as v2_utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
@ -75,12 +74,7 @@ class CreateNetworkTrunk(command.ShowOne):
|
|||
action='store_true',
|
||||
help=_("Disable trunk")
|
||||
)
|
||||
parser.add_argument(
|
||||
'--project',
|
||||
metavar='<project>',
|
||||
help=_("Owner's project (name or ID)")
|
||||
)
|
||||
identity_common.add_project_domain_option_to_parser(parser)
|
||||
nc_osc_utils.add_project_owner_option_to_parser(parser)
|
||||
return parser
|
||||
|
||||
def take_action(self, parsed_args):
|
||||
|
@ -90,8 +84,8 @@ class CreateNetworkTrunk(command.ShowOne):
|
|||
body = {TRUNK: attrs}
|
||||
obj = client.create_trunk(body)
|
||||
columns = _get_columns(obj[TRUNK])
|
||||
data = utils.get_dict_properties(obj[TRUNK], columns,
|
||||
formatters=_formatters)
|
||||
data = osc_utils.get_dict_properties(obj[TRUNK], columns,
|
||||
formatters=_formatters)
|
||||
return columns, data
|
||||
|
||||
|
||||
|
@ -169,7 +163,7 @@ class ListNetworkTrunk(command.Lister):
|
|||
'updated_at'
|
||||
)
|
||||
return (headers,
|
||||
(utils.get_dict_properties(
|
||||
(osc_utils.get_dict_properties(
|
||||
s, columns,
|
||||
formatters=_formatters,
|
||||
) for s in data[TRUNKS]))
|
||||
|
@ -254,8 +248,8 @@ class ShowNetworkTrunk(command.ShowOne):
|
|||
trunk_id = _get_id(client, parsed_args.trunk, TRUNK)
|
||||
obj = client.show_trunk(trunk_id)
|
||||
columns = _get_columns(obj[TRUNK])
|
||||
data = utils.get_dict_properties(obj[TRUNK], columns,
|
||||
formatters=_formatters)
|
||||
data = osc_utils.get_dict_properties(obj[TRUNK], columns,
|
||||
formatters=_formatters)
|
||||
return columns, data
|
||||
|
||||
|
||||
|
@ -279,7 +273,7 @@ class ListNetworkSubport(command.Lister):
|
|||
headers = ('Port', 'Segmentation Type', 'Segmentation ID')
|
||||
columns = ('port_id', 'segmentation_type', 'segmentation_id')
|
||||
return (headers,
|
||||
(utils.get_dict_properties(
|
||||
(osc_utils.get_dict_properties(
|
||||
s, columns,
|
||||
) for s in data[SUB_PORTS]))
|
||||
|
||||
|
@ -311,13 +305,9 @@ class UnsetNetworkTrunk(command.Command):
|
|||
client.trunk_remove_subports(trunk_id, attrs)
|
||||
|
||||
|
||||
def _format_admin_state(item):
|
||||
return 'UP' if item else 'DOWN'
|
||||
|
||||
|
||||
_formatters = {
|
||||
'admin_state_up': _format_admin_state,
|
||||
'sub_ports': utils.format_list_of_dicts,
|
||||
'admin_state_up': v2_utils.format_admin_state,
|
||||
'sub_ports': osc_utils.format_list_of_dicts,
|
||||
}
|
||||
|
||||
|
||||
|
@ -346,7 +336,7 @@ def _get_attrs_for_trunk(client_manager, parsed_args):
|
|||
# "trunk set" command doesn't support setting project.
|
||||
if 'project' in parsed_args and parsed_args.project is not None:
|
||||
identity_client = client_manager.identity
|
||||
project_id = identity_common.find_project(
|
||||
project_id = nc_osc_utils.find_project(
|
||||
identity_client,
|
||||
parsed_args.project,
|
||||
parsed_args.project_domain,
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
# Copyright 2016 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""This module is intended to contain methods specific
|
||||
to Networking v2 API and its extensions.
|
||||
"""
|
||||
|
||||
|
||||
def format_admin_state(state):
|
||||
return 'UP' if state else 'DOWN'
|
|
@ -0,0 +1,60 @@
|
|||
# Copyright 2016 NEC Corporation
|
||||
#
|
||||
# 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 testtools
|
||||
|
||||
from neutronclient.osc import utils
|
||||
|
||||
|
||||
class TestUtils(testtools.TestCase):
|
||||
|
||||
def test_get_column_definitions(self):
|
||||
attr_map = (
|
||||
('id', 'ID', utils.LIST_BOTH),
|
||||
('tenant_id', 'Project', utils.LIST_LONG_ONLY),
|
||||
('name', 'Name', utils.LIST_BOTH),
|
||||
('summary', 'Summary', utils.LIST_SHORT_ONLY),
|
||||
)
|
||||
headers, columns = utils.get_column_definitions(attr_map,
|
||||
long_listing=False)
|
||||
self.assertEqual(['id', 'name', 'summary'], columns)
|
||||
self.assertEqual(['ID', 'Name', 'Summary'], headers)
|
||||
|
||||
def test_get_column_definitions_long(self):
|
||||
attr_map = (
|
||||
('id', 'ID', utils.LIST_BOTH),
|
||||
('tenant_id', 'Project', utils.LIST_LONG_ONLY),
|
||||
('name', 'Name', utils.LIST_BOTH),
|
||||
('summary', 'Summary', utils.LIST_SHORT_ONLY),
|
||||
)
|
||||
headers, columns = utils.get_column_definitions(attr_map,
|
||||
long_listing=True)
|
||||
self.assertEqual(['id', 'tenant_id', 'name'], columns)
|
||||
self.assertEqual(['ID', 'Project', 'Name'], headers)
|
||||
|
||||
def test_get_columns(self):
|
||||
item = {
|
||||
'id': 'test-id',
|
||||
'tenant_id': 'test-tenant_id',
|
||||
# 'name' is not included
|
||||
'foo': 'bar', # unknown attribute
|
||||
}
|
||||
attr_map = (
|
||||
('id', 'ID', utils.LIST_BOTH),
|
||||
('tenant_id', 'Project', utils.LIST_LONG_ONLY),
|
||||
('name', 'Name', utils.LIST_BOTH),
|
||||
)
|
||||
columns, display_names = utils.get_columns(item, attr_map)
|
||||
self.assertEqual(tuple(['id', 'tenant_id', 'foo']), columns)
|
||||
self.assertEqual(tuple(['ID', 'Project', 'foo']), display_names)
|
|
@ -22,6 +22,7 @@ from osc_lib.tests import utils as tests_utils
|
|||
from osc_lib import utils
|
||||
|
||||
from neutronclient.osc.v2.trunk import network_trunk as trunk
|
||||
from neutronclient.osc.v2 import utils as v2_utils
|
||||
from neutronclient.tests.unit.osc.v2 import fakes as test_fakes
|
||||
from neutronclient.tests.unit.osc.v2.trunk import fakes
|
||||
|
||||
|
@ -47,7 +48,7 @@ class TestCreateNetworkTrunk(test_fakes.TestNeutronClientOSCV2):
|
|||
|
||||
def get_data(self):
|
||||
return (
|
||||
trunk._format_admin_state(self._trunk['admin_state_up']),
|
||||
v2_utils.format_admin_state(self._trunk['admin_state_up']),
|
||||
self._trunk['description'],
|
||||
self._trunk['id'],
|
||||
self._trunk['name'],
|
||||
|
@ -248,7 +249,7 @@ class TestShowNetworkTrunk(test_fakes.TestNeutronClientOSCV2):
|
|||
'sub_ports',
|
||||
)
|
||||
data = (
|
||||
trunk._format_admin_state(_trunk['admin_state_up']),
|
||||
v2_utils.format_admin_state(_trunk['admin_state_up']),
|
||||
_trunk['description'],
|
||||
_trunk['id'],
|
||||
_trunk['name'],
|
||||
|
@ -327,7 +328,7 @@ class TestListNetworkTrunk(test_fakes.TestNeutronClientOSCV2):
|
|||
t['port_id'],
|
||||
t['description'],
|
||||
t['status'],
|
||||
trunk._format_admin_state(t['admin_state_up']),
|
||||
v2_utils.format_admin_state(t['admin_state_up']),
|
||||
'2001-01-01 00:00:00',
|
||||
'2001-01-01 00:00:00',
|
||||
))
|
||||
|
@ -384,7 +385,7 @@ class TestSetNetworkTrunk(test_fakes.TestNeutronClientOSCV2):
|
|||
'sub_ports',
|
||||
)
|
||||
data = (
|
||||
trunk._format_admin_state(_trunk['admin_state_up']),
|
||||
v2_utils.format_admin_state(_trunk['admin_state_up']),
|
||||
_trunk['id'],
|
||||
_trunk['name'],
|
||||
_trunk['description'],
|
||||
|
@ -620,7 +621,7 @@ class TestUnsetNetworkTrunk(test_fakes.TestNeutronClientOSCV2):
|
|||
'sub_ports',
|
||||
)
|
||||
data = (
|
||||
trunk._format_admin_state(_trunk['admin_state_up']),
|
||||
v2_utils.format_admin_state(_trunk['admin_state_up']),
|
||||
_trunk['id'],
|
||||
_trunk['name'],
|
||||
_trunk['port_id'],
|
||||
|
|
|
@ -12,6 +12,9 @@ oslo.serialization>=1.10.0 # Apache-2.0
|
|||
oslo.utils>=3.17.0 # Apache-2.0
|
||||
os-client-config>=1.22.0 # Apache-2.0
|
||||
keystoneauth1>=2.14.0 # Apache-2.0
|
||||
# keystoneclient is used only by neutronclient.osc.utils
|
||||
# TODO(amotoki): Drop this after osc.utils has no dependency on keystoneclient
|
||||
python-keystoneclient>=3.8.0 # Apache-2.0
|
||||
requests>=2.10.0 # Apache-2.0
|
||||
simplejson>=2.2.0 # MIT
|
||||
six>=1.9.0 # MIT
|
||||
|
|
Loading…
Reference in New Issue