From 0fcd6c31c3bf42a8bd2c4cf4e76d5dcb3784c9d0 Mon Sep 17 00:00:00 2001 From: yong sheng gong Date: Thu, 21 Dec 2017 14:14:56 +0800 Subject: [PATCH] Base OpenStackClient(OSC) plugin support This patch enables tackerclient OSC plugin support. Co-Authored-By: yong sheng gong Co-Authored-By: Trinath Somanchi This adds the first list VIM command. Change-Id: I75d430b00861ee9020d346cfb8ac8b736d36f47d Implements: blueprint tacker-support-python-openstackclient --- requirements.txt | 1 + setup.cfg | 7 + tackerclient/osc/__init__.py | 0 tackerclient/osc/plugin.py | 61 +++++++++ tackerclient/osc/utils.py | 194 +++++++++++++++++++++++++++ tackerclient/osc/v1/__init__.py | 0 tackerclient/osc/v1/nfvo/__init__.py | 0 tackerclient/osc/v1/nfvo/vim.py | 56 ++++++++ 8 files changed, 319 insertions(+) create mode 100644 tackerclient/osc/__init__.py create mode 100644 tackerclient/osc/plugin.py create mode 100644 tackerclient/osc/utils.py create mode 100644 tackerclient/osc/v1/__init__.py create mode 100644 tackerclient/osc/v1/nfvo/__init__.py create mode 100644 tackerclient/osc/v1/nfvo/vim.py diff --git a/requirements.txt b/requirements.txt index 7da96f56..288edb40 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,6 +13,7 @@ stevedore>=1.20.0 # Apache-2.0 Babel!=2.4.0,>=2.3.4 # BSD oslo.i18n>=3.15.3 # Apache-2.0 +osc-lib>=1.8.0 # Apache-2.0 oslo.log>=3.30.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 diff --git a/setup.cfg b/setup.cfg index 0e8395d3..24609cbc 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,6 +31,13 @@ setup-hooks = console_scripts = tacker = tackerclient.shell:main +openstack.cli.extension = + tackerclient = tackerclient.osc.plugin + +openstack.tackerclient.v1 = + vim_list = tackerclient.osc.v1.nfvo.vim:ListVIM + + [build_sphinx] all_files = 1 build-dir = doc/build diff --git a/tackerclient/osc/__init__.py b/tackerclient/osc/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tackerclient/osc/plugin.py b/tackerclient/osc/plugin.py new file mode 100644 index 00000000..3077a989 --- /dev/null +++ b/tackerclient/osc/plugin.py @@ -0,0 +1,61 @@ +# 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. +# + +"""OpenStackClient plugin for nfv-orchestration service.""" + +import logging + +from osc_lib import utils + + +LOG = logging.getLogger(__name__) + +# Required by the OSC plugin interface +DEFAULT_TACKER_API_VERSION = '1' +API_NAME = 'tackerclient' +API_VERSION_OPTION = 'os_tacker_api_version' +API_VERSIONS = { + '1': 'tackerclient.v1_0.client.Client', +} + + +def make_client(instance): + """Returns a client to the ClientManager.""" + + tacker_client = utils.get_client_class( + API_NAME, + instance._api_version[API_NAME], + API_VERSIONS) + LOG.debug('Instantiating tacker client: %s', tacker_client) + + kwargs = {'service_type': 'nfv-orchestration', + 'region_name': instance._region_name, + 'endpoint_type': instance._interface, + 'interface': instance._interface, + 'session': instance.session + } + + client = tacker_client(**kwargs) + + return client + + +def build_option_parser(parser): + """Hook to add global options.""" + parser.add_argument( + '--os-tacker-api-version', + metavar='', + default=utils.env( + 'OS_TACKER_API_VERSION', + default=DEFAULT_TACKER_API_VERSION)) + return parser diff --git a/tackerclient/osc/utils.py b/tackerclient/osc/utils.py new file mode 100644 index 00000000..9b82f856 --- /dev/null +++ b/tackerclient/osc/utils.py @@ -0,0 +1,194 @@ +# 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 tackerclient OSC plugin should not be added +to this module. They should go to tackerclient.osc.v1.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 tackerclient.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 in (LIST_BOTH, LIST_SHORT_ONLY)] + columns = [col for col, hdr, listing_mode in attr_map + 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='', + help=_("Owner's project (name or ID)") + ) + # Borrowed from openstackclient.identity.common + # as it is not exposed officially. + parser.add_argument( + '--project-domain', + metavar='', + 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. diff --git a/tackerclient/osc/v1/__init__.py b/tackerclient/osc/v1/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tackerclient/osc/v1/nfvo/__init__.py b/tackerclient/osc/v1/nfvo/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tackerclient/osc/v1/nfvo/vim.py b/tackerclient/osc/v1/nfvo/vim.py new file mode 100644 index 00000000..4a4e8f98 --- /dev/null +++ b/tackerclient/osc/v1/nfvo/vim.py @@ -0,0 +1,56 @@ +# Copyright 2016 Brocade Communications Systems 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. + +from osc_lib.command import command +from osc_lib import utils + +from tackerclient.i18n import _ +from tackerclient.osc import utils as tacker_osc_utils + +_attr_map = ( + ('id', 'ID', tacker_osc_utils.LIST_BOTH), + ('name', 'Name', tacker_osc_utils.LIST_BOTH), + ('tenant_id', 'Tenant_id', tacker_osc_utils.LIST_BOTH), + ('type', 'Type', tacker_osc_utils.LIST_BOTH), + ('is_default', 'Is Default', + tacker_osc_utils.LIST_BOTH), + ('placement_attr', 'Placement attribution', + tacker_osc_utils.LIST_LONG_ONLY), + ('status', 'Status', tacker_osc_utils.LIST_BOTH), +) + + +class ListVIM(command.Lister): + _description = _("List VIMs that belong to a given tenant.") + + def get_parser(self, prog_name): + parser = super(ListVIM, self).get_parser(prog_name) + parser.add_argument( + '--long', + action='store_true', + help=_("List additional fields in output") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.tackerclient + data = client.list_vims() + headers, columns = tacker_osc_utils.get_column_definitions( + _attr_map, long_listing=parsed_args.long) + return (headers, + (utils.get_dict_properties( + s, columns, + ) for s in data['vims']))