diff options
author | Sergey Reshetnyak <sreshetniak@mirantis.com> | 2014-09-21 21:20:37 +0400 |
---|---|---|
committer | Sergey Reshetnyak <sreshetniak@mirantis.com> | 2014-09-28 14:53:23 +0400 |
commit | d02f0e1daa411bdde40526f61b031c718c79ba5f (patch) | |
tree | 5c89729926e3c7af0afc65cd9dc11e750f601244 | |
parent | ccec5b71edefe27b91ea461804767b2b17bd22a3 (diff) | |
download | python-saharaclient-d02f0e1daa411bdde40526f61b031c718c79ba5f.tar.gz |
Use base utils from oslo-incubator instead copy-pasted from nova
Change-Id: Ie7bfbfdce51f5d45b51f02551a309736c3d0d752
-rw-r--r-- | saharaclient/api/shell.py | 2 | ||||
-rw-r--r-- | saharaclient/nova/__init__.py | 0 | ||||
-rw-r--r-- | saharaclient/nova/auth_plugin.py | 143 | ||||
-rw-r--r-- | saharaclient/nova/base.py | 495 | ||||
-rw-r--r-- | saharaclient/nova/extension.py | 39 | ||||
-rw-r--r-- | saharaclient/nova/utils.py | 384 | ||||
-rw-r--r-- | saharaclient/shell.py | 118 | ||||
-rw-r--r-- | saharaclient/tests/unit/nova/test_shell.py | 2 |
8 files changed, 6 insertions, 1177 deletions
diff --git a/saharaclient/api/shell.py b/saharaclient/api/shell.py index 2eac129..ec420da 100644 --- a/saharaclient/api/shell.py +++ b/saharaclient/api/shell.py @@ -19,8 +19,8 @@ import inspect import json import sys -from saharaclient.nova import utils from saharaclient.openstack.common.apiclient import exceptions +from saharaclient.openstack.common import cliutils as utils def _print_list_field(field): diff --git a/saharaclient/nova/__init__.py b/saharaclient/nova/__init__.py deleted file mode 100644 index e69de29..0000000 --- a/saharaclient/nova/__init__.py +++ /dev/null diff --git a/saharaclient/nova/auth_plugin.py b/saharaclient/nova/auth_plugin.py deleted file mode 100644 index db5b7ba..0000000 --- a/saharaclient/nova/auth_plugin.py +++ /dev/null @@ -1,143 +0,0 @@ -# Copyright 2013 OpenStack Foundation -# Copyright 2013 Spanish National Research Council. -# 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 - -import pkg_resources -import six - -from saharaclient.nova import utils -from saharaclient.openstack.common.apiclient import exceptions - - -logger = logging.getLogger(__name__) - - -_discovered_plugins = {} - - -def discover_auth_systems(): - """Discover the available auth-systems. - - This won't take into account the old style auth-systems. - """ - ep_name = 'openstack.client.auth_plugin' - for ep in pkg_resources.iter_entry_points(ep_name): - try: - auth_plugin = ep.load() - except (ImportError, pkg_resources.UnknownExtra, AttributeError) as e: - logger.debug("ERROR: Cannot load auth plugin %s", ep.name) - logger.debug(e, exc_info=1) - else: - _discovered_plugins[ep.name] = auth_plugin - - -def load_auth_system_opts(parser): - """Load options needed by the available auth-systems into a parser. - - This function will try to populate the parser with options from the - available plugins. - """ - for name, auth_plugin in six.iteritems(_discovered_plugins): - add_opts_fn = getattr(auth_plugin, "add_opts", None) - if add_opts_fn: - group = parser.add_argument_group("Auth-system '%s' options" % - name) - add_opts_fn(group) - - -def load_plugin(auth_system): - if auth_system in _discovered_plugins: - return _discovered_plugins[auth_system]() - - # NOTE(aloga): If we arrive here, the plugin will be an old-style one, - # so we have to create a fake AuthPlugin for it. - return DeprecatedAuthPlugin(auth_system) - - -class BaseAuthPlugin(object): - """Base class for authentication plugins. - - An authentication plugin needs to override at least the authenticate - method to be a valid plugin. - """ - def __init__(self): - self.opts = {} - - def get_auth_url(self): - """Return the auth url for the plugin (if any).""" - return None - - @staticmethod - def add_opts(parser): - """Populate and return the parser with the options for this plugin. - - If the plugin does not need any options, it should return the same - parser untouched. - """ - return parser - - def parse_opts(self, args): - """Parse the actual auth-system options if any. - - This method is expected to populate the attribute self.opts with a - dict containing the options and values needed to make authentication. - If the dict is empty, the client should assume that it needs the same - options as the 'keystone' auth system (i.e. os_username and - os_password). - - Returns the self.opts dict. - """ - return self.opts - - def authenticate(self, cls, auth_url): - """Authenticate using plugin defined method.""" - raise exceptions.AuthSystemNotFound(self.auth_system) - - -class DeprecatedAuthPlugin(object): - """Class to mimic the AuthPlugin class for deprecated auth systems. - - Old auth systems only define two entry points: openstack.client.auth_url - and openstack.client.authenticate. This class will load those entry points - into a class similar to a valid AuthPlugin. - """ - def __init__(self, auth_system): - self.auth_system = auth_system - - def authenticate(cls, auth_url): - raise exceptions.AuthSystemNotFound(self.auth_system) - - self.opts = {} - - self.get_auth_url = lambda: None - self.authenticate = authenticate - - self._load_endpoints() - - def _load_endpoints(self): - ep_name = 'openstack.client.auth_url' - fn = utils._load_entry_point(ep_name, name=self.auth_system) - if fn: - self.get_auth_url = fn - - ep_name = 'openstack.client.authenticate' - fn = utils._load_entry_point(ep_name, name=self.auth_system) - if fn: - self.authenticate = fn - - def parse_opts(self, args): - return self.opts diff --git a/saharaclient/nova/base.py b/saharaclient/nova/base.py deleted file mode 100644 index 6028702..0000000 --- a/saharaclient/nova/base.py +++ /dev/null @@ -1,495 +0,0 @@ -# Copyright 2010 Jacob Kaplan-Moss - -# Copyright 2011 OpenStack Foundation -# 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. - -""" -Base utilities to build API operation managers and objects on top of. -""" - -import abc -import base64 -import contextlib -import hashlib -import inspect -import os -import threading - -import six - -from saharaclient.nova import utils -from saharaclient.openstack.common.apiclient import exceptions -from saharaclient.openstack.common import strutils - - -def getid(obj): - """Abstracts the common pattern of allowing an object or ID as parameter. - - Abstracts the common pattern of allowing both an object or an object's - ID as a parameter when dealing with relationships. - """ - try: - return obj.id - except AttributeError: - return obj - - -class Manager(utils.HookableMixin): - """Managers interact with API and provide CRUD operations for them. - - Managers interact with a particular type of API (servers, flavors, - images, etc.) and provide CRUD operations for them. - """ - resource_class = None - cache_lock = threading.RLock() - - def __init__(self, api): - self.api = api - - def _list(self, url, response_key, obj_class=None, body=None): - if body: - _resp, body = self.api.client.post(url, body=body) - else: - _resp, body = self.api.client.get(url) - - if obj_class is None: - obj_class = self.resource_class - - data = body[response_key] - # NOTE(ja): keystone returns values as list as {'values': [ ... ]} - # unlike other services which just return the list... - if isinstance(data, dict): - try: - data = data['values'] - except KeyError: - pass - - with self.completion_cache('human_id', obj_class, mode="w"): - with self.completion_cache('uuid', obj_class, mode="w"): - return [obj_class(self, res, loaded=True) - for res in data if res] - - @contextlib.contextmanager - def completion_cache(self, cache_type, obj_class, mode): - """Completion cache store items used for bash autocompletion. - - The completion cache store items that can be used for bash - autocompletion, like UUIDs or human-friendly IDs. - - A resource listing will clear and repopulate the cache. - - A resource create will append to the cache. - - Delete is not handled because listings are assumed to be performed - often enough to keep the cache reasonably up-to-date. - """ - # NOTE(wryan): This lock protects read and write access to the - # completion caches - with self.cache_lock: - base_dir = utils.env('NOVACLIENT_UUID_CACHE_DIR', - default="~/.novaclient") - - # NOTE(sirp): Keep separate UUID caches for each username + - # endpoint pair - username = utils.env('OS_USERNAME', 'NOVA_USERNAME') - url = utils.env('OS_URL', 'NOVA_URL') - uniqifier = hashlib.md5(username.encode('utf-8') + - url.encode('utf-8')).hexdigest() - - cache_dir = os.path.expanduser(os.path.join(base_dir, uniqifier)) - - try: - os.makedirs(cache_dir, 0o755) - except OSError: - # NOTE(kiall): This is typically either permission denied while - # attempting to create the directory, or the - # directory already exists. Either way, don't - # fail. - pass - - resource = obj_class.__name__.lower() - filename = "%s-%s-cache" % (resource, cache_type.replace('_', '-')) - path = os.path.join(cache_dir, filename) - - cache_attr = "_%s_cache" % cache_type - - try: - setattr(self, cache_attr, open(path, mode)) - except IOError: - # NOTE(kiall): This is typically a permission denied while - # attempting to write the cache file. - pass - - try: - yield - finally: - cache = getattr(self, cache_attr, None) - if cache: - cache.close() - delattr(self, cache_attr) - - def write_to_completion_cache(self, cache_type, val): - cache = getattr(self, "_%s_cache" % cache_type, None) - if cache: - cache.write("%s\n" % val) - - def _get(self, url, response_key): - _resp, body = self.api.client.get(url) - return self.resource_class(self, body[response_key], loaded=True) - - def _create(self, url, body, response_key, return_raw=False, **kwargs): - self.run_hooks('modify_body_for_create', body, **kwargs) - _resp, body = self.api.client.post(url, body=body) - if return_raw: - return body[response_key] - - with self.completion_cache('human_id', self.resource_class, mode="a"): - with self.completion_cache('uuid', self.resource_class, mode="a"): - return self.resource_class(self, body[response_key]) - - def _delete(self, url): - _resp, _body = self.api.client.delete(url) - - def _update(self, url, body, response_key=None, **kwargs): - self.run_hooks('modify_body_for_update', body, **kwargs) - _resp, body = self.api.client.put(url, body=body) - if body: - if response_key: - return self.resource_class(self, body[response_key]) - else: - return self.resource_class(self, body) - - -@six.add_metaclass(abc.ABCMeta) -class ManagerWithFind(Manager): - """Like a `Manager`, but with additional `find()`/`findall()` methods.""" - - @abc.abstractmethod - def list(self): - pass - - def find(self, **kwargs): - """Find a single item with attributes matching ``**kwargs``. - - This isn't very efficient: it loads the entire list then filters on - the Python side. - """ - matches = self.findall(**kwargs) - num_matches = len(matches) - if num_matches == 0: - msg = "No %s matching %s." % (self.resource_class.__name__, kwargs) - raise exceptions.NotFound(404, msg) - elif num_matches > 1: - raise exceptions.NoUniqueMatch - else: - return matches[0] - - def findall(self, **kwargs): - """Find all items with attributes matching ``**kwargs``. - - This isn't very efficient: it loads the entire list then filters on - the Python side. - """ - found = [] - searches = kwargs.items() - - detailed = True - list_kwargs = {} - - list_argspec = inspect.getargspec(self.list) - if 'detailed' in list_argspec.args: - detailed = ("human_id" not in kwargs and - "name" not in kwargs and - "display_name" not in kwargs) - list_kwargs['detailed'] = detailed - - if 'is_public' in list_argspec.args and 'is_public' in kwargs: - is_public = kwargs['is_public'] - list_kwargs['is_public'] = is_public - if is_public is None: - tmp_kwargs = kwargs.copy() - del tmp_kwargs['is_public'] - searches = tmp_kwargs.items() - - listing = self.list(**list_kwargs) - - for obj in listing: - try: - if all(getattr(obj, attr) == value - for (attr, value) in searches): - if detailed: - found.append(obj) - else: - found.append(self.get(obj.id)) - except AttributeError: - continue - - return found - - -class BootingManagerWithFind(ManagerWithFind): - """Like a `ManagerWithFind`, but has the ability to boot servers.""" - - def _parse_block_device_mapping(self, block_device_mapping): - bdm = [] - - for device_name, mapping in six.iteritems(block_device_mapping): - # - # The mapping is in the format: - # <id>:[<type>]:[<size(GB)>]:[<delete_on_terminate>] - # - bdm_dict = {'device_name': device_name} - - mapping_parts = mapping.split(':') - source_id = mapping_parts[0] - if len(mapping_parts) == 1: - bdm_dict['volume_id'] = source_id - - elif len(mapping_parts) > 1: - source_type = mapping_parts[1] - if source_type.startswith('snap'): - bdm_dict['snapshot_id'] = source_id - else: - bdm_dict['volume_id'] = source_id - - if len(mapping_parts) > 2 and mapping_parts[2]: - bdm_dict['volume_size'] = str(int(mapping_parts[2])) - - if len(mapping_parts) > 3: - bdm_dict['delete_on_termination'] = mapping_parts[3] - - bdm.append(bdm_dict) - return bdm - - def _boot(self, resource_url, response_key, name, image, flavor, - meta=None, files=None, userdata=None, - reservation_id=None, return_raw=False, min_count=None, - max_count=None, security_groups=None, key_name=None, - availability_zone=None, block_device_mapping=None, - block_device_mapping_v2=None, nics=None, scheduler_hints=None, - config_drive=None, admin_pass=None, disk_config=None, **kwargs): - """Create (boot) a new server. - - :param name: Something to name the server. - :param image: The :class:`Image` to boot with. - :param flavor: The :class:`Flavor` to boot onto. - :param meta: A dict of arbitrary key/value metadata to store for this - server. A maximum of five entries is allowed, and both - keys and values must be 255 characters or less. - :param files: A dict of files to overwrite on the server upon boot. - Keys are file names (i.e. ``/etc/passwd``) and values - are the file contents (either as a string or as a - file-like object). A maximum of five entries is allowed, - and each file must be 10k or less. - :param reservation_id: a UUID for the set of servers being requested. - :param return_raw: If True, don't try to coearse the result into - a Resource object. - :param security_groups: list of security group names - :param key_name: (optional extension) name of keypair to inject into - the instance - :param availability_zone: Name of the availability zone for instance - placement. - :param block_device_mapping: A dict of block device mappings for this - server. - :param block_device_mapping_v2: A dict of block device mappings V2 for - this server. - :param nics: (optional extension) an ordered list of nics to be - added to this server, with information about - connected networks, fixed ips, etc. - :param scheduler_hints: (optional extension) arbitrary key-value pairs - specified by the client to help boot an instance. - :param config_drive: (optional extension) value for config drive - either boolean, or volume-id - :param admin_pass: admin password for the server. - :param disk_config: (optional extension) control how the disk is - partitioned when the server is created. - """ - body = {"server": { - "name": name, - "imageRef": str(getid(image)) if image else '', - "flavorRef": str(getid(flavor)), - }} - if userdata: - if hasattr(userdata, 'read'): - userdata = userdata.read() - - userdata = strutils.safe_encode(userdata) - body["server"]["user_data"] = base64.b64encode(userdata) - if meta: - body["server"]["metadata"] = meta - if reservation_id: - body["server"]["reservation_id"] = reservation_id - if key_name: - body["server"]["key_name"] = key_name - if scheduler_hints: - body['os:scheduler_hints'] = scheduler_hints - if config_drive: - body["server"]["config_drive"] = config_drive - if admin_pass: - body["server"]["adminPass"] = admin_pass - if not min_count: - min_count = 1 - if not max_count: - max_count = min_count - body["server"]["min_count"] = min_count - body["server"]["max_count"] = max_count - - if security_groups: - body["server"]["security_groups"] = ( - [{'name': sg} for sg in security_groups] - ) - - # Files are a slight bit tricky. They're passed in a "personality" - # list to the POST. Each item is a dict giving a file name and the - # base64-encoded contents of the file. We want to allow passing - # either an open file *or* some contents as files here. - if files: - personality = body['server']['personality'] = [] - for filepath, file_or_string in sorted(files.items(), - key=lambda x: x[0]): - if hasattr(file_or_string, 'read'): - data = file_or_string.read() - else: - data = file_or_string - personality.append({ - 'path': filepath, - 'contents': base64.b64encode(data.encode('utf-8')), - }) - - if availability_zone: - body["server"]["availability_zone"] = availability_zone - - # Block device mappings are passed as a list of dictionaries - if block_device_mapping: - body['server']['block_device_mapping'] = ( - self._parse_block_device_mapping(block_device_mapping) - ) - elif block_device_mapping_v2: - # Append the image to the list only if we have new style BDMs - if image: - bdm_dict = {'uuid': image.id, 'source_type': 'image', - 'destination_type': 'local', 'boot_index': 0, - 'delete_on_termination': True} - block_device_mapping_v2.insert(0, bdm_dict) - - body['server']['block_device_mapping_v2'] = block_device_mapping_v2 - - if nics is not None: - # NOTE(tr3buchet): nics can be an empty list - all_net_data = [] - for nic_info in nics: - net_data = {} - # if value is empty string, do not send value in body - if nic_info.get('net-id'): - net_data['uuid'] = nic_info['net-id'] - if nic_info.get('v4-fixed-ip'): - net_data['fixed_ip'] = nic_info['v4-fixed-ip'] - if nic_info.get('port-id'): - net_data['port'] = nic_info['port-id'] - all_net_data.append(net_data) - body['server']['networks'] = all_net_data - - if disk_config is not None: - body['server']['OS-DCF:diskConfig'] = disk_config - - return self._create(resource_url, body, response_key, - return_raw=return_raw, **kwargs) - - -class Resource(object): - """A resource represents a particular instance of an object. - - A resource represents a particular instance of an object (server, - flavor, etc). This is pretty much just a bag for attributes. - - :param manager: Manager object - :param info: dictionary representing resource attributes - :param loaded: prevent lazy-loading if set to True - """ - HUMAN_ID = False - NAME_ATTR = 'name' - - def __init__(self, manager, info, loaded=False): - self.manager = manager - self._info = info - self._add_details(info) - self._loaded = loaded - - # NOTE(sirp): ensure `id` is already present because if it isn't we'll - # enter an infinite loop of __getattr__ -> get -> __init__ -> - # __getattr__ -> ... - if 'id' in self.__dict__ and len(str(self.id)) == 36: - self.manager.write_to_completion_cache('uuid', self.id) - - human_id = self.human_id - if human_id: - self.manager.write_to_completion_cache('human_id', human_id) - - @property - def human_id(self): - """Provide a pretty ID which can be used for bash completion.""" - - if self.NAME_ATTR in self.__dict__ and self.HUMAN_ID: - return strutils.to_slug(getattr(self, self.NAME_ATTR)) - return None - - def _add_details(self, info): - for (k, v) in six.iteritems(info): - try: - setattr(self, k, v) - self._info[k] = v - except AttributeError: - # In this case we already defined the attribute on the class - pass - - def __getattr__(self, k): - if k not in self.__dict__: - # NOTE(bcwaldon): disallow lazy-loading if already loaded once - if not self.is_loaded(): - self.get() - return self.__getattr__(k) - - raise AttributeError(k) - else: - return self.__dict__[k] - - def __repr__(self): - reprkeys = sorted(k for k in self.__dict__.keys() - if k[0] != '_' and k != 'manager') - info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) - return "<%s %s>" % (self.__class__.__name__, info) - - def get(self): - # set_loaded() first ... so if we have to bail, we know we tried. - self.set_loaded(True) - if not hasattr(self.manager, 'get'): - return - - new = self.manager.get(self.id) - if new: - self._add_details(new._info) - - def __eq__(self, other): - if not isinstance(other, self.__class__): - return False - if hasattr(self, 'id') and hasattr(other, 'id'): - return self.id == other.id - return self._info == other._info - - def is_loaded(self): - return self._loaded - - def set_loaded(self, val): - self._loaded = val diff --git a/saharaclient/nova/extension.py b/saharaclient/nova/extension.py deleted file mode 100644 index 9a292dc..0000000 --- a/saharaclient/nova/extension.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright 2011 OpenStack Foundation -# 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 saharaclient.nova import base -from saharaclient.nova import utils - - -class Extension(utils.HookableMixin): - """Extension descriptor.""" - - SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__') - - def __init__(self, name, module): - self.name = name - self.module = module - self._parse_extension_module() - - def _parse_extension_module(self): - self.manager_class = None - for attr_name, attr_value in self.module.__dict__.items(): - if attr_name in self.SUPPORTED_HOOKS: - self.add_hook(attr_name, attr_value) - elif utils.safe_issubclass(attr_value, base.Manager): - self.manager_class = attr_value - - def __repr__(self): - return "<Extension '%s'>" % self.name diff --git a/saharaclient/nova/utils.py b/saharaclient/nova/utils.py deleted file mode 100644 index 0fd61eb..0000000 --- a/saharaclient/nova/utils.py +++ /dev/null @@ -1,384 +0,0 @@ -# -# 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 json -import os -import sys -import textwrap -import uuid - -import pkg_resources -import prettytable -import six - -from saharaclient.openstack.common.apiclient import exceptions -from saharaclient.openstack.common import strutils - - -def arg(*args, **kwargs): - """Decorator for CLI args.""" - def _decorator(func): - add_arg(func, *args, **kwargs) - return func - return _decorator - - -def env(*args, **kwargs): - """returns the first environment variable set - - if none are non-empty, defaults to '' or keyword arg default - """ - for arg in args: - value = os.environ.get(arg, None) - if value: - return value - return kwargs.get('default', '') - - -def add_arg(f, *args, **kwargs): - """Bind CLI arguments to a shell.py `do_foo` function.""" - - if not hasattr(f, 'arguments'): - f.arguments = [] - - # NOTE(sirp): avoid dups that can occur when the module is shared across - # tests. - if (args, kwargs) not in f.arguments: - # Because of the semantics of decorator composition if we just append - # to the options list positional options will appear to be backwards. - f.arguments.insert(0, (args, kwargs)) - - -def bool_from_str(val): - """Convert a string representation of a bool into a bool value.""" - - if not val: - return False - try: - return bool(int(val)) - except ValueError: - if val.lower() in ['true', 'yes', 'y']: - return True - if val.lower() in ['false', 'no', 'n']: - return False - raise - - -def add_resource_manager_extra_kwargs_hook(f, hook): - """Add hook to bind CLI arguments to ResourceManager calls. - - The `do_foo` calls in shell.py will receive CLI args and then in turn pass - them through to the ResourceManager. Before passing through the args, the - hooks registered here will be called, giving us a chance to add extra - kwargs (taken from the command-line) to what's passed to the - ResourceManager. - """ - if not hasattr(f, 'resource_manager_kwargs_hooks'): - f.resource_manager_kwargs_hooks = [] - - names = [h.__name__ for h in f.resource_manager_kwargs_hooks] - if hook.__name__ not in names: - f.resource_manager_kwargs_hooks.append(hook) - - -def unauthenticated(f): - """Adds 'unauthenticated' attribute to decorated function. - - Usage: - @unauthenticated - def mymethod(f): - ... - """ - f.unauthenticated = True - return f - - -def isunauthenticated(f): - """Checks to see if the function is marked as not requiring authentication. - - Checks to see if the function is marked as not requiring authentication - with the @unauthenticated decorator. Returns True if decorator is - set to True, False otherwise. - """ - return getattr(f, 'unauthenticated', False) - - -def service_type(stype): - """Adds 'service_type' attribute to decorated function. - - Usage: - @service_type('volume') - def mymethod(f): - ... - """ - def inner(f): - f.service_type = stype - return f - return inner - - -def get_service_type(f): - """Retrieves service type from function.""" - return getattr(f, 'service_type', None) - - -def pretty_choice_list(l): - return ', '.join("'%s'" % i for i in l) - - -def print_list(objs, fields, formatters={}, sortby_index=None): - if sortby_index is None: - sortby = None - else: - sortby = fields[sortby_index] - mixed_case_fields = ['serverId'] - pt = prettytable.PrettyTable([f for f in fields], caching=False) - pt.align = 'l' - - for o in objs: - row = [] - for field in fields: - if field in formatters: - row.append(formatters[field](o)) - else: - if field in mixed_case_fields: - field_name = field.replace(' ', '_') - else: - field_name = field.lower().replace(' ', '_') - data = getattr(o, field_name, '') - if data is None: - data = '-' - row.append(data) - pt.add_row(row) - - if sortby is not None: - result = strutils.safe_encode(pt.get_string(sortby=sortby)) - else: - result = strutils.safe_encode(pt.get_string()) - - print(result) - - -def _flatten(data, prefix=None): - """Flatten a dict, using name as a prefix for the keys of dict. - - >>> _flatten('cpu_info', {'arch':'x86_64'}) - [('cpu_info_arch': 'x86_64')] - - """ - if isinstance(data, dict): - for key, value in six.iteritems(data): - new_key = '%s_%s' % (prefix, key) if prefix else key - if isinstance(value, (dict, list)): - for item in _flatten(value, new_key): - yield item - else: - yield new_key, value - else: - yield prefix, data - - -def flatten_dict(data): - """Return a new flattened dict. - - Return a new dict whose sub-dicts have been merged into the - original. Each of the parents keys are prepended to the child's - to prevent collisions. Any string elements will be JSON parsed - before flattening. - - >>> flatten_dict({'service': {'host':'cloud9@compute-068', 'id': 143}}) - {'service_host': colud9@compute-068', 'service_id': 143} - - """ - data = data.copy() - # Try and decode any nested JSON structures. - for key, value in six.iteritems(data): - if isinstance(value, six.string_types): - try: - data[key] = json.loads(value) - except ValueError: - pass - - return dict(_flatten(data)) - - -def print_dict(d, dict_property="Property", dict_value="Value", wrap=0): - pt = prettytable.PrettyTable([dict_property, dict_value], caching=False) - pt.align = 'l' - for k, v in sorted(d.items()): - # convert dict to str to check length - if isinstance(v, dict): - v = str(v) - if wrap > 0: - v = textwrap.fill(str(v), wrap) - # if value has a newline, add in multiple rows - # e.g. fault with stacktrace - if v and isinstance(v, six.string_types) and r'\n' in v: - lines = v.strip().split(r'\n') - col1 = k - for line in lines: - pt.add_row([col1, line]) - col1 = '' - else: - if v is None: - v = '-' - pt.add_row([k, v]) - print(strutils.safe_encode(pt.get_string())) - - -def find_resource(manager, name_or_id, **find_args): - """Helper for the _find_* methods.""" - # first try to get entity as integer id - try: - return manager.get(int(name_or_id)) - except (TypeError, ValueError, exceptions.NotFound): - pass - - # now try to get entity as uuid - try: - tmp_id = strutils.safe_encode(name_or_id) - if six.PY3: - tmp_id = tmp_id.decode() - uuid.UUID(tmp_id) - return manager.get(tmp_id) - except (TypeError, ValueError, exceptions.NotFound): - pass - - # for str id which is not uuid (for Flavor search currently) - if getattr(manager, 'is_alphanum_id_allowed', False): - try: - return manager.get(name_or_id) - except exceptions.NotFound: - pass - - try: - try: - return manager.find(human_id=name_or_id, **find_args) - except exceptions.NotFound: - pass - - # finally try to find entity by name - try: - resource = getattr(manager, 'resource_class', None) - name_attr = resource.NAME_ATTR if resource else 'name' - kwargs = {name_attr: name_or_id} - kwargs.update(find_args) - return manager.find(**kwargs) - except exceptions.NotFound: - msg = ("No %s with a name or ID of '%s' exists." % - (manager.resource_class.__name__.lower(), name_or_id)) - raise exceptions.CommandError(msg) - except exceptions.NoUniqueMatch: - msg = ("Multiple %s matches found for '%s', use an ID to be more" - " specific." % (manager.resource_class.__name__.lower(), - name_or_id)) - raise exceptions.CommandError(msg) - - -def _format_servers_list_networks(server): - output = [] - for (network, addresses) in server.networks.items(): - if len(addresses) == 0: - continue - addresses_csv = ', '.join(addresses) - group = "%s=%s" % (network, addresses_csv) - output.append(group) - - return '; '.join(output) - - -def _format_security_groups(groups): - return ', '.join(group['name'] for group in groups) - - -def _format_field_name(attr): - """Format an object attribute in a human-friendly way.""" - # Split at ':' and leave the extension name as-is. - parts = attr.rsplit(':', 1) - name = parts[-1].replace('_', ' ') - # Don't title() on mixed case - if name.isupper() or name.islower(): - name = name.title() - parts[-1] = name - return ': '.join(parts) - - -def _make_field_formatter(attr, filters=None): - """Return a field name & formatter suitable for passing to print_list. - - Given an object attribute, return a formatted field name and a - formatter suitable for passing to print_list. - - Optionally pass a dict mapping attribute names to a function. The function - will be passed the value of the attribute and should return the string to - display. - """ - filter_ = None - if filters: - filter_ = filters.get(attr) - - def get_field(obj): - field = getattr(obj, attr, '') - if field and filter_: - field = filter_(field) - return field - - name = _format_field_name(attr) - formatter = get_field - return name, formatter - - -class HookableMixin(object): - """Mixin so classes can register and run hooks.""" - _hooks_map = {} - - @classmethod - def add_hook(cls, hook_type, hook_func): - if hook_type not in cls._hooks_map: - cls._hooks_map[hook_type] = [] - - cls._hooks_map[hook_type].append(hook_func) - - @classmethod - def run_hooks(cls, hook_type, *args, **kwargs): - hook_funcs = cls._hooks_map.get(hook_type) or [] - for hook_func in hook_funcs: - hook_func(*args, **kwargs) - - -def safe_issubclass(*args): - """Like issubclass, but will just return False if not a class.""" - - try: - if issubclass(*args): - return True - except TypeError: - pass - - return False - - -def import_class(import_str): - """Returns a class from a string including module and class.""" - mod_str, _sep, class_str = import_str.rpartition('.') - __import__(mod_str) - return getattr(sys.modules[mod_str], class_str) - - -def _load_entry_point(ep_name, name=None): - """Try to load the entry point ep_name that matches name.""" - for ep in pkg_resources.iter_entry_points(ep_name, name=name): - try: - return ep.load() - except (ImportError, pkg_resources.UnknownExtra, AttributeError): - continue diff --git a/saharaclient/shell.py b/saharaclient/shell.py index 6d546df..0ffc6f2 100644 --- a/saharaclient/shell.py +++ b/saharaclient/shell.py @@ -25,15 +25,9 @@ Command-line interface to the OpenStack Sahara API. from __future__ import print_function import argparse import getpass -import glob -import imp -import itertools import logging -import os -import pkgutil import sys -import pkg_resources import six @@ -55,8 +49,7 @@ except ImportError: from saharaclient.api import client from saharaclient.api import shell as shell_api -from saharaclient.nova import auth_plugin as nova_auth_plugin -from saharaclient.nova import extension as nova_extension +from saharaclient.openstack.common.apiclient import auth from saharaclient.openstack.common.apiclient import exceptions as exc from saharaclient.openstack.common import cliutils from saharaclient.openstack.common import strutils @@ -271,57 +264,19 @@ class OpenStackSaharaShell(object): # type=positive_non_zero_float, # help="Set HTTP call timeout (in seconds)") - parser.add_argument('--os-username', - metavar='<auth-user-name>', - default=cliutils.env('OS_USERNAME', - 'SAHARA_USERNAME'), - help='Defaults to env[OS_USERNAME].') - parser.add_argument('--os_username', - help=argparse.SUPPRESS) - - parser.add_argument('--os-password', - metavar='<auth-password>', - default=cliutils.env('OS_PASSWORD', - 'SAHARA_PASSWORD'), - help='Defaults to env[OS_PASSWORD].') - parser.add_argument('--os_password', - help=argparse.SUPPRESS) - - parser.add_argument('--os-tenant-name', - metavar='<auth-tenant-name>', - default=cliutils.env('OS_TENANT_NAME', - 'SAHARA_PROJECT_ID'), - help='Defaults to env[OS_TENANT_NAME].') - parser.add_argument('--os_tenant_name', - help=argparse.SUPPRESS) - parser.add_argument('--os-tenant-id', metavar='<auth-tenant-id>', default=cliutils.env('OS_TENANT_ID'), help='Defaults to env[OS_TENANT_ID].') - parser.add_argument('--os-auth-url', - metavar='<auth-url>', - default=cliutils.env('OS_AUTH_URL', 'SAHARA_URL'), - help='Defaults to env[OS_AUTH_URL].') - parser.add_argument('--os_auth_url', - help=argparse.SUPPRESS) - # NA # parser.add_argument('--os-region-name', # metavar='<region-name>', -# default=utils.env('OS_REGION_NAME', 'SAHARA_REGION_NAME'), +# default=cliutils.env('OS_REGION_NAME', 'SAHARA_REGION_NAME'), # help='Defaults to env[OS_REGION_NAME].') # parser.add_argument('--os_region_name', # help=argparse.SUPPRESS) - parser.add_argument('--os-auth-system', - metavar='<auth-system>', - default=cliutils.env('OS_AUTH_SYSTEM'), - help='Defaults to env[OS_AUTH_SYSTEM].') - parser.add_argument('--os_auth_system', - help=argparse.SUPPRESS) - parser.add_argument('--service-type', metavar='<service-type>', help='Defaults to data_processing for all ' @@ -395,7 +350,7 @@ class OpenStackSaharaShell(object): help=argparse.SUPPRESS) # The auth-system-plugins might require some extra options - nova_auth_plugin.load_auth_system_opts(parser) + auth.load_auth_system_opts(parser) return parser @@ -416,60 +371,10 @@ class OpenStackSaharaShell(object): self._find_actions(subparsers, actions_module) self._find_actions(subparsers, self) - for extension in self.extensions: - self._find_actions(subparsers, extension.module) - self._add_bash_completion_subparser(subparsers) return parser - def _discover_extensions(self, version): - extensions = [] - for name, module in itertools.chain( - self._discover_via_python_path(), - self._discover_via_contrib_path(version), - self._discover_via_entry_points()): - - extension = nova_extension.Extension(name, module) - extensions.append(extension) - - return extensions - - def _discover_via_python_path(self): - for (module_loader, name, _ispkg) in pkgutil.iter_modules(): - if name.endswith('_python_saharaclient_ext'): - if not hasattr(module_loader, 'load_module'): - # Python 2.6 compat: actually get an ImpImporter obj - module_loader = module_loader.find_module(name) - - module = module_loader.load_module(name) - if hasattr(module, 'extension_name'): - name = module.extension_name - - yield name, module - - def _discover_via_contrib_path(self, version): - module_path = os.path.dirname(os.path.abspath(__file__)) - version_str = "v%s" % version.replace('.', '_') - ext_path = os.path.join(module_path, version_str, 'contrib') - ext_glob = os.path.join(ext_path, "*.py") - - for ext_path in glob.iglob(ext_glob): - name = os.path.basename(ext_path)[:-3] - - if name == "__init__": - continue - - module = imp.load_source(name, ext_path) - yield name, module - - def _discover_via_entry_points(self): - for ep in pkg_resources.iter_entry_points('saharaclient.extension'): - name = ep.name - module = ep.load() - - yield name, module - def _add_bash_completion_subparser(self, subparsers): subparser = ( subparsers.add_parser('bash_completion', @@ -520,15 +425,6 @@ class OpenStackSaharaShell(object): (options, args) = parser.parse_known_args(argv) self.setup_debugging(options.debug) - # Discover available auth plugins - nova_auth_plugin.discover_auth_systems() - - # build available subcommands based on version - self.extensions = ( - self._discover_extensions(options.sahara_api_version) - ) - self._run_extension_hooks('__pre_parse_args__') - # NOTE(dtroyer): Hackery to handle --endpoint_type due to argparse # thinking usage-list --end is ambiguous; but it # works fine with only --endpoint-type present @@ -547,7 +443,6 @@ class OpenStackSaharaShell(object): return 0 args = subcommand_parser.parse_args(argv) - self._run_extension_hooks('__post_parse_args__', args) # Short-circuit and deal with help right away. if args.func == self.do_help: @@ -580,7 +475,7 @@ class OpenStackSaharaShell(object): ) if os_auth_system and os_auth_system != "keystone": - auth_plugin = nova_auth_plugin.load_plugin(os_auth_system) + auth_plugin = auth.load_plugin(os_auth_system) else: auth_plugin = None @@ -713,11 +608,6 @@ class OpenStackSaharaShell(object): results.append(Tyme("Total", total)) cliutils.print_list(results, ["url", "seconds"], sortby_index=None) - def _run_extension_hooks(self, hook_type, *args, **kwargs): - """Run hooks for all registered extensions.""" - for extension in self.extensions: - extension.run_hooks(hook_type, *args, **kwargs) - def do_bash_completion(self, _args): """Prints arguments for bash-completion. diff --git a/saharaclient/tests/unit/nova/test_shell.py b/saharaclient/tests/unit/nova/test_shell.py index d33f6f0..287b51c 100644 --- a/saharaclient/tests/unit/nova/test_shell.py +++ b/saharaclient/tests/unit/nova/test_shell.py @@ -275,7 +275,7 @@ class ShellTest(utils.TestCase): '+------+------------+----------+------+-------------+\n' '| name | id | username | tags | description |\n' '+------+------------+----------+------+-------------+\n' - '| fake | aaa-bb-ccc | you | | - |\n' + '| fake | aaa-bb-ccc | you | | None |\n' '+------+------------+----------+------+-------------+\n' ) self.make_env() |