summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Reshetnyak <sreshetniak@mirantis.com>2014-09-21 21:20:37 +0400
committerSergey Reshetnyak <sreshetniak@mirantis.com>2014-09-28 14:53:23 +0400
commitd02f0e1daa411bdde40526f61b031c718c79ba5f (patch)
tree5c89729926e3c7af0afc65cd9dc11e750f601244
parentccec5b71edefe27b91ea461804767b2b17bd22a3 (diff)
downloadpython-saharaclient-d02f0e1daa411bdde40526f61b031c718c79ba5f.tar.gz
Use base utils from oslo-incubator instead copy-pasted from nova
Change-Id: Ie7bfbfdce51f5d45b51f02551a309736c3d0d752
-rw-r--r--saharaclient/api/shell.py2
-rw-r--r--saharaclient/nova/__init__.py0
-rw-r--r--saharaclient/nova/auth_plugin.py143
-rw-r--r--saharaclient/nova/base.py495
-rw-r--r--saharaclient/nova/extension.py39
-rw-r--r--saharaclient/nova/utils.py384
-rw-r--r--saharaclient/shell.py118
-rw-r--r--saharaclient/tests/unit/nova/test_shell.py2
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()