diff --git a/awx/lib/site-packages/README b/awx/lib/site-packages/README index c9376f750b..c50c8f14d9 100644 --- a/awx/lib/site-packages/README +++ b/awx/lib/site-packages/README @@ -53,13 +53,13 @@ pexpect==3.1 (pexpect/*, excluded pxssh.py, fdpexpect.py, FSM.py, screen.py, ANSI.py) pip==1.5.4 (pip/*, excluded bin/pip*) prettytable==0.7.2 (prettytable.py) -pyrax==1.9.0 (pyrax/*) +pyrax==1.9.3 (pyrax/*) python-cinderclient==1.1.1 (cinderclient/*) python-dateutil==2.4.0 (dateutil/*) python-glanceclient==0.17.0 (glanceclient/*) python-ironicclient==0.5.0 (ironicclient/*) python-neutronclient==2.3.11 (neutronclient/*) -python-novaclient==2.23.0 (novaclient/*, excluded bin/nova) +python-novaclient==2.20.0 (novaclient/*, excluded bin/nova) python-swiftclient==2.2.0 (swiftclient/*, excluded bin/swift) python-troveclient==1.0.9 (troveclient/*) pytz==2014.10 (pytz/*) diff --git a/awx/lib/site-packages/novaclient/base.py b/awx/lib/site-packages/novaclient/base.py index 01b4bac944..173eda0858 100644 --- a/awx/lib/site-packages/novaclient/base.py +++ b/awx/lib/site-packages/novaclient/base.py @@ -20,17 +20,13 @@ Base utilities to build API operation managers and objects on top of. """ import abc -import contextlib -import hashlib import inspect -import os -import threading import six from novaclient import exceptions from novaclient.openstack.common.apiclient import base -from novaclient.openstack.common import cliutils +from novaclient import utils Resource = base.Resource @@ -46,17 +42,24 @@ def getid(obj): return obj -class Manager(base.HookableMixin): +class Manager(utils.HookableMixin): """ 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 _write_object_to_completion_cache(self, obj): + if hasattr(self.api, 'write_object_to_completion_cache'): + self.api.write_object_to_completion_cache(obj) + + def _clear_completion_cache_for_class(self, obj_class): + if hasattr(self.api, 'clear_completion_cache_for_class'): + self.api.clear_completion_cache_for_class(obj_class) + def _list(self, url, response_key, obj_class=None, body=None): if body: _resp, body = self.api.client.post(url, body=body) @@ -75,86 +78,22 @@ class Manager(base.HookableMixin): 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] + self._clear_completion_cache_for_class(obj_class) - @contextlib.contextmanager - def alternate_service_type(self, service_type): - original_service_type = self.api.client.service_type - self.api.client.service_type = service_type - try: - yield - finally: - self.api.client.service_type = original_service_type + objs = [] + for res in data: + if res: + obj = obj_class(self, res, loaded=True) + self._write_object_to_completion_cache(obj) + objs.append(obj) - @contextlib.contextmanager - def completion_cache(self, cache_type, obj_class, mode): - """ - 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 = cliutils.env('NOVACLIENT_UUID_CACHE_DIR', - default="~/.novaclient") - - # NOTE(sirp): Keep separate UUID caches for each username + - # endpoint pair - username = cliutils.env('OS_USERNAME', 'NOVA_USERNAME') - url = cliutils.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) + return objs def _get(self, url, response_key): _resp, body = self.api.client.get(url) - return self.resource_class(self, body[response_key], loaded=True) + obj = self.resource_class(self, body[response_key], loaded=True) + self._write_object_to_completion_cache(obj) + return obj def _create(self, url, body, response_key, return_raw=False, **kwargs): self.run_hooks('modify_body_for_create', body, **kwargs) @@ -162,9 +101,9 @@ class Manager(base.HookableMixin): 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]) + obj = self.resource_class(self, body[response_key]) + self._write_object_to_completion_cache(obj) + return obj def _delete(self, url): _resp, _body = self.api.client.delete(url) @@ -241,10 +180,6 @@ class ManagerWithFind(Manager): list_kwargs['search_opts'] = {"name": kwargs["name"]} elif "display_name" in kwargs: list_kwargs['search_opts'] = {"name": kwargs["display_name"]} - if "all_tenants" in kwargs: - all_tenants = kwargs['all_tenants'] - list_kwargs['search_opts']['all_tenants'] = all_tenants - searches = [(k, v) for k, v in searches if k != 'all_tenants'] listing = self.list(**list_kwargs) diff --git a/awx/lib/site-packages/novaclient/client.py b/awx/lib/site-packages/novaclient/client.py index 32eb775054..b0d9aea107 100644 --- a/awx/lib/site-packages/novaclient/client.py +++ b/awx/lib/site-packages/novaclient/client.py @@ -21,15 +21,16 @@ OpenStack Client interface. Handles the REST calls and responses. """ import copy +import errno import functools +import glob import hashlib import logging +import os import re -import socket import time from keystoneclient import adapter -from oslo.utils import importutils from oslo.utils import netutils import requests from requests import adapters @@ -42,19 +43,9 @@ except ImportError: from six.moves.urllib import parse from novaclient import exceptions -from novaclient.i18n import _ +from novaclient.openstack.common.gettextutils import _ from novaclient import service_catalog - - -class TCPKeepAliveAdapter(adapters.HTTPAdapter): - """The custom adapter used to set TCP Keep-Alive on all connections.""" - def init_poolmanager(self, *args, **kwargs): - if requests.__version__ >= '2.4.1': - kwargs.setdefault('socket_options', [ - (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), - ]) - super(TCPKeepAliveAdapter, self).init_poolmanager(*args, **kwargs) +from novaclient import utils class _ClientConnectionPool(object): @@ -67,42 +58,98 @@ class _ClientConnectionPool(object): Store and reuse HTTP adapters per Service URL. """ if url not in self._adapters: - self._adapters[url] = TCPKeepAliveAdapter() + self._adapters[url] = adapters.HTTPAdapter() return self._adapters[url] -class SessionClient(adapter.LegacyJsonAdapter): +class CompletionCache(object): + """The completion cache is how we support tab-completion with novaclient. - def __init__(self, *args, **kwargs): - self.times = [] - super(SessionClient, self).__init__(*args, **kwargs) + The `Manager` writes object IDs and Human-IDs to the completion-cache on + object-show, object-list, and object-create calls. + + The `nova.bash_completion` script then uses these files to provide the + actual tab-completion. + + The cache directory layout is: + + ~/.novaclient/ + / + -id-cache + -human-id-cache + """ + def __init__(self, username, auth_url, attributes=('id', 'human_id')): + self.directory = self._make_directory_name(username, auth_url) + self.attributes = attributes + + def _make_directory_name(self, username, auth_url): + """Creates a unique directory name based on the auth_url and username + of the current user. + """ + uniqifier = hashlib.md5(username.encode('utf-8') + + auth_url.encode('utf-8')).hexdigest() + base_dir = utils.env('NOVACLIENT_UUID_CACHE_DIR', + default="~/.novaclient") + return os.path.expanduser(os.path.join(base_dir, uniqifier)) + + def _prepare_directory(self): + try: + os.makedirs(self.directory, 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 + + def clear_class(self, obj_class): + self._prepare_directory() + + resource = obj_class.__name__.lower() + resource_glob = os.path.join(self.directory, "%s-*-cache" % resource) + + for filename in glob.iglob(resource_glob): + try: + os.unlink(filename) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + def _write_attribute(self, resource, attribute, value): + self._prepare_directory() + + filename = "%s-%s-cache" % (resource, attribute.replace('_', '-')) + path = os.path.join(self.directory, filename) + + with open(path, 'a') as f: + f.write("%s\n" % value) + + def write_object(self, obj): + resource = obj.__class__.__name__.lower() + + for attribute in self.attributes: + value = getattr(obj, attribute, None) + if value: + self._write_attribute(resource, attribute, value) + + +class SessionClient(adapter.LegacyJsonAdapter): def request(self, url, method, **kwargs): # NOTE(jamielennox): The standard call raises errors from # keystoneclient, where we need to raise the novaclient errors. raise_exc = kwargs.pop('raise_exc', True) - start_time = time.time() resp, body = super(SessionClient, self).request(url, method, raise_exc=False, **kwargs) - end_time = time.time() - self.times.append(('%s %s' % (method, url), - start_time, end_time)) - if raise_exc and resp.status_code >= 400: raise exceptions.from_response(resp, body, url, method) return resp, body - def get_timings(self): - return self.times - - def reset_timings(self): - self.times = [] - def _original_only(f): """Indicates and enforces that this function can only be used if we are @@ -145,7 +192,7 @@ class HTTPClient(object): self.tenant_id = tenant_id self._connection_pool = (_ClientConnectionPool() - if connection_pool else None) + if connection_pool else None) # This will be called by #_get_password if self.password is None. # EG if a password can only be obtained by prompting the user, but a @@ -212,11 +259,6 @@ class HTTPClient(object): # otherwise we will get all the requests logging messages rql.setLevel(logging.WARNING) - # NOTE(melwitt): Service catalog is only set if bypass_url isn't - # used. Otherwise, we can cache using services_url. - self.service_catalog = None - self.services_url = {} - def use_token_cache(self, use_it): self.os_cache = use_it @@ -274,7 +316,7 @@ class HTTPClient(object): if not self.http_log_debug: return - string_parts = ['curl -g -i'] + string_parts = ['curl -i'] if not kwargs.get('verify', True): string_parts.append(' --insecure') @@ -335,10 +377,10 @@ class HTTPClient(object): self._session.close() self._current_url = service_url self._logger.debug( - "New session created for: (%s)" % service_url) + "New session created for: (%s)" % service_url) self._session = requests.Session() self._session.mount(service_url, - self._connection_pool.get(service_url)) + self._connection_pool.get(service_url)) return self._session elif self._session: return self._session @@ -378,7 +420,7 @@ class HTTPClient(object): # or 'actively refused' in the body, so that's what we'll do. if resp.status_code == 400: if ('Connection refused' in resp.text or - 'actively refused' in resp.text): + 'actively refused' in resp.text): raise exceptions.ConnectionRefused(resp.text) try: body = json.loads(resp.text) @@ -410,12 +452,7 @@ class HTTPClient(object): path = re.sub(r'v[1-9]/[a-z0-9]+$', '', path) url = parse.urlunsplit((scheme, netloc, path, None, None)) else: - if self.service_catalog: - url = self.get_service_url(self.service_type) + url - else: - # NOTE(melwitt): The service catalog is not available - # when bypass_url is used. - url = self.management_url + url + url = self.management_url + url # Perform the request once. If we get a 401 back then it # might be because the auth token expired, so try to @@ -458,19 +495,6 @@ class HTTPClient(object): def delete(self, url, **kwargs): return self._cs_request(url, 'DELETE', **kwargs) - def get_service_url(self, service_type): - if service_type not in self.services_url: - url = self.service_catalog.url_for( - attr='region', - filter_value=self.region_name, - endpoint_type=self.endpoint_type, - service_type=service_type, - service_name=self.service_name, - volume_service_name=self.volume_service_name,) - url = url.rstrip('/') - self.services_url[service_type] = url - return self.services_url[service_type] - def _extract_service_catalog(self, url, resp, body, extract_token=True): """See what the auth service told us and process the response. We may get redirected to another site, fail or actually get @@ -487,7 +511,14 @@ class HTTPClient(object): self.auth_token = self.service_catalog.get_token() self.tenant_id = self.service_catalog.get_tenant_id() - self.management_url = self.get_service_url(self.service_type) + management_url = self.service_catalog.url_for( + attr='region', + filter_value=self.region_name, + endpoint_type=self.endpoint_type, + service_type=self.service_type, + service_name=self.service_name, + volume_service_name=self.volume_service_name,) + self.management_url = management_url.rstrip('/') return None except exceptions.AmbiguousEndpoints: print(_("Found more than one valid endpoint. Use a more " @@ -527,10 +558,6 @@ class HTTPClient(object): extract_token=False) def authenticate(self): - if not self.auth_url: - msg = _("Authentication requires 'auth_url', which should be " - "specified in '%s'") % self.__class__.__name__ - raise exceptions.AuthorizationFailure(msg) magic_tuple = netutils.urlsplit(self.auth_url) scheme, netloc, path, query, frag = magic_tuple port = magic_tuple.port @@ -677,11 +704,11 @@ def _construct_http_client(username=None, password=None, project_id=None, auth_token=None, cacert=None, tenant_id=None, user_id=None, connection_pool=False, session=None, auth=None, user_agent='python-novaclient', - interface=None, **kwargs): + **kwargs): if session: return SessionClient(session=session, auth=auth, - interface=interface or endpoint_type, + interface=endpoint_type, service_type=service_type, region_name=region_name, service_name=service_name, @@ -718,9 +745,9 @@ def _construct_http_client(username=None, password=None, project_id=None, def get_client_class(version): version_map = { - '1.1': 'novaclient.v2.client.Client', - '2': 'novaclient.v2.client.Client', - '3': 'novaclient.v2.client.Client', + '1.1': 'novaclient.v1_1.client.Client', + '2': 'novaclient.v1_1.client.Client', + '3': 'novaclient.v3.client.Client', } try: client_path = version_map[str(version)] @@ -730,7 +757,7 @@ def get_client_class(version): 'keys': ', '.join(version_map.keys())} raise exceptions.UnsupportedVersion(msg) - return importutils.import_class(client_path) + return utils.import_class(client_path) def Client(version, *args, **kwargs): diff --git a/awx/lib/site-packages/novaclient/extension.py b/awx/lib/site-packages/novaclient/extension.py index 9cfcc13d9e..ac105070a6 100644 --- a/awx/lib/site-packages/novaclient/extension.py +++ b/awx/lib/site-packages/novaclient/extension.py @@ -14,11 +14,10 @@ # under the License. from novaclient import base -from novaclient.openstack.common.apiclient import base as common_base from novaclient import utils -class Extension(common_base.HookableMixin): +class Extension(utils.HookableMixin): """Extension descriptor.""" SUPPORTED_HOOKS = ('__pre_parse_args__', '__post_parse_args__') diff --git a/awx/lib/site-packages/novaclient/openstack/common/__init__.py b/awx/lib/site-packages/novaclient/openstack/common/__init__.py index e69de29bb2..d1223eaf76 100644 --- a/awx/lib/site-packages/novaclient/openstack/common/__init__.py +++ b/awx/lib/site-packages/novaclient/openstack/common/__init__.py @@ -0,0 +1,17 @@ +# +# 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 six + + +six.add_move(six.MovedModule('mox', 'mox', 'mox3.mox')) diff --git a/awx/lib/site-packages/novaclient/openstack/common/apiclient/base.py b/awx/lib/site-packages/novaclient/openstack/common/apiclient/base.py index cc5c8b005a..8380ba397f 100644 --- a/awx/lib/site-packages/novaclient/openstack/common/apiclient/base.py +++ b/awx/lib/site-packages/novaclient/openstack/common/apiclient/base.py @@ -26,12 +26,13 @@ Base utilities to build API operation managers and objects on top of. import abc import copy -from oslo.utils import strutils import six from six.moves.urllib import parse -from novaclient.openstack.common._i18n import _ from novaclient.openstack.common.apiclient import exceptions +from novaclient.openstack.common.gettextutils import _ +from novaclient.openstack.common import strutils +from novaclient.openstack.common import uuidutils def getid(obj): @@ -99,13 +100,12 @@ class BaseManager(HookableMixin): super(BaseManager, self).__init__() self.client = client - def _list(self, url, response_key=None, obj_class=None, json=None): + def _list(self, url, response_key, obj_class=None, json=None): """List the collection. :param url: a partial URL, e.g., '/servers' :param response_key: the key to be looked up in response dictionary, - e.g., 'servers'. If response_key is None - all response body - will be used. + e.g., 'servers' :param obj_class: class for constructing the returned objects (self.resource_class will be used by default) :param json: data that will be encoded as JSON and passed in POST @@ -119,7 +119,7 @@ class BaseManager(HookableMixin): if obj_class is None: obj_class = self.resource_class - data = body[response_key] if response_key is not None else body + data = body[response_key] # NOTE(ja): keystone returns values as list as {'values': [ ... ]} # unlike other services which just return the list... try: @@ -129,17 +129,15 @@ class BaseManager(HookableMixin): return [obj_class(self, res, loaded=True) for res in data if res] - def _get(self, url, response_key=None): + def _get(self, url, response_key): """Get an object from collection. :param url: a partial URL, e.g., '/servers' :param response_key: the key to be looked up in response dictionary, - e.g., 'server'. If response_key is None - all response body - will be used. + e.g., 'server' """ body = self.client.get(url).json() - data = body[response_key] if response_key is not None else body - return self.resource_class(self, data, loaded=True) + return self.resource_class(self, body[response_key], loaded=True) def _head(self, url): """Retrieve request headers for an object. @@ -149,23 +147,21 @@ class BaseManager(HookableMixin): resp = self.client.head(url) return resp.status_code == 204 - def _post(self, url, json, response_key=None, return_raw=False): + def _post(self, url, json, response_key, return_raw=False): """Create an object. :param url: a partial URL, e.g., '/servers' :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, - e.g., 'server'. If response_key is None - all response body - will be used. + e.g., 'servers' :param return_raw: flag to force returning raw JSON instead of Python object of self.resource_class """ body = self.client.post(url, json=json).json() - data = body[response_key] if response_key is not None else body if return_raw: - return data - return self.resource_class(self, data) + return body[response_key] + return self.resource_class(self, body[response_key]) def _put(self, url, json=None, response_key=None): """Update an object with PUT method. @@ -174,8 +170,7 @@ class BaseManager(HookableMixin): :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, - e.g., 'servers'. If response_key is None - all response body - will be used. + e.g., 'servers' """ resp = self.client.put(url, json=json) # PUT requests may not return a body @@ -193,8 +188,7 @@ class BaseManager(HookableMixin): :param json: data that will be encoded as JSON and passed in POST request (GET will be sent by default) :param response_key: the key to be looked up in response dictionary, - e.g., 'servers'. If response_key is None - all response body - will be used. + e.g., 'servers' """ body = self.client.patch(url, json=json).json() if response_key is not None: @@ -443,6 +437,21 @@ class Resource(object): self._info = info self._add_details(info) self._loaded = loaded + self._init_completion_cache() + + def _init_completion_cache(self): + cache_write = getattr(self.manager, 'write_to_completion_cache', None) + if not cache_write: + return + + # 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 uuidutils.is_uuid_like(self.id): + cache_write('uuid', self.id) + + if self.human_id: + cache_write('human_id', self.human_id) def __repr__(self): reprkeys = sorted(k diff --git a/awx/lib/site-packages/novaclient/openstack/common/apiclient/client.py b/awx/lib/site-packages/novaclient/openstack/common/apiclient/client.py index c591850e68..a3666daf0a 100644 --- a/awx/lib/site-packages/novaclient/openstack/common/apiclient/client.py +++ b/awx/lib/site-packages/novaclient/openstack/common/apiclient/client.py @@ -33,11 +33,11 @@ try: except ImportError: import json -from oslo.utils import importutils import requests -from novaclient.openstack.common._i18n import _ from novaclient.openstack.common.apiclient import exceptions +from novaclient.openstack.common.gettextutils import _ +from novaclient.openstack.common import importutils _logger = logging.getLogger(__name__) @@ -104,7 +104,7 @@ class HTTPClient(object): return string_parts = [ - "curl -g -i", + "curl -i", "-X '%s'" % method, "'%s'" % url, ] @@ -156,7 +156,7 @@ class HTTPClient(object): requests.Session.request (such as `headers`) or `json` that will be encoded as JSON and used as `data` argument """ - kwargs.setdefault("headers", {}) + kwargs.setdefault("headers", kwargs.get("headers", {})) kwargs["headers"]["User-Agent"] = self.user_agent if self.original_ip: kwargs["headers"]["Forwarded"] = "for=%s;by=%s" % ( @@ -247,10 +247,6 @@ class HTTPClient(object): raise self.cached_token = None client.cached_endpoint = None - if self.auth_plugin.opts.get('token'): - self.auth_plugin.opts['token'] = None - if self.auth_plugin.opts.get('endpoint'): - self.auth_plugin.opts['endpoint'] = None self.authenticate() try: token, endpoint = self.auth_plugin.token_and_endpoint( @@ -361,7 +357,8 @@ class BaseClient(object): "Must be one of: %(version_map)s") % { 'api_name': api_name, 'version': version, - 'version_map': ', '.join(version_map.keys())} + 'version_map': ', '.join(version_map.keys()) + } raise exceptions.UnsupportedVersion(msg) return importutils.import_class(client_path) diff --git a/awx/lib/site-packages/novaclient/openstack/common/apiclient/exceptions.py b/awx/lib/site-packages/novaclient/openstack/common/apiclient/exceptions.py index 15e885b9bc..8e0d2ad9b8 100644 --- a/awx/lib/site-packages/novaclient/openstack/common/apiclient/exceptions.py +++ b/awx/lib/site-packages/novaclient/openstack/common/apiclient/exceptions.py @@ -25,7 +25,7 @@ import sys import six -from novaclient.openstack.common._i18n import _ +from novaclient.openstack.common.gettextutils import _ class ClientException(Exception): @@ -34,6 +34,14 @@ class ClientException(Exception): pass +class MissingArgs(ClientException): + """Supplied arguments are not sufficient for calling a function.""" + def __init__(self, missing): + self.missing = missing + msg = _("Missing arguments: %s") % ", ".join(missing) + super(MissingArgs, self).__init__(msg) + + class ValidationError(ClientException): """Error in validation on API client side.""" pass @@ -439,8 +447,8 @@ def from_response(response, method, url): except ValueError: pass else: - if isinstance(body, dict) and isinstance(body.get("error"), dict): - error = body["error"] + if isinstance(body, dict): + error = list(body.values())[0] kwargs["message"] = error.get("message") kwargs["details"] = error.get("details") elif content_type.startswith("text/"): diff --git a/awx/lib/site-packages/novaclient/openstack/common/apiclient/fake_client.py b/awx/lib/site-packages/novaclient/openstack/common/apiclient/fake_client.py index eeb9b810a7..b6670a67b5 100644 --- a/awx/lib/site-packages/novaclient/openstack/common/apiclient/fake_client.py +++ b/awx/lib/site-packages/novaclient/openstack/common/apiclient/fake_client.py @@ -33,9 +33,7 @@ from six.moves.urllib import parse from novaclient.openstack.common.apiclient import client -def assert_has_keys(dct, required=None, optional=None): - required = required or [] - optional = optional or [] +def assert_has_keys(dct, required=[], optional=[]): for k in required: try: assert k in dct diff --git a/awx/lib/site-packages/novaclient/openstack/common/cliutils.py b/awx/lib/site-packages/novaclient/openstack/common/cliutils.py index ac2cf75734..6a96da57b2 100644 --- a/awx/lib/site-packages/novaclient/openstack/common/cliutils.py +++ b/awx/lib/site-packages/novaclient/openstack/common/cliutils.py @@ -24,21 +24,14 @@ import os import sys import textwrap -from oslo.utils import encodeutils -from oslo.utils import strutils import prettytable import six from six import moves -from novaclient.openstack.common._i18n import _ - - -class MissingArgs(Exception): - """Supplied arguments are not sufficient for calling a function.""" - def __init__(self, missing): - self.missing = missing - msg = _("Missing arguments: %s") % ", ".join(missing) - super(MissingArgs, self).__init__(msg) +from novaclient.openstack.common.apiclient import exceptions +from novaclient.openstack.common.gettextutils import _ +from novaclient.openstack.common import strutils +from novaclient.openstack.common import uuidutils def validate_args(fn, *args, **kwargs): @@ -63,7 +56,7 @@ def validate_args(fn, *args, **kwargs): required_args = argspec.args[:len(argspec.args) - num_defaults] def isbound(method): - return getattr(method, '__self__', None) is not None + return getattr(method, 'im_self', None) is not None if isbound(fn): required_args.pop(0) @@ -71,7 +64,7 @@ def validate_args(fn, *args, **kwargs): missing = [arg for arg in required_args if arg not in kwargs] missing = missing[len(args):] if missing: - raise MissingArgs(missing) + raise exceptions.MissingArgs(missing) def arg(*args, **kwargs): @@ -139,7 +132,7 @@ def isunauthenticated(func): def print_list(objs, fields, formatters=None, sortby_index=0, - mixed_case_fields=None, field_labels=None): + mixed_case_fields=None): """Print a list or objects as a table, one row per object. :param objs: iterable of :class:`Resource` @@ -148,22 +141,14 @@ def print_list(objs, fields, formatters=None, sortby_index=0, :param sortby_index: index of the field for sorting table rows :param mixed_case_fields: fields corresponding to object attributes that have mixed case names (e.g., 'serverId') - :param field_labels: Labels to use in the heading of the table, default to - fields. """ formatters = formatters or {} mixed_case_fields = mixed_case_fields or [] - field_labels = field_labels or fields - if len(field_labels) != len(fields): - raise ValueError(_("Field labels list %(labels)s has different number " - "of elements than fields list %(fields)s"), - {'labels': field_labels, 'fields': fields}) - if sortby_index is None: kwargs = {} else: - kwargs = {'sortby': field_labels[sortby_index]} - pt = prettytable.PrettyTable(field_labels) + kwargs = {'sortby': fields[sortby_index]} + pt = prettytable.PrettyTable(fields, caching=False) pt.align = 'l' for o in objs: @@ -180,7 +165,7 @@ def print_list(objs, fields, formatters=None, sortby_index=0, row.append(data) pt.add_row(row) - print(encodeutils.safe_encode(pt.get_string(**kwargs))) + print(strutils.safe_encode(pt.get_string(**kwargs))) def print_dict(dct, dict_property="Property", wrap=0): @@ -190,7 +175,7 @@ def print_dict(dct, dict_property="Property", wrap=0): :param dict_property: name of the first column :param wrap: wrapping for the second column """ - pt = prettytable.PrettyTable([dict_property, 'Value']) + pt = prettytable.PrettyTable([dict_property, 'Value'], caching=False) pt.align = 'l' for k, v in six.iteritems(dct): # convert dict to str to check length @@ -208,7 +193,7 @@ def print_dict(dct, dict_property="Property", wrap=0): col1 = '' else: pt.add_row([k, v]) - print(encodeutils.safe_encode(pt.get_string())) + print(strutils.safe_encode(pt.get_string())) def get_password(max_password_prompts=3): @@ -232,16 +217,76 @@ def get_password(max_password_prompts=3): return pw +def find_resource(manager, name_or_id, **find_args): + """Look for resource in a given manager. + + Used as a helper for the _find_* methods. + Example: + + def _find_hypervisor(cs, hypervisor): + #Get a hypervisor by name or ID. + return cliutils.find_resource(cs.hypervisors, hypervisor) + """ + # 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 uuidutils.is_uuid_like(tmp_id): + return manager.get(tmp_id) + except (TypeError, ValueError, exceptions.NotFound): + pass + + # for str id which is not uuid + 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 %(name)s with a name or " + "ID of '%(name_or_id)s' exists.") % \ + { + "name": manager.resource_class.__name__.lower(), + "name_or_id": name_or_id + } + raise exceptions.CommandError(msg) + except exceptions.NoUniqueMatch: + msg = _("Multiple %(name)s matches found for " + "'%(name_or_id)s', use an ID to be more specific.") % \ + { + "name": manager.resource_class.__name__.lower(), + "name_or_id": name_or_id + } + raise exceptions.CommandError(msg) + + def service_type(stype): """Adds 'service_type' attribute to decorated function. Usage: - - .. code-block:: python - - @service_type('volume') - def mymethod(f): - ... + @service_type('volume') + def mymethod(f): + ... """ def inner(f): f.service_type = stype diff --git a/awx/lib/site-packages/novaclient/openstack/common/gettextutils.py b/awx/lib/site-packages/novaclient/openstack/common/gettextutils.py index e76b61a117..a48a728125 100644 --- a/awx/lib/site-packages/novaclient/openstack/common/gettextutils.py +++ b/awx/lib/site-packages/novaclient/openstack/common/gettextutils.py @@ -32,24 +32,113 @@ import os from babel import localedata import six -_localedir = os.environ.get('novaclient'.upper() + '_LOCALEDIR') -_t = gettext.translation('novaclient', localedir=_localedir, fallback=True) - -# We use separate translation catalogs for each log level, so set up a -# mapping between the log level name and the translator. The domain -# for the log level is project_name + "-log-" + log_level so messages -# for each level end up in their own catalog. -_t_log_levels = dict( - (level, gettext.translation('novaclient' + '-log-' + level, - localedir=_localedir, - fallback=True)) - for level in ['info', 'warning', 'error', 'critical'] -) - _AVAILABLE_LANGUAGES = {} + +# FIXME(dhellmann): Remove this when moving to oslo.i18n. USE_LAZY = False +class TranslatorFactory(object): + """Create translator functions + """ + + def __init__(self, domain, lazy=False, localedir=None): + """Establish a set of translation functions for the domain. + + :param domain: Name of translation domain, + specifying a message catalog. + :type domain: str + :param lazy: Delays translation until a message is emitted. + Defaults to False. + :type lazy: Boolean + :param localedir: Directory with translation catalogs. + :type localedir: str + """ + self.domain = domain + self.lazy = lazy + if localedir is None: + localedir = os.environ.get(domain.upper() + '_LOCALEDIR') + self.localedir = localedir + + def _make_translation_func(self, domain=None): + """Return a new translation function ready for use. + + Takes into account whether or not lazy translation is being + done. + + The domain can be specified to override the default from the + factory, but the localedir from the factory is always used + because we assume the log-level translation catalogs are + installed in the same directory as the main application + catalog. + + """ + if domain is None: + domain = self.domain + if self.lazy: + return functools.partial(Message, domain=domain) + t = gettext.translation( + domain, + localedir=self.localedir, + fallback=True, + ) + if six.PY3: + return t.gettext + return t.ugettext + + @property + def primary(self): + "The default translation function." + return self._make_translation_func() + + def _make_log_translation_func(self, level): + return self._make_translation_func(self.domain + '-log-' + level) + + @property + def log_info(self): + "Translate info-level log messages." + return self._make_log_translation_func('info') + + @property + def log_warning(self): + "Translate warning-level log messages." + return self._make_log_translation_func('warning') + + @property + def log_error(self): + "Translate error-level log messages." + return self._make_log_translation_func('error') + + @property + def log_critical(self): + "Translate critical-level log messages." + return self._make_log_translation_func('critical') + + +# NOTE(dhellmann): When this module moves out of the incubator into +# oslo.i18n, these global variables can be moved to an integration +# module within each application. + +# Create the global translation functions. +_translators = TranslatorFactory('novaclient') + +# The primary translation function using the well-known name "_" +_ = _translators.primary + +# Translators for log levels. +# +# The abbreviated names are meant to reflect the usual use of a short +# name like '_'. The "L" is for "log" and the other letter comes from +# the level. +_LI = _translators.log_info +_LW = _translators.log_warning +_LE = _translators.log_error +_LC = _translators.log_critical + +# NOTE(dhellmann): End of globals that will move to the application's +# integration module. + + def enable_lazy(): """Convenience function for configuring _() to use lazy gettext @@ -58,41 +147,18 @@ def enable_lazy(): your project is importing _ directly instead of using the gettextutils.install() way of importing the _ function. """ - global USE_LAZY + # FIXME(dhellmann): This function will be removed in oslo.i18n, + # because the TranslatorFactory makes it superfluous. + global _, _LI, _LW, _LE, _LC, USE_LAZY + tf = TranslatorFactory('novaclient', lazy=True) + _ = tf.primary + _LI = tf.log_info + _LW = tf.log_warning + _LE = tf.log_error + _LC = tf.log_critical USE_LAZY = True -def _(msg): - if USE_LAZY: - return Message(msg, domain='novaclient') - else: - if six.PY3: - return _t.gettext(msg) - return _t.ugettext(msg) - - -def _log_translation(msg, level): - """Build a single translation of a log message - """ - if USE_LAZY: - return Message(msg, domain='novaclient' + '-log-' + level) - else: - translator = _t_log_levels[level] - if six.PY3: - return translator.gettext(msg) - return translator.ugettext(msg) - -# Translators for log levels. -# -# The abbreviated names are meant to reflect the usual use of a short -# name like '_'. The "L" is for "log" and the other letter comes from -# the level. -_LI = functools.partial(_log_translation, level='info') -_LW = functools.partial(_log_translation, level='warning') -_LE = functools.partial(_log_translation, level='error') -_LC = functools.partial(_log_translation, level='critical') - - def install(domain, lazy=False): """Install a _() function using the given translation domain. @@ -112,26 +178,9 @@ def install(domain, lazy=False): any available locale. """ if lazy: - # NOTE(mrodden): Lazy gettext functionality. - # - # The following introduces a deferred way to do translations on - # messages in OpenStack. We override the standard _() function - # and % (format string) operation to build Message objects that can - # later be translated when we have more information. - def _lazy_gettext(msg): - """Create and return a Message object. - - Lazy gettext function for a given domain, it is a factory method - for a project/module to get a lazy gettext function for its own - translation domain (i.e. nova, glance, cinder, etc.) - - Message encapsulates a string so that we can translate - it later when needed. - """ - return Message(msg, domain=domain) - from six import moves - moves.builtins.__dict__['_'] = _lazy_gettext + tf = TranslatorFactory(domain, lazy=True) + moves.builtins.__dict__['_'] = tf.primary else: localedir = '%s_LOCALEDIR' % domain.upper() if six.PY3: @@ -274,13 +323,14 @@ class Message(six.text_type): def __radd__(self, other): return self.__add__(other) - def __str__(self): - # NOTE(luisg): Logging in python 2.6 tries to str() log records, - # and it expects specifically a UnicodeError in order to proceed. - msg = _('Message objects do not support str() because they may ' - 'contain non-ascii characters. ' - 'Please use unicode() or translate() instead.') - raise UnicodeError(msg) + if six.PY2: + def __str__(self): + # NOTE(luisg): Logging in python 2.6 tries to str() log records, + # and it expects specifically a UnicodeError in order to proceed. + msg = _('Message objects do not support str() because they may ' + 'contain non-ascii characters. ' + 'Please use unicode() or translate() instead.') + raise UnicodeError(msg) def get_available_languages(domain): @@ -323,8 +373,8 @@ def get_available_languages(domain): 'zh_Hant_HK': 'zh_HK', 'zh_Hant': 'zh_TW', 'fil': 'tl_PH'} - for (locale, alias) in six.iteritems(aliases): - if locale in language_list and alias not in language_list: + for (locale_, alias) in six.iteritems(aliases): + if locale_ in language_list and alias not in language_list: language_list.append(alias) _AVAILABLE_LANGUAGES[domain] = language_list diff --git a/awx/lib/site-packages/novaclient/openstack/common/importutils.py b/awx/lib/site-packages/novaclient/openstack/common/importutils.py index e35b088afa..863255db38 100644 --- a/awx/lib/site-packages/novaclient/openstack/common/importutils.py +++ b/awx/lib/site-packages/novaclient/openstack/common/importutils.py @@ -24,10 +24,10 @@ import traceback 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) try: - __import__(mod_str) return getattr(sys.modules[mod_str], class_str) - except (ValueError, AttributeError): + except AttributeError: raise ImportError('Class %s cannot be found (%s)' % (class_str, traceback.format_exception(*sys.exc_info()))) diff --git a/awx/lib/site-packages/novaclient/service_catalog.py b/awx/lib/site-packages/novaclient/service_catalog.py index 6883f537bd..26f4ccc5cc 100644 --- a/awx/lib/site-packages/novaclient/service_catalog.py +++ b/awx/lib/site-packages/novaclient/service_catalog.py @@ -32,8 +32,8 @@ class ServiceCatalog(object): return self.catalog['access']['token']['tenant']['id'] def url_for(self, attr=None, filter_value=None, - service_type=None, endpoint_type='publicURL', - service_name=None, volume_service_name=None): + service_type=None, endpoint_type='publicURL', + service_name=None, volume_service_name=None): """Fetch the public URL from the Compute service for a particular endpoint attribute. If none given, return the first. See tests for sample service catalog. @@ -72,11 +72,11 @@ class ServiceCatalog(object): endpoints = service['endpoints'] for endpoint in endpoints: # Ignore 1.0 compute endpoints - if (service.get("type") == 'compute' and - endpoint.get('versionId', '2') not in ('1.1', '2')): + if service.get("type") == 'compute' and \ + endpoint.get('versionId', '2') not in ('1.1', '2'): continue - if (not filter_value or - endpoint.get(attr).lower() == filter_value.lower()): + if not filter_value or \ + endpoint.get(attr).lower() == filter_value.lower(): endpoint["serviceName"] = service.get("name") matching_endpoints.append(endpoint) @@ -84,6 +84,6 @@ class ServiceCatalog(object): raise novaclient.exceptions.EndpointNotFound() elif len(matching_endpoints) > 1: raise novaclient.exceptions.AmbiguousEndpoints( - endpoints=matching_endpoints) + endpoints=matching_endpoints) else: return matching_endpoints[0][endpoint_type] diff --git a/awx/lib/site-packages/novaclient/shell.py b/awx/lib/site-packages/novaclient/shell.py index 652713ba63..6623b7fbf4 100644 --- a/awx/lib/site-packages/novaclient/shell.py +++ b/awx/lib/site-packages/novaclient/shell.py @@ -28,12 +28,7 @@ import logging import os import pkgutil import sys -import time -from keystoneclient.auth.identity.generic import password -from keystoneclient.auth.identity.generic import token -from keystoneclient.auth.identity import v3 as identity -from keystoneclient import session as ksession from oslo.utils import encodeutils from oslo.utils import strutils import pkg_resources @@ -52,12 +47,13 @@ import novaclient.auth_plugin from novaclient import client from novaclient import exceptions as exc import novaclient.extension -from novaclient.i18n import _ from novaclient.openstack.common import cliutils +from novaclient.openstack.common.gettextutils import _ from novaclient import utils -from novaclient.v2 import shell as shell_v2 +from novaclient.v1_1 import shell as shell_v1_1 +from novaclient.v3 import shell as shell_v3 -DEFAULT_OS_COMPUTE_API_VERSION = "2" +DEFAULT_OS_COMPUTE_API_VERSION = "1.1" DEFAULT_NOVA_ENDPOINT_TYPE = 'publicURL' # NOTE(cyeoh): Having the service type dependent on the API version # is pretty ugly, but we have to do this because traditionally the @@ -139,7 +135,7 @@ class SecretsHelper(object): if not HAS_KEYRING or not self.args.os_cache: return if (auth_token == self.auth_token and - management_url == self.management_url): + management_url == self.management_url): # Nothing changed.... return if not all([management_url, auth_token, tenant_id]): @@ -159,7 +155,7 @@ class SecretsHelper(object): self._password = self.args.os_password else: verify_pass = strutils.bool_from_string( - cliutils.env("OS_VERIFY_PASSWORD", default=False), True) + utils.env("OS_VERIFY_PASSWORD", default=False), True) self._password = self._prompt_password(verify_pass) if not self._password: raise exc.CommandError( @@ -228,47 +224,13 @@ class NovaClientArgumentParser(argparse.ArgumentParser): choose_from = ' (choose from' progparts = self.prog.partition(' ') self.exit(2, _("error: %(errmsg)s\nTry '%(mainp)s help %(subp)s'" - " for more information.\n") % - {'errmsg': message.split(choose_from)[0], - 'mainp': progparts[0], - 'subp': progparts[2]}) - - def _get_option_tuples(self, option_string): - """returns (action, option, value) candidates for an option prefix - - Returns [first candidate] if all candidates refers to current and - deprecated forms of the same options: "nova boot ... --key KEY" - parsing succeed because --key could only match --key-name, - --key_name which are current/deprecated forms of the same option. - """ - option_tuples = (super(NovaClientArgumentParser, self) - ._get_option_tuples(option_string)) - if len(option_tuples) > 1: - normalizeds = [option.replace('_', '-') - for action, option, value in option_tuples] - if len(set(normalizeds)) == 1: - return option_tuples[:1] - return option_tuples + " for more information.\n") % + {'errmsg': message.split(choose_from)[0], + 'mainp': progparts[0], + 'subp': progparts[2]}) class OpenStackComputeShell(object): - times = [] - - def _append_global_identity_args(self, parser): - # Register the CLI arguments that have moved to the session object. - ksession.Session.register_cli_options(parser) - - parser.set_defaults(insecure=cliutils.env('NOVACLIENT_INSECURE', - default=False)) - - identity.Password.register_argparse_arguments(parser) - - parser.set_defaults(os_username=cliutils.env('OS_USERNAME', - 'NOVA_USERNAME')) - parser.set_defaults(os_password=cliutils.env('OS_PASSWORD', - 'NOVA_PASSWORD')) - parser.set_defaults(os_auth_url=cliutils.env('OS_AUTH_URL', - 'NOVA_URL')) def get_base_parser(self): parser = NovaClientArgumentParser( @@ -281,8 +243,7 @@ class OpenStackComputeShell(object): ) # Global arguments - parser.add_argument( - '-h', '--help', + parser.add_argument('-h', '--help', action='store_true', help=argparse.SUPPRESS, ) @@ -291,118 +252,111 @@ class OpenStackComputeShell(object): action='version', version=novaclient.__version__) - parser.add_argument( - '--debug', + parser.add_argument('--debug', default=False, action='store_true', help=_("Print debugging output")) - parser.add_argument( - '--os-cache', + parser.add_argument('--os-cache', default=strutils.bool_from_string( - cliutils.env('OS_CACHE', default=False), True), + utils.env('OS_CACHE', default=False), True), action='store_true', help=_("Use the auth token cache. Defaults to False if " "env[OS_CACHE] is not set.")) - parser.add_argument( - '--timings', + parser.add_argument('--timings', default=False, action='store_true', help=_("Print call timing info")) - parser.add_argument( - '--os-auth-token', - default=cliutils.env('OS_AUTH_TOKEN'), - help='Defaults to env[OS_AUTH_TOKEN]') + parser.add_argument('--timeout', + default=600, + metavar='', + type=positive_non_zero_float, + help=_("Set HTTP call timeout (in seconds)")) - parser.add_argument( - '--os_username', + parser.add_argument('--os-auth-token', + default=utils.env('OS_AUTH_TOKEN'), + help='Defaults to env[OS_AUTH_TOKEN]') + + parser.add_argument('--os-username', + metavar='', + default=utils.env('OS_USERNAME', 'NOVA_USERNAME'), + help=_('Defaults to env[OS_USERNAME].')) + parser.add_argument('--os_username', help=argparse.SUPPRESS) - parser.add_argument( - '--os_password', + parser.add_argument('--os-user-id', + metavar='', + default=utils.env('OS_USER_ID'), + help=_('Defaults to env[OS_USER_ID].')) + + parser.add_argument('--os-password', + metavar='', + default=utils.env('OS_PASSWORD', 'NOVA_PASSWORD'), + help=_('Defaults to env[OS_PASSWORD].')) + parser.add_argument('--os_password', help=argparse.SUPPRESS) - parser.add_argument( - '--os-tenant-name', + parser.add_argument('--os-tenant-name', metavar='', - default=cliutils.env('OS_TENANT_NAME', 'NOVA_PROJECT_ID'), + default=utils.env('OS_TENANT_NAME', 'NOVA_PROJECT_ID'), help=_('Defaults to env[OS_TENANT_NAME].')) - parser.add_argument( - '--os_tenant_name', + parser.add_argument('--os_tenant_name', help=argparse.SUPPRESS) - parser.add_argument( - '--os-tenant-id', + parser.add_argument('--os-tenant-id', metavar='', - default=cliutils.env('OS_TENANT_ID'), + default=utils.env('OS_TENANT_ID'), help=_('Defaults to env[OS_TENANT_ID].')) - parser.add_argument( - '--os_auth_url', + parser.add_argument('--os-auth-url', + metavar='', + default=utils.env('OS_AUTH_URL', 'NOVA_URL'), + help=_('Defaults to env[OS_AUTH_URL].')) + parser.add_argument('--os_auth_url', help=argparse.SUPPRESS) - parser.add_argument( - '--os-region-name', + parser.add_argument('--os-region-name', metavar='', - default=cliutils.env('OS_REGION_NAME', 'NOVA_REGION_NAME'), + default=utils.env('OS_REGION_NAME', 'NOVA_REGION_NAME'), help=_('Defaults to env[OS_REGION_NAME].')) - parser.add_argument( - '--os_region_name', + parser.add_argument('--os_region_name', help=argparse.SUPPRESS) - parser.add_argument( - '--os-auth-system', + parser.add_argument('--os-auth-system', metavar='', - default=cliutils.env('OS_AUTH_SYSTEM'), + default=utils.env('OS_AUTH_SYSTEM'), help='Defaults to env[OS_AUTH_SYSTEM].') - parser.add_argument( - '--os_auth_system', + parser.add_argument('--os_auth_system', help=argparse.SUPPRESS) - parser.add_argument( - '--service-type', + parser.add_argument('--service-type', metavar='', help=_('Defaults to compute for most actions')) - parser.add_argument( - '--service_type', + parser.add_argument('--service_type', help=argparse.SUPPRESS) - parser.add_argument( - '--service-name', + parser.add_argument('--service-name', metavar='', - default=cliutils.env('NOVA_SERVICE_NAME'), + default=utils.env('NOVA_SERVICE_NAME'), help=_('Defaults to env[NOVA_SERVICE_NAME]')) - parser.add_argument( - '--service_name', + parser.add_argument('--service_name', help=argparse.SUPPRESS) - parser.add_argument( - '--volume-service-name', + parser.add_argument('--volume-service-name', metavar='', - default=cliutils.env('NOVA_VOLUME_SERVICE_NAME'), + default=utils.env('NOVA_VOLUME_SERVICE_NAME'), help=_('Defaults to env[NOVA_VOLUME_SERVICE_NAME]')) - parser.add_argument( - '--volume_service_name', + parser.add_argument('--volume_service_name', help=argparse.SUPPRESS) - parser.add_argument( - '--os-endpoint-type', + parser.add_argument('--endpoint-type', metavar='', - dest='endpoint_type', - default=cliutils.env( - 'NOVA_ENDPOINT_TYPE', - default=cliutils.env( - 'OS_ENDPOINT_TYPE', - default=DEFAULT_NOVA_ENDPOINT_TYPE)), - help=_('Defaults to env[NOVA_ENDPOINT_TYPE], ' - 'env[OS_ENDPOINT_TYPE] or ') + - DEFAULT_NOVA_ENDPOINT_TYPE + '.') - - parser.add_argument( - '--endpoint-type', - help=argparse.SUPPRESS) + default=utils.env('NOVA_ENDPOINT_TYPE', + default=DEFAULT_NOVA_ENDPOINT_TYPE), + help=_('Defaults to env[NOVA_ENDPOINT_TYPE] or ') + + DEFAULT_NOVA_ENDPOINT_TYPE + '.') # NOTE(dtroyer): We can't add --endpoint_type here due to argparse # thinking usage-list --end is ambiguous; but it # works fine with only --endpoint-type present @@ -410,32 +364,42 @@ class OpenStackComputeShell(object): # parser.add_argument('--endpoint_type', # help=argparse.SUPPRESS) - parser.add_argument( - '--os-compute-api-version', + parser.add_argument('--os-compute-api-version', metavar='', - default=cliutils.env('OS_COMPUTE_API_VERSION', - default=DEFAULT_OS_COMPUTE_API_VERSION), + default=utils.env('OS_COMPUTE_API_VERSION', + default=DEFAULT_OS_COMPUTE_API_VERSION), help=_('Accepts 1.1 or 3, ' - 'defaults to env[OS_COMPUTE_API_VERSION].')) - parser.add_argument( - '--os_compute_api_version', + 'defaults to env[OS_COMPUTE_API_VERSION].')) + parser.add_argument('--os_compute_api_version', help=argparse.SUPPRESS) - parser.add_argument( - '--bypass-url', + parser.add_argument('--os-cacert', + metavar='', + default=utils.env('OS_CACERT', default=None), + help='Specify a CA bundle file to use in ' + 'verifying a TLS (https) server certificate. ' + 'Defaults to env[OS_CACERT]') + + parser.add_argument('--insecure', + default=utils.env('NOVACLIENT_INSECURE', default=False), + action='store_true', + help=_("Explicitly allow novaclient to perform \"insecure\" " + "SSL (https) requests. The server's certificate will " + "not be verified against any certificate authorities. " + "This option should be used with caution.")) + + parser.add_argument('--bypass-url', metavar='', dest='bypass_url', - default=cliutils.env('NOVACLIENT_BYPASS_URL'), + default=utils.env('NOVACLIENT_BYPASS_URL'), help="Use this API endpoint instead of the Service Catalog. " "Defaults to env[NOVACLIENT_BYPASS_URL]") parser.add_argument('--bypass_url', - help=argparse.SUPPRESS) + help=argparse.SUPPRESS) # The auth-system-plugins might require some extra options novaclient.auth_plugin.load_auth_system_opts(parser) - self._append_global_identity_args(parser) - return parser def get_subcommand_parser(self, version): @@ -446,12 +410,12 @@ class OpenStackComputeShell(object): try: actions_module = { - '1.1': shell_v2, - '2': shell_v2, - '3': shell_v2, + '1.1': shell_v1_1, + '2': shell_v1_1, + '3': shell_v3, }[version] except KeyError: - actions_module = shell_v2 + actions_module = shell_v1_1 self._find_actions(subparsers, actions_module) self._find_actions(subparsers, self) @@ -491,10 +455,6 @@ class OpenStackComputeShell(object): def _discover_via_contrib_path(self, version): module_path = os.path.dirname(os.path.abspath(__file__)) version_str = "v%s" % version.replace('.', '_') - # NOTE(akurilin): v1.1, v2 and v3 have one implementation, so - # we should discover contrib modules in one place. - if version_str in ["v1_1", "v3"]: - version_str = "v2" ext_path = os.path.join(module_path, version_str, 'contrib') ext_glob = os.path.join(ext_path, "*.py") @@ -515,8 +475,7 @@ class OpenStackComputeShell(object): yield name, module def _add_bash_completion_subparser(self, subparsers): - subparser = subparsers.add_parser( - 'bash_completion', + subparser = subparsers.add_parser('bash_completion', add_help=False, formatter_class=OpenStackHelpFormatter ) @@ -532,14 +491,13 @@ class OpenStackComputeShell(object): action_help = desc.strip() arguments = getattr(callback, 'arguments', []) - subparser = subparsers.add_parser( - command, + subparser = subparsers.add_parser(command, help=action_help, description=desc, add_help=False, - formatter_class=OpenStackHelpFormatter) - subparser.add_argument( - '-h', '--help', + formatter_class=OpenStackHelpFormatter + ) + subparser.add_argument('-h', '--help', action='help', help=argparse.SUPPRESS, ) @@ -558,20 +516,6 @@ class OpenStackComputeShell(object): logging.basicConfig(level=logging.DEBUG, format=streamformat) - def _get_keystone_auth(self, session, auth_url, **kwargs): - auth_token = kwargs.pop('auth_token', None) - if auth_token: - return token.Token(auth_url, auth_token, **kwargs) - else: - return password.Password( - auth_url, - username=kwargs.pop('username'), - user_id=kwargs.pop('user_id'), - password=kwargs.pop('password'), - user_domain_id=kwargs.pop('user_domain_id'), - user_domain_name=kwargs.pop('user_domain_name'), - **kwargs) - def main(self, argv): # Parse args once to find version and debug settings parser = self.get_base_parser() @@ -583,7 +527,7 @@ class OpenStackComputeShell(object): # build available subcommands based on version self.extensions = self._discover_extensions( - options.os_compute_api_version) + options.os_compute_api_version) self._run_extension_hooks('__pre_parse_args__') # NOTE(dtroyer): Hackery to handle --endpoint_type due to argparse @@ -595,7 +539,7 @@ class OpenStackComputeShell(object): argv[spot] = '--endpoint-type' subcommand_parser = self.get_subcommand_parser( - options.os_compute_api_version) + options.os_compute_api_version) self.parser = subcommand_parser if options.help or not argv: @@ -631,9 +575,6 @@ class OpenStackComputeShell(object): cacert = args.os_cacert timeout = args.timeout - keystone_session = None - keystone_auth = None - # We may have either, both or none of these. # If we have both, we don't need USERNAME, PASSWORD etc. # Fill in the blanks from the SecretsHelper if possible. @@ -651,11 +592,6 @@ class OpenStackComputeShell(object): if not endpoint_type: endpoint_type = DEFAULT_NOVA_ENDPOINT_TYPE - # This allow users to use endpoint_type as (internal, public or admin) - # just like other openstack clients (glance, cinder etc) - if endpoint_type in ['internal', 'public', 'admin']: - endpoint_type += 'URL' - if not service_type: os_compute_api_version = (options.os_compute_api_version or DEFAULT_OS_COMPUTE_API_VERSION) @@ -665,20 +601,13 @@ class OpenStackComputeShell(object): except KeyError: service_type = DEFAULT_NOVA_SERVICE_TYPE_MAP[ DEFAULT_OS_COMPUTE_API_VERSION] - service_type = cliutils.get_service_type(args.func) or service_type + service_type = utils.get_service_type(args.func) or service_type # If we have an auth token but no management_url, we must auth anyway. # Expired tokens are handled by client.py:_cs_request must_auth = not (cliutils.isunauthenticated(args.func) or (auth_token and management_url)) - # Do not use Keystone session for cases with no session support. The - # presence of auth_plugin means os_auth_system is present and is not - # keystone. - use_session = True - if auth_plugin or bypass_url or os_cache or volume_service_name: - use_session = False - # FIXME(usrleon): Here should be restrict for project id same as # for os_username or os_password but for compatibility it is not. if must_auth: @@ -687,86 +616,54 @@ class OpenStackComputeShell(object): if not auth_plugin or not auth_plugin.opts: if not os_username and not os_user_id: - raise exc.CommandError( - _("You must provide a username " - "or user id via --os-username, --os-user-id, " - "env[OS_USERNAME] or env[OS_USER_ID]")) + raise exc.CommandError(_("You must provide a username " + "or user id via --os-username, --os-user-id, " + "env[OS_USERNAME] or env[OS_USER_ID]")) - if not any([args.os_tenant_name, args.os_tenant_id, - args.os_project_id, args.os_project_name]): - raise exc.CommandError(_("You must provide a project name or" - " project id via --os-project-name," - " --os-project-id, env[OS_PROJECT_ID]" - " or env[OS_PROJECT_NAME]. You may" - " use os-project and os-tenant" - " interchangeably.")) + if not os_tenant_name and not os_tenant_id: + raise exc.CommandError(_("You must provide a tenant name " + "or tenant id via --os-tenant-name, " + "--os-tenant-id, env[OS_TENANT_NAME] " + "or env[OS_TENANT_ID]")) if not os_auth_url: if os_auth_system and os_auth_system != 'keystone': os_auth_url = auth_plugin.get_auth_url() if not os_auth_url: - raise exc.CommandError( - _("You must provide an auth url " - "via either --os-auth-url or env[OS_AUTH_URL] " - "or specify an auth_system which defines a " - "default url with --os-auth-system " - "or env[OS_AUTH_SYSTEM]")) - - project_id = args.os_project_id or args.os_tenant_id - project_name = args.os_project_name or args.os_tenant_name - if use_session: - # Not using Nova auth plugin, so use keystone - start_time = time.time() - keystone_session = ksession.Session.load_from_cli_options(args) - keystone_auth = self._get_keystone_auth( - keystone_session, - args.os_auth_url, - username=args.os_username, - user_id=args.os_user_id, - user_domain_id=args.os_user_domain_id, - user_domain_name=args.os_user_domain_name, - password=args.os_password, - auth_token=args.os_auth_token, - project_id=project_id, - project_name=project_name, - project_domain_id=args.os_project_domain_id, - project_domain_name=args.os_project_domain_name) - end_time = time.time() - self.times.append( - ('%s %s' % ('auth_url', args.os_auth_url), - start_time, end_time)) + raise exc.CommandError(_("You must provide an auth url " + "via either --os-auth-url or env[OS_AUTH_URL] " + "or specify an auth_system which defines a " + "default url with --os-auth-system " + "or env[OS_AUTH_SYSTEM]")) if (options.os_compute_api_version and options.os_compute_api_version != '1.0'): - if not any([args.os_tenant_id, args.os_tenant_name, - args.os_project_id, args.os_project_name]): - raise exc.CommandError(_("You must provide a project name or" - " project id via --os-project-name," - " --os-project-id, env[OS_PROJECT_ID]" - " or env[OS_PROJECT_NAME]. You may" - " use os-project and os-tenant" - " interchangeably.")) + if not os_tenant_name and not os_tenant_id: + raise exc.CommandError(_("You must provide a tenant name " + "or tenant id via --os-tenant-name, " + "--os-tenant-id, env[OS_TENANT_NAME] " + "or env[OS_TENANT_ID]")) if not os_auth_url: - raise exc.CommandError( - _("You must provide an auth url " - "via either --os-auth-url or env[OS_AUTH_URL]")) + raise exc.CommandError(_("You must provide an auth url " + "via either --os-auth-url or env[OS_AUTH_URL]")) - self.cs = client.Client( - options.os_compute_api_version, - os_username, os_password, os_tenant_name, - tenant_id=os_tenant_id, user_id=os_user_id, - auth_url=os_auth_url, insecure=insecure, - region_name=os_region_name, endpoint_type=endpoint_type, - extensions=self.extensions, service_type=service_type, - service_name=service_name, auth_system=os_auth_system, - auth_plugin=auth_plugin, auth_token=auth_token, - volume_service_name=volume_service_name, - timings=args.timings, bypass_url=bypass_url, - os_cache=os_cache, http_log_debug=options.debug, - cacert=cacert, timeout=timeout, - session=keystone_session, auth=keystone_auth) + completion_cache = client.CompletionCache(os_username, os_auth_url) + + self.cs = client.Client(options.os_compute_api_version, + os_username, os_password, os_tenant_name, + tenant_id=os_tenant_id, user_id=os_user_id, + auth_url=os_auth_url, insecure=insecure, + region_name=os_region_name, endpoint_type=endpoint_type, + extensions=self.extensions, service_type=service_type, + service_name=service_name, auth_system=os_auth_system, + auth_plugin=auth_plugin, auth_token=auth_token, + volume_service_name=volume_service_name, + timings=args.timings, bypass_url=bypass_url, + os_cache=os_cache, http_log_debug=options.debug, + cacert=cacert, timeout=timeout, + completion_cache=completion_cache) # Now check for the password/token of which pieces of the # identifying keyring key can come from the underlying client @@ -799,11 +696,7 @@ class OpenStackComputeShell(object): # This does a couple of bits which are useful even if we've # got the token + service URL already. It exits fast in that case. if not cliutils.isunauthenticated(args.func): - if not use_session: - # Only call authenticate() if Nova auth plugin is used. - # If keystone is used, authentication is handled as part - # of session. - self.cs.authenticate() + self.cs.authenticate() except exc.Unauthorized: raise exc.CommandError(_("Invalid OpenStack Nova credentials.")) except exc.AuthorizationFailure: @@ -832,13 +725,12 @@ class OpenStackComputeShell(object): volume_service_name=volume_service_name, timings=args.timings, bypass_url=bypass_url, os_cache=os_cache, http_log_debug=options.debug, - session=keystone_session, auth=keystone_auth, cacert=cacert, timeout=timeout) args.func(self.cs, args) if args.timings: - self._dump_timings(self.times + self.cs.get_timings()) + self._dump_timings(self.cs.get_timings()) def _dump_timings(self, timings): class Tyme(object): @@ -873,11 +765,8 @@ class OpenStackComputeShell(object): commands.remove('bash_completion') print(' '.join(commands | options)) - @cliutils.arg( - 'command', - metavar='', - nargs='?', - help='Display help for ') + @utils.arg('command', metavar='', nargs='?', + help='Display help for ') def do_help(self, args): """ Display help about this program or one of its subcommands. @@ -897,7 +786,7 @@ class OpenStackHelpFormatter(argparse.HelpFormatter): def __init__(self, prog, indent_increment=2, max_help_position=32, width=None): super(OpenStackHelpFormatter, self).__init__(prog, indent_increment, - max_help_position, width) + max_help_position, width) def start_section(self, heading): # Title-case the headings @@ -917,9 +806,9 @@ def main(): print("ERROR (%(name)s): %(msg)s" % details, file=sys.stderr) sys.exit(1) - except KeyboardInterrupt: - print("... terminating nova client", file=sys.stderr) - sys.exit(130) + except KeyboardInterrupt as e: + print("Shutting down novaclient", file=sys.stderr) + sys.exit(1) if __name__ == "__main__": diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/agents.py b/awx/lib/site-packages/novaclient/tests/fixture_data/agents.py index a21f473dec..f1c2aed016 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/agents.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/agents.py @@ -10,9 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httpretty - -from novaclient.openstack.common import jsonutils from novaclient.tests.fixture_data import base @@ -35,9 +32,9 @@ class Fixture(base.Fixture): } } - httpretty.register_uri(httpretty.POST, self.url(), - body=jsonutils.dumps(post_os_agents), - content_type='application/json') + self.requests.register_uri('POST', self.url(), + json=post_os_agents, + headers=self.json_headers) put_os_agents_1 = { "agent": { @@ -48,10 +45,10 @@ class Fixture(base.Fixture): } } - httpretty.register_uri(httpretty.PUT, self.url(1), - body=jsonutils.dumps(put_os_agents_1), - content_type='application/json') + self.requests.register_uri('PUT', self.url(1), + json=put_os_agents_1, + headers=self.json_headers) - httpretty.register_uri(httpretty.DELETE, self.url(1), - content_type='application/json', - status=202) + self.requests.register_uri('DELETE', self.url(1), + headers=self.json_headers, + status_code=202) diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/aggregates.py b/awx/lib/site-packages/novaclient/tests/fixture_data/aggregates.py index 33e30905fa..26f00d7acf 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/aggregates.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/aggregates.py @@ -10,9 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httpretty - -from novaclient.openstack.common import jsonutils from novaclient.tests.fixture_data import base @@ -32,21 +29,24 @@ class Fixture(base.Fixture): 'availability_zone': 'nova1'}, ]} - httpretty.register_uri(httpretty.GET, self.url(), - body=jsonutils.dumps(get_os_aggregates), - content_type='application/json') + self.requests.register_uri('GET', self.url(), + json=get_os_aggregates, + headers=self.json_headers) - r = jsonutils.dumps({'aggregate': get_os_aggregates['aggregates'][0]}) + get_aggregates_1 = {'aggregate': get_os_aggregates['aggregates'][0]} - httpretty.register_uri(httpretty.POST, self.url(), body=r, - content_type='application/json') + self.requests.register_uri('POST', self.url(), + json=get_aggregates_1, + headers=self.json_headers) for agg_id in (1, 2): - for method in (httpretty.GET, httpretty.PUT): - httpretty.register_uri(method, self.url(agg_id), body=r, - content_type='application/json') + for method in ('GET', 'PUT'): + self.requests.register_uri(method, self.url(agg_id), + json=get_aggregates_1, + headers=self.json_headers) - httpretty.register_uri(httpretty.POST, self.url(agg_id, 'action'), - body=r, content_type='application/json') + self.requests.register_uri('POST', self.url(agg_id, 'action'), + json=get_aggregates_1, + headers=self.json_headers) - httpretty.register_uri(httpretty.DELETE, self.url(1), status=202) + self.requests.register_uri('DELETE', self.url(1), status_code=202) diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/availability_zones.py b/awx/lib/site-packages/novaclient/tests/fixture_data/availability_zones.py index c23788f51a..451a64b2ac 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/availability_zones.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/availability_zones.py @@ -10,9 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httpretty - -from novaclient.openstack.common import jsonutils from novaclient.tests.fixture_data import base @@ -41,9 +38,10 @@ class V1(base.Fixture): } ] } - httpretty.register_uri(httpretty.GET, self.url(), - body=jsonutils.dumps(get_os_availability_zone), - content_type='application/json') + + self.requests.register_uri('GET', self.url(), + json=get_os_availability_zone, + headers=self.json_headers) get_os_zone_detail = { self.zone_info_key: [ @@ -88,9 +86,9 @@ class V1(base.Fixture): ] } - httpretty.register_uri(httpretty.GET, self.url('detail'), - body=jsonutils.dumps(get_os_zone_detail), - content_type='application/json') + self.requests.register_uri('GET', self.url('detail'), + json=get_os_zone_detail, + headers=self.json_headers) class V3(V1): diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/base.py b/awx/lib/site-packages/novaclient/tests/fixture_data/base.py index 72f46d1dd3..6a2e238b58 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/base.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/base.py @@ -19,9 +19,11 @@ COMPUTE_URL = 'http://compute.host' class Fixture(fixtures.Fixture): base_url = None + json_headers = {'Content-Type': 'application/json'} - def __init__(self, compute_url=COMPUTE_URL): + def __init__(self, requests, compute_url=COMPUTE_URL): super(Fixture, self).__init__() + self.requests = requests self.compute_url = compute_url def url(self, *args, **kwargs): diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/certs.py b/awx/lib/site-packages/novaclient/tests/fixture_data/certs.py index 3ce0f0f072..40421ca1bc 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/certs.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/certs.py @@ -10,9 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httpretty - -from novaclient.openstack.common import jsonutils from novaclient.tests.fixture_data import base @@ -43,9 +40,9 @@ class Fixture(base.Fixture): 'data': 'foo' } } - httpretty.register_uri(httpretty.GET, self.url('root'), - body=jsonutils.dumps(get_os_certificate), - content_type='application/json') + self.requests.register_uri('GET', self.url('root'), + json=get_os_certificate, + headers=self.json_headers) post_os_certificates = { 'certificate': { @@ -53,6 +50,6 @@ class Fixture(base.Fixture): 'data': 'bar' } } - httpretty.register_uri(httpretty.POST, self.url(), - body=jsonutils.dumps(post_os_certificates), - content_type='application/json') + self.requests.register_uri('POST', self.url(), + json=post_os_certificates, + headers=self.json_headers) diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/client.py b/awx/lib/site-packages/novaclient/tests/fixture_data/client.py index c4ca3ec38b..e52d4a2335 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/client.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/client.py @@ -11,11 +11,10 @@ # under the License. import fixtures -import httpretty from keystoneclient.auth.identity import v2 +from keystoneclient import fixture from keystoneclient import session -from novaclient.openstack.common import jsonutils from novaclient.v1_1 import client as v1_1client from novaclient.v3 import client as v3client @@ -25,74 +24,31 @@ COMPUTE_URL = 'http://compute.host' class V1(fixtures.Fixture): - def __init__(self, compute_url=COMPUTE_URL, identity_url=IDENTITY_URL): + def __init__(self, requests, + compute_url=COMPUTE_URL, identity_url=IDENTITY_URL): super(V1, self).__init__() self.identity_url = identity_url self.compute_url = compute_url self.client = None + self.requests = requests - self.token = { - 'access': { - "token": { - "id": "ab48a9efdfedb23ty3494", - "expires": "2010-11-01T03:32:15-05:00", - "tenant": { - "id": "345", - "name": "My Project" - } - }, - "user": { - "id": "123", - "name": "jqsmith", - "roles": [ - { - "id": "234", - "name": "compute:admin", - }, - { - "id": "235", - "name": "object-store:admin", - "tenantId": "1", - } - ], - "roles_links": [], - }, - "serviceCatalog": [ - { - "name": "Cloud Servers", - "type": "compute", - "endpoints": [ - { - "publicURL": self.compute_url, - "internalURL": "https://compute1.host/v1/1", - }, - ], - "endpoints_links": [], - }, - { - "name": "Cloud Servers", - "type": "computev3", - "endpoints": [ - { - "publicURL": self.compute_url, - "internalURL": "https://compute1.host/v1/1", - }, - ], - "endpoints_links": [], - }, - ], - } - } + self.token = fixture.V2Token() + self.token.set_scope() + + s = self.token.add_service('compute') + s.add_endpoint(self.compute_url) + + s = self.token.add_service('computev3') + s.add_endpoint(self.compute_url) def setUp(self): super(V1, self).setUp() - httpretty.enable() - self.addCleanup(httpretty.disable) auth_url = '%s/tokens' % self.identity_url - httpretty.register_uri(httpretty.POST, auth_url, - body=jsonutils.dumps(self.token), - content_type='application/json') + headers = {'X-Content-Type': 'application/json'} + self.requests.register_uri('POST', auth_url, + json=self.token, + headers=headers) self.client = self.new_client() def new_client(self): diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/cloudpipe.py b/awx/lib/site-packages/novaclient/tests/fixture_data/cloudpipe.py index fffd2a1fe8..32b5c210ca 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/cloudpipe.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/cloudpipe.py @@ -10,9 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httpretty - -from novaclient.openstack.common import jsonutils from novaclient.tests.fixture_data import base @@ -24,17 +21,17 @@ class Fixture(base.Fixture): super(Fixture, self).setUp() get_os_cloudpipe = {'cloudpipes': [{'project_id': 1}]} - httpretty.register_uri(httpretty.GET, self.url(), - body=jsonutils.dumps(get_os_cloudpipe), - content_type='application/json') + self.requests.register_uri('GET', self.url(), + json=get_os_cloudpipe, + headers=self.json_headers) instance_id = '9d5824aa-20e6-4b9f-b967-76a699fc51fd' post_os_cloudpipe = {'instance_id': instance_id} - httpretty.register_uri(httpretty.POST, self.url(), - body=jsonutils.dumps(post_os_cloudpipe), - content_type='application/json', - status=202) + self.requests.register_uri('POST', self.url(), + json=post_os_cloudpipe, + headers=self.json_headers, + status_code=202) - httpretty.register_uri(httpretty.PUT, self.url('configure-project'), - content_type='application/json', - status=202) + self.requests.register_uri('PUT', self.url('configure-project'), + headers=self.json_headers, + status_code=202) diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/fixedips.py b/awx/lib/site-packages/novaclient/tests/fixture_data/fixedips.py index 64a36d2132..71f18a4e65 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/fixedips.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/fixedips.py @@ -10,9 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httpretty - -from novaclient.openstack.common import jsonutils from novaclient.tests.fixture_data import base @@ -31,11 +28,12 @@ class Fixture(base.Fixture): 'host': 'bar' } } - httpretty.register_uri(httpretty.GET, self.url('192.168.1.1'), - body=jsonutils.dumps(get_os_fixed_ips), - content_type='application/json') - httpretty.register_uri(httpretty.POST, - self.url('192.168.1.1', 'action'), - content_type='application/json', - status=202) + self.requests.register_uri('GET', self.url('192.168.1.1'), + json=get_os_fixed_ips, + headers=self.json_headers) + + self.requests.register_uri('POST', + self.url('192.168.1.1', 'action'), + headers=self.json_headers, + status_code=202) diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/floatingips.py b/awx/lib/site-packages/novaclient/tests/fixture_data/floatingips.py index 8dd590427a..5fc6848a74 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/floatingips.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/floatingips.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httpretty - from novaclient.openstack.common import jsonutils from novaclient.tests import fakes from novaclient.tests.fixture_data import base @@ -28,29 +26,28 @@ class FloatingFixture(base.Fixture): {'id': 2, 'fixed_ip': '10.0.0.2', 'ip': '11.0.0.2'}] get_os_floating_ips = {'floating_ips': floating_ips} - httpretty.register_uri(httpretty.GET, self.url(), - body=jsonutils.dumps(get_os_floating_ips), - content_type='application/json') + self.requests.register_uri('GET', self.url(), + json=get_os_floating_ips, + headers=self.json_headers) for ip in floating_ips: get_os_floating_ip = {'floating_ip': ip} - httpretty.register_uri(httpretty.GET, self.url(ip['id']), - body=jsonutils.dumps(get_os_floating_ip), - content_type='application/json') + self.requests.register_uri('GET', self.url(ip['id']), + json=get_os_floating_ip, + headers=self.json_headers) - httpretty.register_uri(httpretty.DELETE, self.url(ip['id']), - content_type='application/json', - status=204) + self.requests.register_uri('DELETE', self.url(ip['id']), + headers=self.json_headers, + status_code=204) - def post_os_floating_ips(request, url, headers): - body = jsonutils.loads(request.body.decode('utf-8')) + def post_os_floating_ips(request, context): + body = jsonutils.loads(request.body) ip = floating_ips[0].copy() ip['pool'] = body.get('pool') - ip = jsonutils.dumps({'floating_ip': ip}) - return 200, headers, ip - httpretty.register_uri(httpretty.POST, self.url(), - body=post_os_floating_ips, - content_type='application/json') + return {'floating_ip': ip} + self.requests.register_uri('POST', self.url(), + json=post_os_floating_ips, + headers=self.json_headers) class DNSFixture(base.Fixture): @@ -66,10 +63,10 @@ class DNSFixture(base.Fixture): {'domain': 'example.com'} ] } - httpretty.register_uri(httpretty.GET, self.url(), - body=jsonutils.dumps(get_os_floating_ip_dns), - content_type='application/json', - status=205) + self.requests.register_uri('GET', self.url(), + json=get_os_floating_ip_dns, + headers=self.json_headers, + status_code=205) get_dns_testdomain_entries_testname = { 'dns_entry': { @@ -80,31 +77,30 @@ class DNSFixture(base.Fixture): } } url = self.url('testdomain', 'entries', 'testname') - body = jsonutils.dumps(get_dns_testdomain_entries_testname) - httpretty.register_uri(httpretty.GET, url, - body=body, - content_type='application/json', - status=205) + self.requests.register_uri('GET', url, + json=get_dns_testdomain_entries_testname, + headers=self.json_headers, + status_code=205) - httpretty.register_uri(httpretty.DELETE, self.url('testdomain'), - status=200) + self.requests.register_uri('DELETE', self.url('testdomain')) url = self.url('testdomain', 'entries', 'testname') - httpretty.register_uri(httpretty.DELETE, url, status=200) + self.requests.register_uri('DELETE', url) - def put_dns_testdomain_entries_testname(request, url, headers): - body = jsonutils.loads(request.body.decode('utf-8')) + def put_dns_testdomain_entries_testname(request, context): + body = jsonutils.loads(request.body) fakes.assert_has_keys(body['dns_entry'], required=['ip', 'dns_type']) - return 205, headers, request.body - httpretty.register_uri(httpretty.PUT, url, - body=put_dns_testdomain_entries_testname, - content_type='application/json') + context.status_code = 205 + return request.body + self.requests.register_uri('PUT', url, + text=put_dns_testdomain_entries_testname, + headers=self.json_headers) url = self.url('testdomain', 'entries') - httpretty.register_uri(httpretty.GET, url, status=404) + self.requests.register_uri('GET', url, status_code=404) - get_os_floating_ip_dns_testdomain_entries = { + get_os_floating_ip_dns_testdomain = { 'dns_entries': [ { 'dns_entry': { @@ -124,14 +120,13 @@ class DNSFixture(base.Fixture): }, ] } - body = jsonutils.dumps(get_os_floating_ip_dns_testdomain_entries) - httpretty.register_uri(httpretty.GET, url + '?ip=1.2.3.4', - body=body, - status=205, - content_type='application/json') + self.requests.register_uri('GET', url + '?ip=1.2.3.4', + json=get_os_floating_ip_dns_testdomain, + status_code=205, + headers=self.json_headers) - def put_os_floating_ip_dns_testdomain(request, url, headers): - body = jsonutils.loads(request.body.decode('utf-8')) + def put_os_floating_ip_dns_testdomain(request, context): + body = jsonutils.loads(request.body) if body['domain_entry']['scope'] == 'private': fakes.assert_has_keys(body['domain_entry'], required=['availability_zone', 'scope']) @@ -142,11 +137,12 @@ class DNSFixture(base.Fixture): fakes.assert_has_keys(body['domain_entry'], required=['project', 'scope']) - headers['Content-Type'] = 'application/json' - return (205, headers, request.body) + return request.body - httpretty.register_uri(httpretty.PUT, self.url('testdomain'), - body=put_os_floating_ip_dns_testdomain) + self.requests.register_uri('PUT', self.url('testdomain'), + text=put_os_floating_ip_dns_testdomain, + status_code=205, + headers=self.json_headers) class BulkFixture(base.Fixture): @@ -162,40 +158,38 @@ class BulkFixture(base.Fixture): {'id': 2, 'fixed_ip': '10.0.0.2', 'ip': '11.0.0.2'}, ] } - httpretty.register_uri(httpretty.GET, self.url(), - body=jsonutils.dumps(get_os_floating_ips_bulk), - content_type='application/json') - httpretty.register_uri(httpretty.GET, self.url('testHost'), - body=jsonutils.dumps(get_os_floating_ips_bulk), - content_type='application/json') + self.requests.register_uri('GET', self.url(), + json=get_os_floating_ips_bulk, + headers=self.json_headers) + self.requests.register_uri('GET', self.url('testHost'), + json=get_os_floating_ips_bulk, + headers=self.json_headers) - def put_os_floating_ips_bulk_delete(request, url, headers): - body = jsonutils.loads(request.body.decode('utf-8')) + def put_os_floating_ips_bulk_delete(request, context): + body = jsonutils.loads(request.body) ip_range = body.get('ip_range') - data = {'floating_ips_bulk_delete': ip_range} - return 200, headers, jsonutils.dumps(data) + return {'floating_ips_bulk_delete': ip_range} - httpretty.register_uri(httpretty.PUT, self.url('delete'), - body=put_os_floating_ips_bulk_delete, - content_type='application/json') + self.requests.register_uri('PUT', self.url('delete'), + json=put_os_floating_ips_bulk_delete, + headers=self.json_headers) - def post_os_floating_ips_bulk(request, url, headers): - body = jsonutils.loads(request.body.decode('utf-8')) + def post_os_floating_ips_bulk(request, context): + body = jsonutils.loads(request.body) params = body.get('floating_ips_bulk_create') pool = params.get('pool', 'defaultPool') interface = params.get('interface', 'defaultInterface') - data = { + return { 'floating_ips_bulk_create': { 'ip_range': '192.168.1.0/30', 'pool': pool, 'interface': interface } } - return 200, headers, jsonutils.dumps(data) - httpretty.register_uri(httpretty.POST, self.url(), - body=post_os_floating_ips_bulk, - content_type='application/json') + self.requests.register_uri('POST', self.url(), + json=post_os_floating_ips_bulk, + headers=self.json_headers) class PoolsFixture(base.Fixture): @@ -211,6 +205,6 @@ class PoolsFixture(base.Fixture): {'name': 'bar'} ] } - httpretty.register_uri(httpretty.GET, self.url(), - body=jsonutils.dumps(get_os_floating_ip_pools), - content_type='application/json') + self.requests.register_uri('GET', self.url(), + json=get_os_floating_ip_pools, + headers=self.json_headers) diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/fping.py b/awx/lib/site-packages/novaclient/tests/fixture_data/fping.py index 9542f83d4c..837db6daa0 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/fping.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/fping.py @@ -10,9 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httpretty - -from novaclient.openstack.common import jsonutils from novaclient.tests.fixture_data import base @@ -30,9 +27,9 @@ class Fixture(base.Fixture): "alive": True, } } - httpretty.register_uri(httpretty.GET, self.url(1), - body=jsonutils.dumps(get_os_fping_1), - content_type='application/json') + self.requests.register_uri('GET', self.url(1), + json=get_os_fping_1, + headers=self.json_headers) get_os_fping = { 'servers': [ @@ -44,6 +41,6 @@ class Fixture(base.Fixture): }, ] } - httpretty.register_uri(httpretty.GET, self.url(), - body=jsonutils.dumps(get_os_fping), - content_type='application/json') + self.requests.register_uri('GET', self.url(), + json=get_os_fping, + headers=self.json_headers) diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/hosts.py b/awx/lib/site-packages/novaclient/tests/fixture_data/hosts.py index 193a5bb674..babfbec7c7 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/hosts.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/hosts.py @@ -10,7 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httpretty from six.moves.urllib import parse from novaclient.openstack.common import jsonutils @@ -36,13 +35,17 @@ class BaseFixture(base.Fixture): 'cpu': 1, 'memory_mb': 2048, 'disk_gb': 30}} ] } - httpretty.register_uri(httpretty.GET, self.url('host'), - body=jsonutils.dumps(get_os_hosts_host), - content_type='application/json') - def get_os_hosts(request, url, headers): - host, query = parse.splitquery(url) + headers = {'Content-Type': 'application/json'} + + self.requests.register_uri('GET', self.url('host'), + json=get_os_hosts_host, + headers=headers) + + def get_os_hosts(request, context): + host, query = parse.splitquery(request.url) zone = 'nova1' + service = None if query: qs = parse.parse_qs(query) @@ -51,70 +54,71 @@ class BaseFixture(base.Fixture): except Exception: pass - data = { + try: + service = qs['service'][0] + except Exception: + pass + + return { 'hosts': [ { 'host': 'host1', - 'service': 'nova-compute', + 'service': service or 'nova-compute', 'zone': zone }, { 'host': 'host1', - 'service': 'nova-cert', + 'service': service or 'nova-cert', 'zone': zone } ] } - return 200, headers, jsonutils.dumps(data) - httpretty.register_uri(httpretty.GET, self.url(), - body=get_os_hosts, - content_type='application/json') + self.requests.register_uri('GET', self.url(), + json=get_os_hosts, + headers=headers) get_os_hosts_sample_host = { 'host': [ {'resource': {'host': 'sample_host'}} ], } - httpretty.register_uri(httpretty.GET, self.url('sample_host'), - body=jsonutils.dumps(get_os_hosts_sample_host), - content_type='application/json') + self.requests.register_uri('GET', self.url('sample_host'), + json=get_os_hosts_sample_host, + headers=headers) - httpretty.register_uri(httpretty.PUT, self.url('sample_host', 1), - body=jsonutils.dumps(self.put_host_1()), - content_type='application/json') + self.requests.register_uri('PUT', self.url('sample_host', 1), + json=self.put_host_1(), + headers=headers) - httpretty.register_uri(httpretty.PUT, self.url('sample_host', 2), - body=jsonutils.dumps(self.put_host_2()), - content_type='application/json') + self.requests.register_uri('PUT', self.url('sample_host', 2), + json=self.put_host_2(), + headers=headers) - httpretty.register_uri(httpretty.PUT, self.url('sample_host', 3), - body=jsonutils.dumps(self.put_host_3()), - content_type='application/json') + self.requests.register_uri('PUT', self.url('sample_host', 3), + json=self.put_host_3(), + headers=headers) - url = self.url('sample_host', 'reboot') - httpretty.register_uri(httpretty.GET, url, - body=jsonutils.dumps(self.get_host_reboot()), - content_type='application/json') + self.requests.register_uri('GET', self.url('sample_host', 'reboot'), + json=self.get_host_reboot(), + headers=headers) - url = self.url('sample_host', 'startup') - httpretty.register_uri(httpretty.GET, url, - body=jsonutils.dumps(self.get_host_startup()), - content_type='application/json') + self.requests.register_uri('GET', self.url('sample_host', 'startup'), + json=self.get_host_startup(), + headers=headers) - url = self.url('sample_host', 'shutdown') - httpretty.register_uri(httpretty.GET, url, - body=jsonutils.dumps(self.get_host_shutdown()), - content_type='application/json') + self.requests.register_uri('GET', self.url('sample_host', 'shutdown'), + json=self.get_host_shutdown(), + headers=headers) - def put_os_hosts_sample_host(request, url, headers): + def put_os_hosts_sample_host(request, context): result = {'host': 'dummy'} - result.update(jsonutils.loads(request.body.decode('utf-8'))) - return 200, headers, jsonutils.dumps(result) + result.update(jsonutils.loads(request.body)) + return result - httpretty.register_uri(httpretty.PUT, self.url('sample_host'), - body=put_os_hosts_sample_host, - content_type='application/json') + self.requests.register_uri('PUT', self.url('sample_host'), + json=put_os_hosts_sample_host, + headers=headers) class V1(BaseFixture): diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/hypervisors.py b/awx/lib/site-packages/novaclient/tests/fixture_data/hypervisors.py index 8133de4421..c3f6f7210d 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/hypervisors.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/hypervisors.py @@ -10,9 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httpretty - -from novaclient.openstack.common import jsonutils from novaclient.tests.fixture_data import base @@ -30,15 +27,20 @@ class V1(base.Fixture): ] } - httpretty.register_uri(httpretty.GET, self.url(), - body=jsonutils.dumps(get_os_hypervisors), - content_type='application/json') + self.headers = {'Content-Type': 'application/json'} + + self.requests.register_uri('GET', self.url(), + json=get_os_hypervisors, + headers=self.headers) get_os_hypervisors_detail = { 'hypervisors': [ { 'id': 1234, - 'service': {'id': 1, 'host': 'compute1'}, + 'service': { + 'id': 1, + 'host': 'compute1', + }, 'vcpus': 4, 'memory_mb': 10 * 1024, 'local_gb': 250, @@ -57,29 +59,32 @@ class V1(base.Fixture): }, { 'id': 2, - 'service': {'id': 2, 'host': 'compute2'}, - 'vcpus': 4, - 'memory_mb': 10 * 1024, - 'local_gb': 250, - 'vcpus_used': 2, - 'memory_mb_used': 5 * 1024, - 'local_gb_used': 125, - 'hypervisor_type': 'xen', - 'hypervisor_version': 3, - 'hypervisor_hostname': 'hyper2', - 'free_ram_mb': 5 * 1024, - 'free_disk_gb': 125, - 'current_workload': 2, - 'running_vms': 2, - 'cpu_info': 'cpu_info', - 'disk_available_least': 100 + 'service': { + 'id': 2, + 'host': 'compute2', + }, + 'vcpus': 4, + 'memory_mb': 10 * 1024, + 'local_gb': 250, + 'vcpus_used': 2, + 'memory_mb_used': 5 * 1024, + 'local_gb_used': 125, + 'hypervisor_type': 'xen', + 'hypervisor_version': 3, + 'hypervisor_hostname': 'hyper2', + 'free_ram_mb': 5 * 1024, + 'free_disk_gb': 125, + 'current_workload': 2, + 'running_vms': 2, + 'cpu_info': 'cpu_info', + 'disk_available_least': 100 } ] } - httpretty.register_uri(httpretty.GET, self.url('detail'), - body=jsonutils.dumps(get_os_hypervisors_detail), - content_type='application/json') + self.requests.register_uri('GET', self.url('detail'), + json=get_os_hypervisors_detail, + headers=self.headers) get_os_hypervisors_stats = { 'hypervisor_statistics': { @@ -98,9 +103,9 @@ class V1(base.Fixture): } } - httpretty.register_uri(httpretty.GET, self.url('statistics'), - body=jsonutils.dumps(get_os_hypervisors_stats), - content_type='application/json') + self.requests.register_uri('GET', self.url('statistics'), + json=get_os_hypervisors_stats, + headers=self.headers) get_os_hypervisors_search = { 'hypervisors': [ @@ -109,9 +114,9 @@ class V1(base.Fixture): ] } - httpretty.register_uri(httpretty.GET, self.url('hyper', 'search'), - body=jsonutils.dumps(get_os_hypervisors_search), - content_type='application/json') + self.requests.register_uri('GET', self.url('hyper', 'search'), + json=get_os_hypervisors_search, + headers=self.headers) get_hyper_server = { 'hypervisors': [ @@ -134,9 +139,9 @@ class V1(base.Fixture): ] } - httpretty.register_uri(httpretty.GET, self.url('hyper', 'servers'), - body=jsonutils.dumps(get_hyper_server), - content_type='application/json') + self.requests.register_uri('GET', self.url('hyper', 'servers'), + json=get_hyper_server, + headers=self.headers) get_os_hypervisors_1234 = { 'hypervisor': { @@ -160,9 +165,9 @@ class V1(base.Fixture): } } - httpretty.register_uri(httpretty.GET, self.url(1234), - body=jsonutils.dumps(get_os_hypervisors_1234), - content_type='application/json') + self.requests.register_uri('GET', self.url(1234), + json=get_os_hypervisors_1234, + headers=self.headers) get_os_hypervisors_uptime = { 'hypervisor': { @@ -172,9 +177,9 @@ class V1(base.Fixture): } } - httpretty.register_uri(httpretty.GET, self.url(1234, 'uptime'), - body=jsonutils.dumps(get_os_hypervisors_uptime), - content_type='application/json') + self.requests.register_uri('GET', self.url(1234, 'uptime'), + json=get_os_hypervisors_uptime, + headers=self.headers) class V3(V1): @@ -189,10 +194,10 @@ class V3(V1): ] } - httpretty.register_uri(httpretty.GET, - self.url('search', query='hyper'), - body=jsonutils.dumps(get_os_hypervisors_search), - content_type='application/json') + self.requests.register_uri('GET', + self.url('search', query='hyper'), + json=get_os_hypervisors_search, + headers=self.headers) get_1234_servers = { 'hypervisor': { @@ -205,6 +210,6 @@ class V3(V1): }, } - httpretty.register_uri(httpretty.GET, self.url(1234, 'servers'), - body=jsonutils.dumps(get_1234_servers), - content_type='application/json') + self.requests.register_uri('GET', self.url(1234, 'servers'), + json=get_1234_servers, + headers=self.headers) diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/images.py b/awx/lib/site-packages/novaclient/tests/fixture_data/images.py index 09a134e5fc..40cfd0c949 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/images.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/images.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httpretty - from novaclient.openstack.common import jsonutils from novaclient.tests import fakes from novaclient.tests.fixture_data import base @@ -31,9 +29,11 @@ class V1(base.Fixture): ] } - httpretty.register_uri(httpretty.GET, self.url(), - body=jsonutils.dumps(get_images), - content_type='application/json') + headers = {'Content-Type': 'application/json'} + + self.requests.register_uri('GET', self.url(), + json=get_images, + headers=headers) image_1 = { 'id': 1, @@ -58,60 +58,53 @@ class V1(base.Fixture): "links": {}, } - get_images_detail = {'images': [image_1, image_2]} + self.requests.register_uri('GET', self.url('detail'), + json={'images': [image_1, image_2]}, + headers=headers) - httpretty.register_uri(httpretty.GET, self.url('detail'), - body=jsonutils.dumps(get_images_detail), - content_type='application/json') + self.requests.register_uri('GET', self.url(1), + json={'image': image_1}, + headers=headers) - get_images_1 = {'image': image_1} + self.requests.register_uri('GET', self.url(2), + json={'image': image_2}, + headers=headers) - httpretty.register_uri(httpretty.GET, self.url(1), - body=jsonutils.dumps(get_images_1), - content_type='application/json') + self.requests.register_uri('GET', self.url(456), + json={'image': image_2}, + headers=headers) - get_images_2 = {'image': image_2} - - httpretty.register_uri(httpretty.GET, self.url(2), - body=jsonutils.dumps(get_images_2), - content_type='application/json') - - httpretty.register_uri(httpretty.GET, self.url(456), - body=jsonutils.dumps(get_images_2), - content_type='application/json') - - def post_images(request, url, headers): - body = jsonutils.loads(request.body.decode('utf-8')) + def post_images(request, context): + body = jsonutils.loads(request.body) assert list(body) == ['image'] fakes.assert_has_keys(body['image'], required=['serverId', 'name']) - return 202, headers, jsonutils.dumps(images_1) + return images_1 - httpretty.register_uri(httpretty.POST, self.url(), - body=post_images, - content_type='application/json') + self.requests.register_uri('POST', self.url(), + json=post_images, + headers=headers, + status_code=202) - def post_images_1_metadata(request, url, headers): - body = jsonutils.loads(request.body.decode('utf-8')) + def post_images_1_metadata(request, context): + body = jsonutils.loads(request.body) assert list(body) == ['metadata'] fakes.assert_has_keys(body['metadata'], required=['test_key']) - data = jsonutils.dumps({'metadata': image_1['metadata']}) - return 200, headers, data + return {'metadata': image_1['metadata']} - httpretty.register_uri(httpretty.POST, self.url(1, 'metadata'), - body=post_images_1_metadata, - content_type='application/json') + self.requests.register_uri('POST', self.url(1, 'metadata'), + json=post_images_1_metadata, + headers=headers) for u in (1, 2, '1/metadata/test_key'): - httpretty.register_uri(httpretty.DELETE, self.url(u), - status=204) + self.requests.register_uri('DELETE', self.url(u), status_code=204) - httpretty.register_uri(httpretty.HEAD, self.url(1), status=200, - x_image_meta_id=1, - x_image_meta_name='CentOS 5.2', - x_image_meta_updated='2010-10-10T12:00:00Z', - x_image_meta_created='2010-10-10T12:00:00Z', - x_image_meta_status='ACTIVE', - x_image_meta_property_test_key='test_value') + image_headers = {'x-image-meta-id': '1', + 'x-image-meta-name': 'CentOS 5.2', + 'x-image-meta-updated': '2010-10-10T12:00:00Z', + 'x-image-meta-created': '2010-10-10T12:00:00Z', + 'x-image-meta-status': 'ACTIVE', + 'x-image-meta-property-test-key': 'test_value'} + self.requests.register_uri('HEAD', self.url(1), headers=image_headers) class V3(V1): diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/keypairs.py b/awx/lib/site-packages/novaclient/tests/fixture_data/keypairs.py index b3400a76cb..cc8921beb7 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/keypairs.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/keypairs.py @@ -10,9 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. - -import httpretty - from novaclient.openstack.common import jsonutils from novaclient.tests import fakes from novaclient.tests.fixture_data import base @@ -26,25 +23,27 @@ class V1(base.Fixture): super(V1, self).setUp() keypair = {'fingerprint': 'FAKE_KEYPAIR', 'name': 'test'} - httpretty.register_uri(httpretty.GET, self.url(), - body=jsonutils.dumps({'keypairs': [keypair]}), - content_type='application/json') + headers = {'Content-Type': 'application/json'} - httpretty.register_uri(httpretty.GET, self.url('test'), - body=jsonutils.dumps({'keypair': keypair}), - content_type='application/json') + self.requests.register_uri('GET', self.url(), + json={'keypairs': [keypair]}, + headers=headers) - httpretty.register_uri(httpretty.DELETE, self.url('test'), status=202) + self.requests.register_uri('GET', self.url('test'), + json={'keypair': keypair}, + headers=headers) - def post_os_keypairs(request, url, headers): - body = jsonutils.loads(request.body.decode('utf-8')) + self.requests.register_uri('DELETE', self.url('test'), status_code=202) + + def post_os_keypairs(request, context): + body = jsonutils.loads(request.body) assert list(body) == ['keypair'] fakes.assert_has_keys(body['keypair'], required=['name']) - return 202, headers, jsonutils.dumps({'keypair': keypair}) + return {'keypair': keypair} - httpretty.register_uri(httpretty.POST, self.url(), - body=post_os_keypairs, - content_type='application/json') + self.requests.register_uri('POST', self.url(), + json=post_os_keypairs, + headers=headers) class V3(V1): diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/limits.py b/awx/lib/site-packages/novaclient/tests/fixture_data/limits.py index 5eca22e4d4..0b9488d3fb 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/limits.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/limits.py @@ -10,9 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httpretty - -from novaclient.openstack.common import jsonutils from novaclient.tests.fixture_data import base @@ -77,6 +74,7 @@ class Fixture(base.Fixture): }, } - httpretty.register_uri(httpretty.GET, self.url(), - body=jsonutils.dumps(get_limits), - content_type='application/json') + headers = {'Content-Type': 'application/json'} + self.requests.register_uri('GET', self.url(), + json=get_limits, + headers=headers) diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/networks.py b/awx/lib/site-packages/novaclient/tests/fixture_data/networks.py index 12bb4b1eb2..df721b25d1 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/networks.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/networks.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httpretty - from novaclient.openstack.common import jsonutils from novaclient.tests.fixture_data import base @@ -34,29 +32,30 @@ class Fixture(base.Fixture): ] } - httpretty.register_uri(httpretty.GET, self.url(), - body=jsonutils.dumps(get_os_networks), - content_type='application/json') + headers = {'Content-Type': 'application/json'} - def post_os_networks(request, url, headers): - body = jsonutils.loads(request.body.decode('utf-8')) - data = jsonutils.dumps({'network': body}) - return 202, headers, data + self.requests.register_uri('GET', self.url(), + json=get_os_networks, + headers=headers) - httpretty.register_uri(httpretty.POST, self.url(), - body=post_os_networks, - content_type='application/json') + def post_os_networks(request, context): + body = jsonutils.loads(request.body) + return {'network': body} + + self.requests.register_uri("POST", self.url(), + json=post_os_networks, + headers=headers) get_os_networks_1 = {'network': {"label": "1", "cidr": "10.0.0.0/24"}} - httpretty.register_uri(httpretty.GET, self.url(1), - body=jsonutils.dumps(get_os_networks_1), - content_type='application/json') + self.requests.register_uri('GET', self.url(1), + json=get_os_networks_1, + headers=headers) - httpretty.register_uri(httpretty.DELETE, - self.url('networkdelete'), - stauts=202) + self.requests.register_uri('DELETE', + self.url('networkdelete'), + status_code=202) for u in ('add', 'networkdisassociate/action', 'networktest/action', '1/action', '2/action'): - httpretty.register_uri(httpretty.POST, self.url(u), stauts=202) + self.requests.register_uri('POST', self.url(u), status_code=202) diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/quotas.py b/awx/lib/site-packages/novaclient/tests/fixture_data/quotas.py index 70db079dd6..3250f8843d 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/quotas.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/quotas.py @@ -10,9 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httpretty - -from novaclient.openstack.common import jsonutils from novaclient.tests.fixture_data import base @@ -25,32 +22,32 @@ class V1(base.Fixture): uuid = '97f4c221-bff4-4578-b030-0df4ef119353' uuid2 = '97f4c221bff44578b0300df4ef119353' - test_json = jsonutils.dumps({'quota_set': self.test_quota('test')}) + test_json = {'quota_set': self.test_quota('test')} + self.headers = {'Content-Type': 'application/json'} for u in ('test', 'tenant-id', 'tenant-id/defaults', '%s/defaults' % uuid2): - httpretty.register_uri(httpretty.GET, self.url(u), - body=test_json, - content_type='application/json') + self.requests.register_uri('GET', self.url(u), + json=test_json, + headers=self.headers) - quota_json = jsonutils.dumps({'quota_set': self.test_quota(uuid)}) - httpretty.register_uri(httpretty.PUT, self.url(uuid), - body=quota_json, - content_type='application/json') - httpretty.register_uri(httpretty.GET, self.url(uuid), - body=quota_json, - content_type='application/json') + self.requests.register_uri('PUT', self.url(uuid), + json={'quota_set': self.test_quota(uuid)}, + headers=self.headers) - quota_json2 = jsonutils.dumps({'quota_set': self.test_quota(uuid2)}) - httpretty.register_uri(httpretty.PUT, self.url(uuid2), - body=quota_json2, - content_type='application/json') - httpretty.register_uri(httpretty.GET, self.url(uuid2), - body=quota_json2, - content_type='application/json') + self.requests.register_uri('GET', self.url(uuid), + json={'quota_set': self.test_quota(uuid)}, + headers=self.headers) + + self.requests.register_uri('PUT', self.url(uuid2), + json={'quota_set': self.test_quota(uuid2)}, + headers=self.headers) + self.requests.register_uri('GET', self.url(uuid2), + json={'quota_set': self.test_quota(uuid2)}, + headers=self.headers) for u in ('test', uuid2): - httpretty.register_uri(httpretty.DELETE, self.url(u), status=202) + self.requests.register_uri('DELETE', self.url(u), status_code=202) def test_quota(self, tenant_id='test'): return { @@ -82,6 +79,6 @@ class V3(V1): } } - httpretty.register_uri(httpretty.GET, self.url('test', 'detail'), - body=jsonutils.dumps(get_detail), - content_type='application/json') + self.requests.register_uri('GET', self.url('test', 'detail'), + json=get_detail, + headers=self.headers) diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/security_group_rules.py b/awx/lib/site-packages/novaclient/tests/fixture_data/security_group_rules.py index 303cfee7c6..480c4fc6d0 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/security_group_rules.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/security_group_rules.py @@ -10,8 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httpretty - from novaclient.openstack.common import jsonutils from novaclient.tests import fakes from novaclient.tests.fixture_data import base @@ -34,24 +32,26 @@ class Fixture(base.Fixture): 'cidr': '10.0.0.0/8' } - get_rules = {'security_group_rules': [rule]} - httpretty.register_uri(httpretty.GET, self.url(), - body=jsonutils.dumps(get_rules), - content_type='application/json') + headers = {'Content-Type': 'application/json'} + + self.requests.register_uri('GET', self.url(), + json={'security_group_rules': [rule]}, + headers=headers) for u in (1, 11, 12): - httpretty.register_uri(httpretty.DELETE, self.url(u), status=202) + self.requests.register_uri('DELETE', self.url(u), status_code=202) - def post_rules(request, url, headers): - body = jsonutils.loads(request.body.decode('utf-8')) + def post_rules(request, context): + body = jsonutils.loads(request.body) assert list(body) == ['security_group_rule'] fakes.assert_has_keys(body['security_group_rule'], required=['parent_group_id'], optional=['group_id', 'ip_protocol', 'from_port', 'to_port', 'cidr']) - return 202, headers, jsonutils.dumps({'security_group_rule': rule}) + return {'security_group_rule': rule} - httpretty.register_uri(httpretty.POST, self.url(), - body=post_rules, - content_type='application/json') + self.requests.register_uri('POST', self.url(), + json=post_rules, + headers=headers, + status_code=202) diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/security_groups.py b/awx/lib/site-packages/novaclient/tests/fixture_data/security_groups.py index 9f0c271ad8..7d1fed380a 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/security_groups.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/security_groups.py @@ -10,9 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. - -import httpretty - from novaclient.openstack.common import jsonutils from novaclient.tests import fakes from novaclient.tests.fixture_data import base @@ -64,36 +61,39 @@ class Fixture(base.Fixture): } get_groups = {'security_groups': [security_group_1, security_group_2]} - httpretty.register_uri(httpretty.GET, self.url(), - body=jsonutils.dumps(get_groups), - content_type='application/json') + headers = {'Content-Type': 'application/json'} + + self.requests.register_uri('GET', self.url(), + json=get_groups, + headers=headers) get_group_1 = {'security_group': security_group_1} - httpretty.register_uri(httpretty.GET, self.url(1), - body=jsonutils.dumps(get_group_1), - content_type='application/json') + self.requests.register_uri('GET', self.url(1), + json=get_group_1, + headers=headers) - httpretty.register_uri(httpretty.DELETE, self.url(1), status=202) + self.requests.register_uri('DELETE', self.url(1), status_code=202) - def post_os_security_groups(request, url, headers): - body = jsonutils.loads(request.body.decode('utf-8')) + def post_os_security_groups(request, context): + body = jsonutils.loads(request.body) assert list(body) == ['security_group'] fakes.assert_has_keys(body['security_group'], required=['name', 'description']) - r = jsonutils.dumps({'security_group': security_group_1}) - return 202, headers, r + return {'security_group': security_group_1} - httpretty.register_uri(httpretty.POST, self.url(), - body=post_os_security_groups, - content_type='application/json') + self.requests.register_uri('POST', self.url(), + json=post_os_security_groups, + headers=headers, + status_code=202) - def put_os_security_groups_1(request, url, headers): - body = jsonutils.loads(request.body.decode('utf-8')) + def put_os_security_groups_1(request, context): + body = jsonutils.loads(request.body) assert list(body) == ['security_group'] fakes.assert_has_keys(body['security_group'], required=['name', 'description']) - return 205, headers, request.body + return body - httpretty.register_uri(httpretty.PUT, self.url(1), - body=put_os_security_groups_1, - content_type='application/json') + self.requests.register_uri('PUT', self.url(1), + json=put_os_security_groups_1, + headers=headers, + status_code=205) diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/server_groups.py b/awx/lib/site-packages/novaclient/tests/fixture_data/server_groups.py index 65a0c78d25..47e307153d 100644 --- a/awx/lib/site-packages/novaclient/tests/fixture_data/server_groups.py +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/server_groups.py @@ -10,9 +10,6 @@ # License for the specific language governing permissions and limitations # under the License. - -import httpretty - from novaclient.openstack.common import jsonutils from novaclient.tests.fixture_data import base @@ -54,22 +51,23 @@ class Fixture(base.Fixture): } ] - get_server_groups = {'server_groups': server_groups} - httpretty.register_uri(httpretty.GET, self.url(), - body=jsonutils.dumps(get_server_groups), - content_type='application/json') + headers = {'Content-Type': 'application/json'} + + self.requests.register_uri('GET', self.url(), + json={'server_groups': server_groups}, + headers=headers) server = server_groups[0] - server_json = jsonutils.dumps({'server_group': server}) + server_j = jsonutils.dumps({'server_group': server}) def _register(method, *args): - httpretty.register_uri(method, self.url(*args), body=server_json) + self.requests.register_uri(method, self.url(*args), text=server_j) - _register(httpretty.POST) - _register(httpretty.POST, server['id']) - _register(httpretty.GET, server['id']) - _register(httpretty.PUT, server['id']) - _register(httpretty.POST, server['id'], '/action') + _register('POST') + _register('POST', server['id']) + _register('GET', server['id']) + _register('PUT', server['id']) + _register('POST', server['id'], '/action') - httpretty.register_uri(httpretty.DELETE, self.url(server['id']), - status=202) + self.requests.register_uri('DELETE', self.url(server['id']), + status_code=202) diff --git a/awx/lib/site-packages/novaclient/tests/fixture_data/servers.py b/awx/lib/site-packages/novaclient/tests/fixture_data/servers.py new file mode 100644 index 0000000000..c8f87bf604 --- /dev/null +++ b/awx/lib/site-packages/novaclient/tests/fixture_data/servers.py @@ -0,0 +1,612 @@ +# 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 novaclient.openstack.common import jsonutils +from novaclient.tests import fakes +from novaclient.tests.fixture_data import base + + +class Base(base.Fixture): + + base_url = 'servers' + + def setUp(self): + super(Base, self).setUp() + + get_servers = { + "servers": [ + {'id': 1234, 'name': 'sample-server'}, + {'id': 5678, 'name': 'sample-server2'} + ] + } + + self.requests.register_uri('GET', self.url(), + json=get_servers, + headers=self.json_headers) + + self.server_1234 = { + "id": 1234, + "name": "sample-server", + "image": { + "id": 2, + "name": "sample image", + }, + "flavor": { + "id": 1, + "name": "256 MB Server", + }, + "hostId": "e4d909c290d0fb1ca068ffaddf22cbd0", + "status": "BUILD", + "progress": 60, + "addresses": { + "public": [{ + "version": 4, + "addr": "1.2.3.4", + }, + { + "version": 4, + "addr": "5.6.7.8", + }], + "private": [{ + "version": 4, + "addr": "10.11.12.13", + }], + }, + "metadata": { + "Server Label": "Web Head 1", + "Image Version": "2.1" + }, + "OS-EXT-SRV-ATTR:host": "computenode1", + "security_groups": [{ + 'id': 1, 'name': 'securitygroup1', + 'description': 'FAKE_SECURITY_GROUP', + 'tenant_id': '4ffc664c198e435e9853f2538fbcd7a7' + }], + "OS-EXT-MOD:some_thing": "mod_some_thing_value", + } + + self.server_5678 = { + "id": 5678, + "name": "sample-server2", + "image": { + "id": 2, + "name": "sample image", + }, + "flavor": { + "id": 1, + "name": "256 MB Server", + }, + "hostId": "9e107d9d372bb6826bd81d3542a419d6", + "status": "ACTIVE", + "addresses": { + "public": [{ + "version": 4, + "addr": "4.5.6.7", + }, + { + "version": 4, + "addr": "5.6.9.8", + }], + "private": [{ + "version": 4, + "addr": "10.13.12.13", + }], + }, + "metadata": { + "Server Label": "DB 1" + }, + "OS-EXT-SRV-ATTR:host": "computenode2", + "security_groups": [{ + 'id': 1, 'name': 'securitygroup1', + 'description': 'FAKE_SECURITY_GROUP', + 'tenant_id': '4ffc664c198e435e9853f2538fbcd7a7' + }, + { + 'id': 2, 'name': 'securitygroup2', + 'description': 'ANOTHER_FAKE_SECURITY_GROUP', + 'tenant_id': '4ffc664c198e435e9853f2538fbcd7a7' + }], + } + + self.server_9012 = { + "id": 9012, + "name": "sample-server3", + "image": "", + "flavor": { + "id": 1, + "name": "256 MB Server", + }, + "hostId": "9e107d9d372bb6826bd81d3542a419d6", + "status": "ACTIVE", + "addresses": { + "public": [{ + "version": 4, + "addr": "4.5.6.7", + }, + { + "version": 4, + "addr": "5.6.9.8", + }], + "private": [{ + "version": 4, + "addr": "10.13.12.13", + }], + }, + "metadata": { + "Server Label": "DB 1" + } + } + + servers = [self.server_1234, self.server_5678, self.server_9012] + get_servers_detail = {"servers": servers} + + self.requests.register_uri('GET', self.url('detail'), + json=get_servers_detail, + headers=self.json_headers) + + self.server_1235 = self.server_1234.copy() + self.server_1235['id'] = 1235 + self.server_1235['status'] = 'error' + self.server_1235['fault'] = {'message': 'something went wrong!'} + + for s in servers + [self.server_1235]: + self.requests.register_uri('GET', self.url(s['id']), + json={'server': s}, + headers=self.json_headers) + + for s in (1234, 5678): + self.requests.register_uri('DELETE', self.url(s), status_code=202) + + for k in ('test_key', 'key1', 'key2'): + self.requests.register_uri('DELETE', + self.url(1234, 'metadata', k), + status_code=204) + + metadata1 = {'metadata': {'test_key': 'test_value'}} + self.requests.register_uri('POST', self.url(1234, 'metadata'), + json=metadata1, + headers=self.json_headers) + self.requests.register_uri('PUT', + self.url(1234, 'metadata', 'test_key'), + json=metadata1, + headers=self.json_headers) + + self.diagnostic = {'data': 'Fake diagnostics'} + + metadata2 = {'metadata': {'key1': 'val1'}} + for u in ('uuid1', 'uuid2', 'uuid3', 'uuid4'): + self.requests.register_uri('POST', self.url(u, 'metadata'), + json=metadata2, status_code=204) + self.requests.register_uri('DELETE', + self.url(u, 'metadata', 'key1'), + json=self.diagnostic, + headers=self.json_headers) + + get_security_groups = { + "security_groups": [{ + 'id': 1, + 'name': 'securitygroup1', + 'description': 'FAKE_SECURITY_GROUP', + 'tenant_id': '4ffc664c198e435e9853f2538fbcd7a7', + 'rules': []}] + } + + self.requests.register_uri('GET', + self.url('1234', 'os-security-groups'), + json=get_security_groups) + + self.requests.register_uri('POST', self.url(), + json=self.post_servers, + headers=self.json_headers) + + self.requests.register_uri('POST', self.url('1234', 'action'), + json=self.post_servers_1234_action, + headers=self.json_headers) + + get_os_interface = { + "interfaceAttachments": [ + { + "port_state": "ACTIVE", + "net_id": "net-id-1", + "port_id": "port-id-1", + "mac_address": "aa:bb:cc:dd:ee:ff", + "fixed_ips": [{"ip_address": "1.2.3.4"}], + }, + { + "port_state": "ACTIVE", + "net_id": "net-id-1", + "port_id": "port-id-1", + "mac_address": "aa:bb:cc:dd:ee:ff", + "fixed_ips": [{"ip_address": "1.2.3.4"}], + } + ] + } + + self.requests.register_uri('GET', + self.url('1234', 'os-interface'), + json=get_os_interface, + headers=self.json_headers) + + interface_data = {'interfaceAttachment': {}} + self.requests.register_uri('POST', + self.url('1234', 'os-interface'), + json=interface_data, + headers=self.json_headers) + + def put_servers_1234(request, context): + body = jsonutils.loads(request.body) + assert list(body) == ['server'] + fakes.assert_has_keys(body['server'], + optional=['name', 'adminPass']) + return request.body + + self.requests.register_uri('PUT', self.url(1234), + text=put_servers_1234, + status_code=204, + headers=self.json_headers) + + def post_os_volumes_boot(request, context): + body = jsonutils.loads(request.body) + assert (set(body.keys()) <= + set(['server', 'os:scheduler_hints'])) + + fakes.assert_has_keys(body['server'], + required=['name', 'flavorRef'], + optional=['imageRef']) + + data = body['server'] + + # Require one, and only one, of the keys for bdm + if 'block_device_mapping' not in data: + if 'block_device_mapping_v2' not in data: + msg = "missing required keys: 'block_device_mapping'" + raise AssertionError(msg) + elif 'block_device_mapping_v2' in data: + msg = "found extra keys: 'block_device_mapping'" + raise AssertionError(msg) + + return {'server': self.server_9012} + + # NOTE(jamielennox): hack to make os_volumes mock go to the right place + base_url = self.base_url + self.base_url = None + self.requests.register_uri('POST', self.url('os-volumes_boot'), + json=post_os_volumes_boot, + status_code=202, + headers=self.json_headers) + self.base_url = base_url + + # + # Server password + # + + self.requests.register_uri('DELETE', + self.url(1234, 'os-server-password'), + status_code=202) + + +class V1(Base): + + def setUp(self): + super(V1, self).setUp() + + # + # Server Addresses + # + + add = self.server_1234['addresses'] + self.requests.register_uri('GET', self.url(1234, 'ips'), + json={'addresses': add}, + headers=self.json_headers) + + self.requests.register_uri('GET', self.url(1234, 'ips', 'public'), + json={'public': add['public']}, + headers=self.json_headers) + + self.requests.register_uri('GET', self.url(1234, 'ips', 'private'), + json={'private': add['private']}, + headers=self.json_headers) + + self.requests.register_uri('DELETE', + self.url(1234, 'ips', 'public', '1.2.3.4'), + status_code=202) + + self.requests.register_uri('GET', + self.url('1234', 'diagnostics'), + json=self.diagnostic) + + self.requests.register_uri('DELETE', + self.url('1234', 'os-interface', 'port-id')) + + # Testing with the following password and key + # + # Clear password: FooBar123 + # + # RSA Private Key: novaclient/tests/idfake.pem + # + # Encrypted password + # OIuEuQttO8Rk93BcKlwHQsziDAnkAm/V6V8VPToA8ZeUaUBWwS0gwo2K6Y61Z96r + # qG447iRz0uTEEYq3RAYJk1mh3mMIRVl27t8MtIecR5ggVVbz1S9AwXJQypDKl0ho + # QFvhCBcMWPohyGewDJOhDbtuN1IoFI9G55ZvFwCm5y7m7B2aVcoLeIsJZE4PLsIw + # /y5a6Z3/AoJZYGG7IH5WN88UROU3B9JZGFB2qtPLQTOvDMZLUhoPRIJeHiVSlo1N + # tI2/++UsXVg3ow6ItqCJGgdNuGG5JB+bslDHWPxROpesEIHdczk46HCpHQN8f1sk + # Hi/fmZZNQQqj1Ijq0caOIw== + + get_server_password = {'password': + 'OIuEuQttO8Rk93BcKlwHQsziDAnkAm/V6V8VPToA8ZeUaUBWwS0gwo2K6Y61Z96r' + 'qG447iRz0uTEEYq3RAYJk1mh3mMIRVl27t8MtIecR5ggVVbz1S9AwXJQypDKl0ho' + 'QFvhCBcMWPohyGewDJOhDbtuN1IoFI9G55ZvFwCm5y7m7B2aVcoLeIsJZE4PLsIw' + '/y5a6Z3/AoJZYGG7IH5WN88UROU3B9JZGFB2qtPLQTOvDMZLUhoPRIJeHiVSlo1N' + 'tI2/++UsXVg3ow6ItqCJGgdNuGG5JB+bslDHWPxROpesEIHdczk46HCpHQN8f1sk' + 'Hi/fmZZNQQqj1Ijq0caOIw=='} + self.requests.register_uri('GET', + self.url(1234, 'os-server-password'), + json=get_server_password) + + def post_servers(self, request, context): + body = jsonutils.loads(request.body) + context.status_code = 202 + assert (set(body.keys()) <= + set(['server', 'os:scheduler_hints'])) + fakes.assert_has_keys(body['server'], + required=['name', 'imageRef', 'flavorRef'], + optional=['metadata', 'personality']) + if 'personality' in body['server']: + for pfile in body['server']['personality']: + fakes.assert_has_keys(pfile, required=['path', 'contents']) + if body['server']['name'] == 'some-bad-server': + body = self.server_1235 + else: + body = self.server_1234 + + return {'server': body} + + def post_servers_1234_action(self, request, context): + _body = '' + body = jsonutils.loads(request.body) + context.status_code = 202 + assert len(body.keys()) == 1 + action = list(body)[0] + if action == 'reboot': + assert list(body[action]) == ['type'] + assert body[action]['type'] in ['HARD', 'SOFT'] + elif action == 'rebuild': + body = body[action] + adminPass = body.get('adminPass', 'randompassword') + assert 'imageRef' in body + _body = self.server_1234.copy() + _body['adminPass'] = adminPass + elif action == 'resize': + keys = body[action].keys() + assert 'flavorRef' in keys + elif action == 'confirmResize': + assert body[action] is None + # This one method returns a different response code + context.status_code = 204 + return None + elif action == 'revertResize': + assert body[action] is None + elif action == 'migrate': + assert body[action] is None + elif action == 'os-stop': + assert body[action] is None + elif action == 'os-start': + assert body[action] is None + elif action == 'forceDelete': + assert body[action] is None + elif action == 'restore': + assert body[action] is None + elif action == 'pause': + assert body[action] is None + elif action == 'unpause': + assert body[action] is None + elif action == 'lock': + assert body[action] is None + elif action == 'unlock': + assert body[action] is None + elif action == 'rescue': + assert body[action] is None + _body = {'Password': 'RescuePassword'} + elif action == 'unrescue': + assert body[action] is None + elif action == 'resume': + assert body[action] is None + elif action == 'suspend': + assert body[action] is None + elif action == 'lock': + assert body[action] is None + elif action == 'unlock': + assert body[action] is None + elif action == 'shelve': + assert body[action] is None + elif action == 'shelveOffload': + assert body[action] is None + elif action == 'unshelve': + assert body[action] is None + elif action == 'addFixedIp': + assert list(body[action]) == ['networkId'] + elif action == 'removeFixedIp': + assert list(body[action]) == ['address'] + elif action == 'addFloatingIp': + assert (list(body[action]) == ['address'] or + sorted(list(body[action])) == ['address', + 'fixed_address']) + elif action == 'removeFloatingIp': + assert list(body[action]) == ['address'] + elif action == 'createImage': + assert set(body[action].keys()) == set(['name', 'metadata']) + context.headers['location'] = "http://blah/images/456" + elif action == 'changePassword': + assert list(body[action]) == ['adminPass'] + elif action == 'os-getConsoleOutput': + assert list(body[action]) == ['length'] + context.status_code = 202 + return {'output': 'foo'} + elif action == 'os-getVNCConsole': + assert list(body[action]) == ['type'] + elif action == 'os-getSPICEConsole': + assert list(body[action]) == ['type'] + elif action == 'os-getRDPConsole': + assert list(body[action]) == ['type'] + elif action == 'os-getSerialConsole': + assert list(body[action]) == ['type'] + elif action == 'os-migrateLive': + assert set(body[action].keys()) == set(['host', + 'block_migration', + 'disk_over_commit']) + elif action == 'os-resetState': + assert list(body[action]) == ['state'] + elif action == 'resetNetwork': + assert body[action] is None + elif action == 'addSecurityGroup': + assert list(body[action]) == ['name'] + elif action == 'removeSecurityGroup': + assert list(body[action]) == ['name'] + elif action == 'createBackup': + assert set(body[action]) == set(['name', + 'backup_type', + 'rotation']) + elif action == 'evacuate': + keys = list(body[action]) + if 'adminPass' in keys: + keys.remove('adminPass') + assert set(keys) == set(['host', 'onSharedStorage']) + else: + raise AssertionError("Unexpected server action: %s" % action) + return {'server': _body} + + +class V3(Base): + + def setUp(self): + super(V3, self).setUp() + + get_interfaces = { + "interface_attachments": [ + { + "port_state": "ACTIVE", + "net_id": "net-id-1", + "port_id": "port-id-1", + "mac_address": "aa:bb:cc:dd:ee:ff", + "fixed_ips": [{"ip_address": "1.2.3.4"}], + }, + { + "port_state": "ACTIVE", + "net_id": "net-id-1", + "port_id": "port-id-1", + "mac_address": "aa:bb:cc:dd:ee:ff", + "fixed_ips": [{"ip_address": "1.2.3.4"}], + } + ] + } + + self.requests.register_uri('GET', + self.url('1234', 'os-attach-interfaces'), + json=get_interfaces, + headers=self.json_headers) + + attach_body = {'interface_attachment': {}} + self.requests.register_uri('POST', + self.url('1234', 'os-attach-interfaces'), + json=attach_body, + headers=self.json_headers) + + self.requests.register_uri('GET', + self.url('1234', 'os-server-diagnostics'), + json=self.diagnostic) + + url = self.url('1234', 'os-attach-interfaces', 'port-id') + self.requests.register_uri('DELETE', url) + + self.requests.register_uri('GET', + self.url(1234, 'os-server-password'), + json={'password': ''}) + + def post_servers(self, request, context): + body = jsonutils.loads(request.body) + assert set(body.keys()) <= set(['server']) + fakes.assert_has_keys(body['server'], + required=['name', 'image_ref', 'flavor_ref'], + optional=['metadata', 'personality', + 'os-scheduler-hints:scheduler_hints']) + if body['server']['name'] == 'some-bad-server': + body = self.server_1235 + else: + body = self.server_1234 + + context.status_code = 202 + return {'server': body} + + def post_servers_1234_action(self, request, context): + context.status_code = 202 + body_is_none_list = [ + 'revert_resize', 'migrate', 'stop', 'start', 'force_delete', + 'restore', 'pause', 'unpause', 'lock', 'unlock', 'unrescue', + 'resume', 'suspend', 'lock', 'unlock', 'shelve', 'shelve_offload', + 'unshelve', 'reset_network', 'rescue', 'confirm_resize'] + body_return_map = { + 'rescue': {'admin_password': 'RescuePassword'}, + 'get_console_output': {'output': 'foo'}, + 'rebuild': {'server': self.server_1234}, + } + body_param_check_exists = { + 'rebuild': 'image_ref', + 'resize': 'flavor_ref'} + body_params_check_exact = { + 'reboot': ['type'], + 'add_fixed_ip': ['network_id'], + 'evacuate': ['host', 'on_shared_storage'], + 'remove_fixed_ip': ['address'], + 'change_password': ['admin_password'], + 'get_console_output': ['length'], + 'get_vnc_console': ['type'], + 'get_spice_console': ['type'], + 'get_serial_console': ['type'], + 'reset_state': ['state'], + 'create_image': ['name', 'metadata'], + 'migrate_live': ['host', 'block_migration', 'disk_over_commit'], + 'create_backup': ['name', 'backup_type', 'rotation'], + 'attach': ['volume_id', 'device'], + 'detach': ['volume_id'], + 'swap_volume_attachment': ['old_volume_id', 'new_volume_id']} + + body = jsonutils.loads(request.body) + assert len(body.keys()) == 1 + action = list(body)[0] + _body = body_return_map.get(action, '') + + if action in body_is_none_list: + assert body[action] is None + + if action in body_param_check_exists: + assert body_param_check_exists[action] in body[action] + + if action == 'evacuate': + body[action].pop('admin_password', None) + + if action in body_params_check_exact: + assert set(body[action]) == set(body_params_check_exact[action]) + + if action == 'reboot': + assert body[action]['type'] in ['HARD', 'SOFT'] + elif action == 'confirm_resize': + # This one method returns a different response code + context.status_code = 204 + elif action == 'create_image': + context.headers['location'] = "http://blah/images/456" + + if action not in set.union(set(body_is_none_list), + set(body_params_check_exact.keys()), + set(body_param_check_exists.keys())): + raise AssertionError("Unexpected server action: %s" % action) + + return _body diff --git a/awx/lib/site-packages/novaclient/tests/test_auth_plugins.py b/awx/lib/site-packages/novaclient/tests/test_auth_plugins.py index 34e83bacad..ffc591c7f7 100644 --- a/awx/lib/site-packages/novaclient/tests/test_auth_plugins.py +++ b/awx/lib/site-packages/novaclient/tests/test_auth_plugins.py @@ -14,6 +14,8 @@ # under the License. import argparse + +from keystoneclient import fixture import mock import pkg_resources import requests @@ -32,30 +34,10 @@ from novaclient.v1_1 import client def mock_http_request(resp=None): """Mock an HTTP Request.""" if not resp: - resp = { - "access": { - "token": { - "expires": "12345", - "id": "FAKE_ID", - "tenant": { - "id": "FAKE_TENANT_ID", - } - }, - "serviceCatalog": [ - { - "type": "compute", - "endpoints": [ - { - "region": "RegionOne", - "adminURL": "http://localhost:8774/v1.1", - "internalURL": "http://localhost:8774/v1.1", - "publicURL": "http://localhost:8774/v1.1/", - }, - ], - }, - ], - }, - } + resp = fixture.V2Token() + resp.set_scope() + s = resp.add_service('compute') + s.add_endpoint("http://localhost:8774/v1.1", region='RegionOne') auth_response = utils.TestResponse({ "status_code": 200, @@ -171,7 +153,7 @@ class DeprecatedAuthPluginTest(utils.TestCase): auth_system="fakewithauthurl", auth_plugin=plugin) cs.client.authenticate() - self.assertEqual(cs.client.auth_url, "http://faked/v2.0") + self.assertEqual("http://faked/v2.0", cs.client.auth_url) test_auth_call() @@ -301,7 +283,7 @@ class AuthPluginTest(utils.TestCase): cs = client.Client("username", "password", "project_id", auth_system="fakewithauthurl", auth_plugin=plugin) - self.assertEqual(cs.client.auth_url, "http://faked/v2.0") + self.assertEqual("http://faked/v2.0", cs.client.auth_url) @mock.patch.object(pkg_resources, "iter_entry_points") def test_exception_if_no_authenticate(self, mock_iter_entry_points): diff --git a/awx/lib/site-packages/novaclient/tests/test_base.py b/awx/lib/site-packages/novaclient/tests/test_base.py index 717dc6ea5c..fca5b72d41 100644 --- a/awx/lib/site-packages/novaclient/tests/test_base.py +++ b/awx/lib/site-packages/novaclient/tests/test_base.py @@ -25,18 +25,18 @@ class BaseTest(utils.TestCase): def test_resource_repr(self): r = base.Resource(None, dict(foo="bar", baz="spam")) - self.assertEqual(repr(r), "") + self.assertEqual("", repr(r)) def test_getid(self): - self.assertEqual(base.getid(4), 4) + self.assertEqual(4, base.getid(4)) class TmpObject(object): id = 4 - self.assertEqual(base.getid(TmpObject), 4) + self.assertEqual(4, base.getid(TmpObject)) def test_resource_lazy_getattr(self): f = flavors.Flavor(cs.flavors, {'id': 1}) - self.assertEqual(f.name, '256 MB Server') + self.assertEqual('256 MB Server', f.name) cs.assert_called('GET', '/flavors/1') # Missing stuff still fails after a second get diff --git a/awx/lib/site-packages/novaclient/tests/test_client.py b/awx/lib/site-packages/novaclient/tests/test_client.py index bf868f8211..f8bcaed80b 100644 --- a/awx/lib/site-packages/novaclient/tests/test_client.py +++ b/awx/lib/site-packages/novaclient/tests/test_client.py @@ -14,11 +14,12 @@ # under the License. +import json import logging -import mock -import requests import fixtures +import mock +import requests import novaclient.client import novaclient.extension @@ -27,8 +28,6 @@ from novaclient.tests import utils import novaclient.v1_1.client import novaclient.v3.client -import json - class ClientConnectionPoolTest(utils.TestCase): @@ -48,7 +47,7 @@ class ClientTest(utils.TestCase): projectid='project', timeout=2, auth_url="http://www.blah.com") - self.assertEqual(instance.timeout, 2) + self.assertEqual(2, instance.timeout) mock_request = mock.Mock() mock_request.return_value = requests.Response() mock_request.return_value.status_code = 200 @@ -107,7 +106,38 @@ class ClientTest(utils.TestCase): allow_redirects=mock.ANY, data=json.dumps(data), verify=mock.ANY)] - self.assertEqual(mock_request.call_args_list, expected) + self.assertEqual(expected, mock_request.call_args_list) + + @mock.patch.object(novaclient.client.HTTPClient, 'request', + return_value=(200, "{'versions':[]}")) + def _check_version_url(self, management_url, version_url, mock_request): + projectid = '25e469aa1848471b875e68cde6531bc5' + instance = novaclient.client.HTTPClient(user='user', + password='password', + projectid=projectid, + auth_url="http://www.blah.com") + instance.auth_token = 'foobar' + instance.management_url = management_url % projectid + instance.version = 'v2.0' + + # If passing None as the part of url, a client accesses the url which + # doesn't include "v2/" for getting API version info. + instance.get(None) + mock_request.assert_called_once_with(version_url, 'GET', + headers=mock.ANY) + mock_request.reset_mock() + + # Otherwise, a client accesses the url which includes "v2/". + instance.get('servers') + url = instance.management_url + 'servers' + mock_request.assert_called_once_with(url, 'GET', headers=mock.ANY) + + def test_client_version_url(self): + self._check_version_url('http://foo.com/v2/%s', 'http://foo.com/') + + def test_client_version_url_with_project_name(self): + self._check_version_url('http://foo.com/nova/v2/%s', + 'http://foo.com/nova/') def test_get_client_class_v3(self): output = novaclient.client.get_client_class('3') @@ -233,7 +263,7 @@ class ClientTest(utils.TestCase): def test_get_password_simple(self): cs = novaclient.client.HTTPClient("user", "password", "", "") cs.password_func = mock.Mock() - self.assertEqual(cs._get_password(), "password") + self.assertEqual("password", cs._get_password()) self.assertFalse(cs.password_func.called) def test_get_password_none(self): @@ -243,26 +273,26 @@ class ClientTest(utils.TestCase): def test_get_password_func(self): cs = novaclient.client.HTTPClient("user", None, "", "") cs.password_func = mock.Mock(return_value="password") - self.assertEqual(cs._get_password(), "password") + self.assertEqual("password", cs._get_password()) cs.password_func.assert_called_once_with() cs.password_func = mock.Mock() - self.assertEqual(cs._get_password(), "password") + self.assertEqual("password", cs._get_password()) self.assertFalse(cs.password_func.called) def test_auth_url_rstrip_slash(self): cs = novaclient.client.HTTPClient("user", "password", "project_id", auth_url="foo/v2/") - self.assertEqual(cs.auth_url, "foo/v2") + self.assertEqual("foo/v2", cs.auth_url) def test_token_and_bypass_url(self): cs = novaclient.client.HTTPClient(None, None, None, auth_token="12345", bypass_url="compute/v100/") self.assertIsNone(cs.auth_url) - self.assertEqual(cs.auth_token, "12345") - self.assertEqual(cs.bypass_url, "compute/v100") - self.assertEqual(cs.management_url, "compute/v100") + self.assertEqual("12345", cs.auth_token) + self.assertEqual("compute/v100", cs.bypass_url) + self.assertEqual("compute/v100", cs.management_url) @mock.patch("novaclient.client.requests.Session") def test_session(self, mock_session): @@ -343,6 +373,9 @@ class ClientTest(utils.TestCase): {'X-Foo': 'bar', 'X-Auth-Token': 'totally_bogus'} }) + cs.http_log_req('GET', '/foo', {'headers': {}, + 'data': '{"auth": {"passwordCredentials": ' + '{"password": "zhaoqin"}}}'}) output = self.logger.output.split('\n') @@ -356,3 +389,32 @@ class ClientTest(utils.TestCase): '"X-Auth-Token: {SHA1}b42162b6ffdbd7c3c37b7c95b7ba9f51dda0236d"' ' -H "X-Foo: bar"', output) + self.assertIn( + "REQ: curl -i '/foo' -X GET -d " + '\'{"auth": {"passwordCredentials": {"password":' + ' "{SHA1}4fc49c6a671ce889078ff6b250f7066cf6d2ada2"}}}\'', + output) + + def test_log_resp(self): + self.logger = self.useFixture( + fixtures.FakeLogger( + format="%(message)s", + level=logging.DEBUG, + nuke_handlers=True + ) + ) + + cs = novaclient.client.HTTPClient("user", None, "", + connection_pool=True) + cs.http_log_debug = True + text = ('{"access": {"token": {"id": "zhaoqin"}}}') + resp = utils.TestResponse({'status_code': 200, 'headers': {}, + 'text': text}) + + cs.http_log_resp(resp) + output = self.logger.output.split('\n') + + self.assertIn('RESP: [200] {}', output) + self.assertIn('RESP BODY: {"access": {"token": {"id":' + ' "{SHA1}4fc49c6a671ce889078ff6b250f7066cf6d2ada2"}}}', + output) diff --git a/awx/lib/site-packages/novaclient/tests/test_discover.py b/awx/lib/site-packages/novaclient/tests/test_discover.py index e4acf0be57..a498824947 100644 --- a/awx/lib/site-packages/novaclient/tests/test_discover.py +++ b/awx/lib/site-packages/novaclient/tests/test_discover.py @@ -40,7 +40,7 @@ class DiscoverTest(utils.TestCase): def test(): shell = novaclient.shell.OpenStackComputeShell() for name, module in shell._discover_via_entry_points(): - self.assertEqual(name, 'foo') + self.assertEqual('foo', name) self.assertTrue(inspect.ismodule(module)) test() @@ -68,7 +68,7 @@ class DiscoverTest(utils.TestCase): def test(): shell = novaclient.shell.OpenStackComputeShell() extensions = shell._discover_extensions('1.1') - self.assertEqual(len(extensions), 3) + self.assertEqual(3, len(extensions)) names = sorted(['foo', 'bar', 'baz']) sorted_extensions = sorted(extensions, key=lambda ext: ext.name) for i in range(len(names)): diff --git a/awx/lib/site-packages/novaclient/tests/test_http.py b/awx/lib/site-packages/novaclient/tests/test_http.py index 2797a438f5..bd12ed827a 100644 --- a/awx/lib/site-packages/novaclient/tests/test_http.py +++ b/awx/lib/site-packages/novaclient/tests/test_http.py @@ -44,6 +44,32 @@ unknown_error_response = utils.TestResponse({ }) unknown_error_mock_request = mock.Mock(return_value=unknown_error_response) +retry_after_response = utils.TestResponse({ + "status_code": 413, + "text": '', + "headers": { + "retry-after": "5" + }, +}) +retry_after_mock_request = mock.Mock(return_value=retry_after_response) + +retry_after_no_headers_response = utils.TestResponse({ + "status_code": 413, + "text": '', +}) +retry_after_no_headers_mock_request = mock.Mock( + return_value=retry_after_no_headers_response) + +retry_after_non_supporting_response = utils.TestResponse({ + "status_code": 403, + "text": '', + "headers": { + "retry-after": "5" + }, +}) +retry_after_non_supporting_mock_request = mock.Mock( + return_value=retry_after_non_supporting_response) + def get_client(): cl = client.HTTPClient("username", "password", @@ -79,7 +105,7 @@ class ClientTest(utils.TestCase): headers=headers, **self.TEST_REQUEST_BASE) # Automatic JSON parsing - self.assertEqual(body, {"hi": "there"}) + self.assertEqual({"hi": "there"}, body) test_get_call() @@ -136,11 +162,11 @@ class ClientTest(utils.TestCase): def test_client_logger(self): cl1 = client.HTTPClient("username", "password", "project_id", "auth_test", http_log_debug=True) - self.assertEqual(len(cl1._logger.handlers), 1) + self.assertEqual(1, len(cl1._logger.handlers)) cl2 = client.HTTPClient("username", "password", "project_id", "auth_test", http_log_debug=True) - self.assertEqual(len(cl2._logger.handlers), 1) + self.assertEqual(1, len(cl2._logger.handlers)) @mock.patch.object(requests, 'request', unknown_error_mock_request) def test_unknown_server_error(self): @@ -154,3 +180,33 @@ class ClientTest(utils.TestCase): self.assertIn('Unknown Error', six.text_type(exc)) else: self.fail('Expected exceptions.ClientException') + + @mock.patch.object(requests, "request", retry_after_mock_request) + def test_retry_after_request(self): + cl = get_client() + + try: + cl.get("/hi") + except exceptions.OverLimit as exc: + self.assertEqual(5, exc.retry_after) + else: + self.fail('Expected exceptions.OverLimit') + + @mock.patch.object(requests, "request", + retry_after_no_headers_mock_request) + def test_retry_after_request_no_headers(self): + cl = get_client() + + try: + cl.get("/hi") + except exceptions.OverLimit as exc: + self.assertEqual(0, exc.retry_after) + else: + self.fail('Expected exceptions.OverLimit') + + @mock.patch.object(requests, "request", + retry_after_non_supporting_mock_request) + def test_retry_after_request_non_supporting_exc(self): + cl = get_client() + + self.assertRaises(exceptions.Forbidden, cl.get, "/hi") diff --git a/awx/lib/site-packages/novaclient/tests/test_service_catalog.py b/awx/lib/site-packages/novaclient/tests/test_service_catalog.py index 9f224bb578..6f6ff4f466 100644 --- a/awx/lib/site-packages/novaclient/tests/test_service_catalog.py +++ b/awx/lib/site-packages/novaclient/tests/test_service_catalog.py @@ -11,116 +11,32 @@ # License for the specific language governing permissions and limitations # under the License. +from keystoneclient import fixture + from novaclient import exceptions from novaclient import service_catalog from novaclient.tests import utils -# Taken directly from keystone/content/common/samples/auth.json -# Do not edit this structure. Instead, grab the latest from there. +SERVICE_CATALOG = fixture.V2Token() +SERVICE_CATALOG.set_scope() -SERVICE_CATALOG = { - "access": { - "token": { - "id": "ab48a9efdfedb23ty3494", - "expires": "2010-11-01T03:32:15-05:00", - "tenant": { - "id": "345", - "name": "My Project" - } - }, - "user": { - "id": "123", - "name": "jqsmith", - "roles": [ - { - "id": "234", - "name": "compute:admin", - }, - { - "id": "235", - "name": "object-store:admin", - "tenantId": "1", - } - ], - "roles_links": [], - }, - "serviceCatalog": [ - { - "name": "Cloud Servers", - "type": "compute", - "endpoints": [ - { - # Tenant 1, no region, v1.0 - "tenantId": "1", - "publicURL": "https://compute1.host/v1/1", - "internalURL": "https://compute1.host/v1/1", - "versionId": "1.0", - "versionInfo": "https://compute1.host/v1.0/", - "versionList": "https://compute1.host/" - }, - { - # Tenant 2, with region, v1.1 - "tenantId": "2", - "publicURL": "https://compute1.host/v1.1/2", - "internalURL": "https://compute1.host/v1.1/2", - "region": "North", - "versionId": "1.1", - "versionInfo": "https://compute1.host/v1.1/", - "versionList": "https://compute1.host/" - }, - { - # Tenant 1, with region, v2.0 - "tenantId": "1", - "publicURL": "https://compute1.host/v2/1", - "internalURL": "https://compute1.host/v2/1", - "region": "North", - "versionId": "2", - "versionInfo": "https://compute1.host/v2/", - "versionList": "https://compute1.host/" - }, - ], - "endpoints_links": [], - }, - { - "name": "Nova Volumes", - "type": "volume", - "endpoints": [ - { - "tenantId": "1", - "publicURL": "https://volume1.host/v1/1", - "internalURL": "https://volume1.host/v1/1", - "region": "South", - "versionId": "1.0", - "versionInfo": "uri", - "versionList": "uri" - }, - { - "tenantId": "2", - "publicURL": "https://volume1.host/v1.1/2", - "internalURL": "https://volume1.host/v1.1/2", - "region": "South", - "versionId": "1.1", - "versionInfo": "https://volume1.host/v1.1/", - "versionList": "https://volume1.host/" - }, - ], - "endpoints_links": [ - { - "rel": "next", - "href": "https://identity1.host/v2.0/endpoints" - }, - ], - }, - ], - "serviceCatalog_links": [ - { - "rel": "next", - "href": "https://identity.host/v2.0/endpoints?session=2hfh8Ar", - }, - ], - }, -} +_s = SERVICE_CATALOG.add_service('compute') +_e = _s.add_endpoint("https://compute1.host/v1/1") +_e["tenantId"] = "1" +_e["versionId"] = "1.0" +_e = _s.add_endpoint("https://compute1.host/v1.1/2", region="North") +_e["tenantId"] = "2" +_e["versionId"] = "1.1" +_e = _s.add_endpoint("https://compute1.host/v2/1", region="North") +_e["tenantId"] = "1" +_e["versionId"] = "2" + +_s = SERVICE_CATALOG.add_service('volume') +_e = _s.add_endpoint("https://volume1.host/v1/1", region="South") +_e["tenantId"] = "1" +_e = _s.add_endpoint("https://volume1.host/v1.1/2", region="South") +_e["tenantId"] = "2" class ServiceCatalogTest(utils.TestCase): @@ -129,10 +45,10 @@ class ServiceCatalogTest(utils.TestCase): self.assertRaises(exceptions.AmbiguousEndpoints, sc.url_for, service_type='compute') - self.assertEqual(sc.url_for('tenantId', '1', service_type='compute'), - "https://compute1.host/v2/1") - self.assertEqual(sc.url_for('tenantId', '2', service_type='compute'), - "https://compute1.host/v1.1/2") + self.assertEqual("https://compute1.host/v2/1", + sc.url_for('tenantId', '1', service_type='compute')) + self.assertEqual("https://compute1.host/v1.1/2", + sc.url_for('tenantId', '2', service_type='compute')) self.assertRaises(exceptions.EndpointNotFound, sc.url_for, "region", "South", service_type='compute') @@ -148,10 +64,10 @@ class ServiceCatalogTest(utils.TestCase): self.assertRaises(exceptions.AmbiguousEndpoints, sc.url_for, service_type='volume') - self.assertEqual(sc.url_for('tenantId', '1', service_type='volume'), - "https://volume1.host/v1/1") - self.assertEqual(sc.url_for('tenantId', '2', service_type='volume'), - "https://volume1.host/v1.1/2") + self.assertEqual("https://volume1.host/v1/1", + sc.url_for('tenantId', '1', service_type='volume')) + self.assertEqual("https://volume1.host/v1.1/2", + sc.url_for('tenantId', '2', service_type='volume')) self.assertRaises(exceptions.EndpointNotFound, sc.url_for, "region", "North", service_type='volume') diff --git a/awx/lib/site-packages/novaclient/tests/test_shell.py b/awx/lib/site-packages/novaclient/tests/test_shell.py index fdcc78e684..240965d79e 100644 --- a/awx/lib/site-packages/novaclient/tests/test_shell.py +++ b/awx/lib/site-packages/novaclient/tests/test_shell.py @@ -11,15 +11,14 @@ # License for the specific language governing permissions and limitations # under the License. -import prettytable +import distutils.version as dist_version import re -import six import sys -from distutils.version import StrictVersion - import fixtures import mock +import prettytable +import six from testtools import matchers import novaclient.client @@ -201,7 +200,8 @@ class ShellTest(utils.TestCase): # default output of empty tables differs depending between prettytable # versions if (hasattr(prettytable, '__version__') and - StrictVersion(prettytable.__version__) < StrictVersion('0.7.2')): + dist_version.StrictVersion(prettytable.__version__) < + dist_version.StrictVersion('0.7.2')): ex = '\n' else: ex = ( @@ -232,7 +232,8 @@ class ShellTest(utils.TestCase): if version is None: cmd = 'list' else: - cmd = '--os-compute-api-version %s list' % version + cmd = ('--service_type %s --os-compute-api-version %s list' % + (service_type, version)) self.make_env() self.shell(cmd) _, client_kwargs = mock_client.call_args_list[0] diff --git a/awx/lib/site-packages/novaclient/tests/test_utils.py b/awx/lib/site-packages/novaclient/tests/test_utils.py index 31678ca2a3..69e5d61888 100644 --- a/awx/lib/site-packages/novaclient/tests/test_utils.py +++ b/awx/lib/site-packages/novaclient/tests/test_utils.py @@ -140,26 +140,26 @@ class PrintResultTestCase(test_utils.TestCase): def test_print_dict(self): dict = {'key': 'value'} utils.print_dict(dict) - self.assertEqual(sys.stdout.getvalue(), - '+----------+-------+\n' + self.assertEqual('+----------+-------+\n' '| Property | Value |\n' '+----------+-------+\n' '| key | value |\n' - '+----------+-------+\n') + '+----------+-------+\n', + sys.stdout.getvalue()) @mock.patch('sys.stdout', six.StringIO()) def test_print_dict_wrap(self): dict = {'key1': 'not wrapped', 'key2': 'this will be wrapped'} utils.print_dict(dict, wrap=16) - self.assertEqual(sys.stdout.getvalue(), - '+----------+--------------+\n' + self.assertEqual('+----------+--------------+\n' '| Property | Value |\n' '+----------+--------------+\n' '| key1 | not wrapped |\n' '| key2 | this will be |\n' '| | wrapped |\n' - '+----------+--------------+\n') + '+----------+--------------+\n', + sys.stdout.getvalue()) @mock.patch('sys.stdout', six.StringIO()) def test_print_list_sort_by_str(self): @@ -169,14 +169,14 @@ class PrintResultTestCase(test_utils.TestCase): utils.print_list(objs, ["Name", "Value"], sortby_index=0) - self.assertEqual(sys.stdout.getvalue(), - '+------+-------+\n' + self.assertEqual('+------+-------+\n' '| Name | Value |\n' '+------+-------+\n' '| k1 | 1 |\n' '| k2 | 3 |\n' '| k3 | 2 |\n' - '+------+-------+\n') + '+------+-------+\n', + sys.stdout.getvalue()) @mock.patch('sys.stdout', six.StringIO()) def test_print_list_sort_by_integer(self): @@ -186,14 +186,14 @@ class PrintResultTestCase(test_utils.TestCase): utils.print_list(objs, ["Name", "Value"], sortby_index=1) - self.assertEqual(sys.stdout.getvalue(), - '+------+-------+\n' + self.assertEqual('+------+-------+\n' '| Name | Value |\n' '+------+-------+\n' '| k1 | 1 |\n' '| k3 | 2 |\n' '| k2 | 3 |\n' - '+------+-------+\n') + '+------+-------+\n', + sys.stdout.getvalue()) # without sorting @mock.patch('sys.stdout', six.StringIO()) @@ -204,47 +204,47 @@ class PrintResultTestCase(test_utils.TestCase): utils.print_list(objs, ["Name", "Value"], sortby_index=None) - self.assertEqual(sys.stdout.getvalue(), - '+------+-------+\n' + self.assertEqual('+------+-------+\n' '| Name | Value |\n' '+------+-------+\n' '| k1 | 1 |\n' '| k3 | 3 |\n' '| k2 | 2 |\n' - '+------+-------+\n') + '+------+-------+\n', + sys.stdout.getvalue()) @mock.patch('sys.stdout', six.StringIO()) def test_print_dict_dictionary(self): dict = {'k': {'foo': 'bar'}} utils.print_dict(dict) - self.assertEqual(sys.stdout.getvalue(), - '+----------+----------------+\n' + self.assertEqual('+----------+----------------+\n' '| Property | Value |\n' '+----------+----------------+\n' '| k | {"foo": "bar"} |\n' - '+----------+----------------+\n') + '+----------+----------------+\n', + sys.stdout.getvalue()) @mock.patch('sys.stdout', six.StringIO()) def test_print_dict_list_dictionary(self): dict = {'k': [{'foo': 'bar'}]} utils.print_dict(dict) - self.assertEqual(sys.stdout.getvalue(), - '+----------+------------------+\n' + self.assertEqual('+----------+------------------+\n' '| Property | Value |\n' '+----------+------------------+\n' '| k | [{"foo": "bar"}] |\n' - '+----------+------------------+\n') + '+----------+------------------+\n', + sys.stdout.getvalue()) @mock.patch('sys.stdout', six.StringIO()) def test_print_dict_list(self): dict = {'k': ['foo', 'bar']} utils.print_dict(dict) - self.assertEqual(sys.stdout.getvalue(), - '+----------+----------------+\n' + self.assertEqual('+----------+----------------+\n' '| Property | Value |\n' '+----------+----------------+\n' '| k | ["foo", "bar"] |\n' - '+----------+----------------+\n') + '+----------+----------------+\n', + sys.stdout.getvalue()) class FlattenTestCase(test_utils.TestCase): @@ -270,22 +270,22 @@ class FlattenTestCase(test_utils.TestCase): def test_pretty_choice_list(self): l = [] r = utils.pretty_choice_list(l) - self.assertEqual(r, "") + self.assertEqual("", r) l = ["v1", "v2", "v3"] r = utils.pretty_choice_list(l) - self.assertEqual(r, "'v1', 'v2', 'v3'") + self.assertEqual("'v1', 'v2', 'v3'", r) def test_pretty_choice_dict(self): d = {} r = utils.pretty_choice_dict(d) - self.assertEqual(r, "") + self.assertEqual("", r) d = {"k1": "v1", "k2": "v2", "k3": "v3"} r = utils.pretty_choice_dict(d) - self.assertEqual(r, "'k1=v1', 'k2=v2', 'k3=v3'") + self.assertEqual("'k1=v1', 'k2=v2', 'k3=v3'", r) class ValidationsTestCase(test_utils.TestCase): diff --git a/awx/lib/site-packages/novaclient/tests/unit/fixture_data/images.py b/awx/lib/site-packages/novaclient/tests/unit/fixture_data/images.py index 4ec472581c..02f3596683 100644 --- a/awx/lib/site-packages/novaclient/tests/unit/fixture_data/images.py +++ b/awx/lib/site-packages/novaclient/tests/unit/fixture_data/images.py @@ -67,6 +67,25 @@ class V1(base.Fixture): json={'image': image_1}, headers=headers) + self.requests.register_uri('GET', self.url(2), + json={'image': image_2}, + headers=headers) + + self.requests.register_uri('GET', self.url(456), + json={'image': image_2}, + headers=headers) + + def post_images(request, context): + body = jsonutils.loads(request.body) + assert list(body) == ['image'] + fakes.assert_has_keys(body['image'], required=['serverId', 'name']) + return images_1 + + self.requests.register_uri('POST', self.url(), + json=post_images, + headers=headers, + status_code=202) + def post_images_1_metadata(request, context): body = jsonutils.loads(request.body) assert list(body) == ['metadata'] @@ -77,9 +96,17 @@ class V1(base.Fixture): json=post_images_1_metadata, headers=headers) - for u in (1, '1/metadata/test_key'): + for u in (1, 2, '1/metadata/test_key'): self.requests.register_uri('DELETE', self.url(u), status_code=204) + image_headers = {'x-image-meta-id': '1', + 'x-image-meta-name': 'CentOS 5.2', + 'x-image-meta-updated': '2010-10-10T12:00:00Z', + 'x-image-meta-created': '2010-10-10T12:00:00Z', + 'x-image-meta-status': 'ACTIVE', + 'x-image-meta-property-test-key': 'test_value'} + self.requests.register_uri('HEAD', self.url(1), headers=image_headers) + class V3(V1): diff --git a/awx/lib/site-packages/novaclient/tests/unit/fixture_data/servers.py b/awx/lib/site-packages/novaclient/tests/unit/fixture_data/servers.py index 3766f4290e..6e3dd46b7a 100644 --- a/awx/lib/site-packages/novaclient/tests/unit/fixture_data/servers.py +++ b/awx/lib/site-packages/novaclient/tests/unit/fixture_data/servers.py @@ -14,7 +14,6 @@ from oslo.serialization import jsonutils from novaclient.tests.unit import fakes from novaclient.tests.unit.fixture_data import base -from novaclient.tests.unit.v2 import fakes as v2_fakes class Base(base.Fixture): @@ -383,24 +382,43 @@ class V1(Base): context.status_code = 202 assert len(body.keys()) == 1 action = list(body)[0] - - if v2_fakes.FakeHTTPClient.check_server_actions(body): - # NOTE(snikitin): No need to do any operations here. This 'pass' - # is needed to avoid AssertionError in the last 'else' statement - # if we found 'action' in method check_server_actions and - # raise AssertionError if we didn't find 'action' at all. - pass + if action == 'reboot': + assert list(body[action]) == ['type'] + assert body[action]['type'] in ['HARD', 'SOFT'] elif action == 'rebuild': body = body[action] adminPass = body.get('adminPass', 'randompassword') assert 'imageRef' in body _body = self.server_1234.copy() _body['adminPass'] = adminPass + elif action == 'resize': + keys = body[action].keys() + assert 'flavorRef' in keys elif action == 'confirmResize': assert body[action] is None # This one method returns a different response code context.status_code = 204 return None + elif action == 'revertResize': + assert body[action] is None + elif action == 'migrate': + assert body[action] is None + elif action == 'os-stop': + assert body[action] is None + elif action == 'os-start': + assert body[action] is None + elif action == 'forceDelete': + assert body[action] is None + elif action == 'restore': + assert body[action] is None + elif action == 'pause': + assert body[action] is None + elif action == 'unpause': + assert body[action] is None + elif action == 'lock': + assert body[action] is None + elif action == 'unlock': + assert body[action] is None elif action == 'rescue': if body[action]: keys = set(body[action].keys()) @@ -408,15 +426,65 @@ class V1(Base): else: assert body[action] is None _body = {'adminPass': 'RescuePassword'} + elif action == 'unrescue': + assert body[action] is None + elif action == 'resume': + assert body[action] is None + elif action == 'suspend': + assert body[action] is None + elif action == 'lock': + assert body[action] is None + elif action == 'unlock': + assert body[action] is None + elif action == 'shelve': + assert body[action] is None + elif action == 'shelveOffload': + assert body[action] is None + elif action == 'unshelve': + assert body[action] is None + elif action == 'addFixedIp': + assert list(body[action]) == ['networkId'] + elif action == 'removeFixedIp': + assert list(body[action]) == ['address'] + elif action == 'addFloatingIp': + assert (list(body[action]) == ['address'] or + sorted(list(body[action])) == ['address', + 'fixed_address']) + elif action == 'removeFloatingIp': + assert list(body[action]) == ['address'] elif action == 'createImage': assert set(body[action].keys()) == set(['name', 'metadata']) context.headers['location'] = "http://blah/images/456" + elif action == 'changePassword': + assert list(body[action]) == ['adminPass'] elif action == 'os-getConsoleOutput': assert list(body[action]) == ['length'] context.status_code = 202 return {'output': 'foo'} + elif action == 'os-getVNCConsole': + assert list(body[action]) == ['type'] + elif action == 'os-getSPICEConsole': + assert list(body[action]) == ['type'] + elif action == 'os-getRDPConsole': + assert list(body[action]) == ['type'] elif action == 'os-getSerialConsole': assert list(body[action]) == ['type'] + elif action == 'os-migrateLive': + assert set(body[action].keys()) == set(['host', + 'block_migration', + 'disk_over_commit']) + elif action == 'os-resetState': + assert list(body[action]) == ['state'] + elif action == 'resetNetwork': + assert body[action] is None + elif action == 'addSecurityGroup': + assert list(body[action]) == ['name'] + elif action == 'removeSecurityGroup': + assert list(body[action]) == ['name'] + elif action == 'createBackup': + assert set(body[action]) == set(['name', + 'backup_type', + 'rotation']) elif action == 'evacuate': keys = list(body[action]) if 'adminPass' in keys: diff --git a/awx/lib/site-packages/novaclient/tests/unit/test_base.py b/awx/lib/site-packages/novaclient/tests/unit/test_base.py index 1ba1d32b00..b7bceb7b86 100644 --- a/awx/lib/site-packages/novaclient/tests/unit/test_base.py +++ b/awx/lib/site-packages/novaclient/tests/unit/test_base.py @@ -36,7 +36,7 @@ class BaseTest(utils.TestCase): def test_resource_lazy_getattr(self): f = flavors.Flavor(cs.flavors, {'id': 1}) - self.assertEqual('256 mb server', f.name) + self.assertEqual('256 MB Server', f.name) cs.assert_called('GET', '/flavors/1') # Missing stuff still fails after a second get diff --git a/awx/lib/site-packages/novaclient/tests/unit/test_client.py b/awx/lib/site-packages/novaclient/tests/unit/test_client.py index d240321f7e..8ed7cb9f3f 100644 --- a/awx/lib/site-packages/novaclient/tests/unit/test_client.py +++ b/awx/lib/site-packages/novaclient/tests/unit/test_client.py @@ -16,7 +16,6 @@ import json import logging -import socket import fixtures import mock @@ -28,24 +27,6 @@ from novaclient.tests.unit import utils import novaclient.v2.client -class TCPKeepAliveAdapterTest(utils.TestCase): - - @mock.patch.object(requests.adapters.HTTPAdapter, 'init_poolmanager') - def test_init_poolmanager(self, mock_init_poolmgr): - adapter = novaclient.client.TCPKeepAliveAdapter() - kwargs = {} - adapter.init_poolmanager(**kwargs) - if requests.__version__ >= '2.4.1': - kwargs.setdefault('socket_options', [ - (socket.IPPROTO_TCP, socket.TCP_NODELAY, 1), - (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), - ]) - # NOTE(melwitt): This is called twice because - # HTTPAdapter.__init__ calls it first. - self.assertEqual(2, mock_init_poolmgr.call_count) - mock_init_poolmgr.assert_called_with(**kwargs) - - class ClientConnectionPoolTest(utils.TestCase): @mock.patch("novaclient.client.TCPKeepAliveAdapter") @@ -86,7 +67,6 @@ class ClientTest(utils.TestCase): auth_url="http://www.blah.com") instance.auth_token = 'foobar' instance.management_url = 'http://example.com' - instance.get_service_url = mock.Mock(return_value='http://example.com') instance.version = 'v2.0' mock_request = mock.Mock() mock_request.side_effect = novaclient.exceptions.Unauthorized(401) @@ -137,8 +117,6 @@ class ClientTest(utils.TestCase): auth_url="http://www.blah.com") instance.auth_token = 'foobar' instance.management_url = management_url % projectid - mock_get_service_url = mock.Mock(return_value=instance.management_url) - instance.get_service_url = mock_get_service_url instance.version = 'v2.0' # If passing None as the part of url, a client accesses the url which @@ -183,26 +161,26 @@ class ClientTest(utils.TestCase): def test_client_with_os_cache_enabled(self): cs = novaclient.v2.client.Client("user", "password", "project_id", auth_url="foo/v2", os_cache=True) - self.assertTrue(cs.os_cache) - self.assertTrue(cs.client.os_cache) + self.assertEqual(True, cs.os_cache) + self.assertEqual(True, cs.client.os_cache) def test_client_with_os_cache_disabled(self): cs = novaclient.v2.client.Client("user", "password", "project_id", auth_url="foo/v2", os_cache=False) - self.assertFalse(cs.os_cache) - self.assertFalse(cs.client.os_cache) + self.assertEqual(False, cs.os_cache) + self.assertEqual(False, cs.client.os_cache) def test_client_with_no_cache_enabled(self): cs = novaclient.v2.client.Client("user", "password", "project_id", auth_url="foo/v2", no_cache=True) - self.assertFalse(cs.os_cache) - self.assertFalse(cs.client.os_cache) + self.assertEqual(False, cs.os_cache) + self.assertEqual(False, cs.client.os_cache) def test_client_with_no_cache_disabled(self): cs = novaclient.v2.client.Client("user", "password", "project_id", auth_url="foo/v2", no_cache=False) - self.assertTrue(cs.os_cache) - self.assertTrue(cs.client.os_cache) + self.assertEqual(True, cs.os_cache) + self.assertEqual(True, cs.client.os_cache) def test_client_set_management_url_v1_1(self): cs = novaclient.v2.client.Client("user", "password", "project_id", diff --git a/awx/lib/site-packages/novaclient/tests/unit/test_http.py b/awx/lib/site-packages/novaclient/tests/unit/test_http.py index a80d94426f..5e676828a2 100644 --- a/awx/lib/site-packages/novaclient/tests/unit/test_http.py +++ b/awx/lib/site-packages/novaclient/tests/unit/test_http.py @@ -82,7 +82,6 @@ def get_authed_client(): cl = get_client() cl.management_url = "http://example.com" cl.auth_token = "token" - cl.get_service_url = mock.Mock(return_value="http://example.com") return cl diff --git a/awx/lib/site-packages/novaclient/tests/unit/test_utils.py b/awx/lib/site-packages/novaclient/tests/unit/test_utils.py index da68ce792b..2e72ef6591 100644 --- a/awx/lib/site-packages/novaclient/tests/unit/test_utils.py +++ b/awx/lib/site-packages/novaclient/tests/unit/test_utils.py @@ -311,7 +311,7 @@ class ValidationsTestCase(test_utils.TestCase): utils.validate_flavor_metadata_keys([key]) self.fail("Invalid key passed validation: %s" % key) except exceptions.CommandError as ce: - self.assertIn(key, str(ce)) + self.assertTrue(key in str(ce)) class ResourceManagerExtraKwargsHookTestCase(test_utils.TestCase): diff --git a/awx/lib/site-packages/novaclient/tests/unit/v2/fakes.py b/awx/lib/site-packages/novaclient/tests/unit/v2/fakes.py index 343c421e2e..55b5bad6fe 100644 --- a/awx/lib/site-packages/novaclient/tests/unit/v2/fakes.py +++ b/awx/lib/site-packages/novaclient/tests/unit/v2/fakes.py @@ -258,7 +258,7 @@ class FakeHTTPClient(base_client.HTTPClient): }, "flavor": { "id": 1, - "name": "256 mb server", + "name": "256 MB Server", }, "hostId": "e4d909c290d0fb1ca068ffaddf22cbd0", "status": "BUILD", @@ -299,7 +299,7 @@ class FakeHTTPClient(base_client.HTTPClient): }, "flavor": { "id": 1, - "name": "256 mb server", + "name": "256 MB Server", }, "hostId": "9e107d9d372bb6826bd81d3542a419d6", "status": "ACTIVE", @@ -340,7 +340,7 @@ class FakeHTTPClient(base_client.HTTPClient): "image": "", "flavor": { "id": 1, - "name": "256 mb server", + "name": "256 MB Server", }, "hostId": "9e107d9d372bb6826bd81d3542a419d6", "status": "ACTIVE", @@ -542,73 +542,48 @@ class FakeHTTPClient(base_client.HTTPClient): # Server actions # - none_actions = ['revertResize', 'migrate', 'os-stop', 'os-start', - 'forceDelete', 'restore', 'pause', 'unpause', 'unlock', - 'unrescue', 'resume', 'suspend', 'lock', 'shelve', - 'shelveOffload', 'unshelve', 'resetNetwork'] - type_actions = ['os-getVNCConsole', 'os-getSPICEConsole', - 'os-getRDPConsole'] - - @classmethod - def check_server_actions(cls, body): - action = list(body)[0] - if action == 'reboot': - assert list(body[action]) == ['type'] - assert body[action]['type'] in ['HARD', 'SOFT'] - elif action == 'resize': - assert 'flavorRef' in body[action] - elif action in cls.none_actions: - assert body[action] is None - elif action == 'addFixedIp': - assert list(body[action]) == ['networkId'] - elif action in ['removeFixedIp', 'removeFloatingIp']: - assert list(body[action]) == ['address'] - elif action == 'addFloatingIp': - assert (list(body[action]) == ['address'] or - sorted(list(body[action])) == ['address', 'fixed_address']) - elif action == 'changePassword': - assert list(body[action]) == ['adminPass'] - elif action in cls.type_actions: - assert list(body[action]) == ['type'] - elif action == 'os-migrateLive': - assert set(body[action].keys()) == set(['host', 'block_migration', - 'disk_over_commit']) - elif action == 'os-resetState': - assert list(body[action]) == ['state'] - elif action == 'resetNetwork': - assert body[action] is None - elif action in ['addSecurityGroup', 'removeSecurityGroup']: - assert list(body[action]) == ['name'] - elif action == 'createBackup': - assert set(body[action]) == set(['name', 'backup_type', - 'rotation']) - else: - return False - return True - def post_servers_1234_action(self, body, **kw): _headers = None _body = None resp = 202 assert len(body.keys()) == 1 action = list(body)[0] - - if self.check_server_actions(body): - # NOTE(snikitin): No need to do any operations here. This 'pass' - # is needed to avoid AssertionError in the last 'else' statement - # if we found 'action' in method check_server_actions and - # raise AssertionError if we didn't find 'action' at all. - pass + if action == 'reboot': + assert list(body[action]) == ['type'] + assert body[action]['type'] in ['HARD', 'SOFT'] elif action == 'rebuild': body = body[action] adminPass = body.get('adminPass', 'randompassword') assert 'imageRef' in body _body = self.get_servers_1234()[2] _body['server']['adminPass'] = adminPass + elif action == 'resize': + keys = body[action].keys() + assert 'flavorRef' in keys elif action == 'confirmResize': assert body[action] is None # This one method returns a different response code return (204, {}, None) + elif action == 'revertResize': + assert body[action] is None + elif action == 'migrate': + assert body[action] is None + elif action == 'os-stop': + assert body[action] is None + elif action == 'os-start': + assert body[action] is None + elif action == 'forceDelete': + assert body[action] is None + elif action == 'restore': + assert body[action] is None + elif action == 'pause': + assert body[action] is None + elif action == 'unpause': + assert body[action] is None + elif action == 'lock': + assert body[action] is None + elif action == 'unlock': + assert body[action] is None elif action == 'rescue': if body[action]: keys = set(body[action].keys()) @@ -616,12 +591,62 @@ class FakeHTTPClient(base_client.HTTPClient): else: assert body[action] is None _body = {'adminPass': 'RescuePassword'} + elif action == 'unrescue': + assert body[action] is None + elif action == 'resume': + assert body[action] is None + elif action == 'suspend': + assert body[action] is None + elif action == 'lock': + assert body[action] is None + elif action == 'unlock': + assert body[action] is None + elif action == 'shelve': + assert body[action] is None + elif action == 'shelveOffload': + assert body[action] is None + elif action == 'unshelve': + assert body[action] is None + elif action == 'addFixedIp': + assert list(body[action]) == ['networkId'] + elif action == 'removeFixedIp': + assert list(body[action]) == ['address'] + elif action == 'addFloatingIp': + assert (list(body[action]) == ['address'] or + sorted(list(body[action])) == ['address', + 'fixed_address']) + elif action == 'removeFloatingIp': + assert list(body[action]) == ['address'] elif action == 'createImage': assert set(body[action].keys()) == set(['name', 'metadata']) _headers = dict(location="http://blah/images/456") + elif action == 'changePassword': + assert list(body[action]) == ['adminPass'] elif action == 'os-getConsoleOutput': assert list(body[action]) == ['length'] return (202, {}, {'output': 'foo'}) + elif action == 'os-getVNCConsole': + assert list(body[action]) == ['type'] + elif action == 'os-getSPICEConsole': + assert list(body[action]) == ['type'] + elif action == 'os-getRDPConsole': + assert list(body[action]) == ['type'] + elif action == 'os-migrateLive': + assert set(body[action].keys()) == set(['host', + 'block_migration', + 'disk_over_commit']) + elif action == 'os-resetState': + assert list(body[action]) == ['state'] + elif action == 'resetNetwork': + assert body[action] is None + elif action == 'addSecurityGroup': + assert list(body[action]) == ['name'] + elif action == 'removeSecurityGroup': + assert list(body[action]) == ['name'] + elif action == 'createBackup': + assert set(body[action]) == set(['name', + 'backup_type', + 'rotation']) elif action == 'evacuate': keys = list(body[action]) if 'adminPass' in keys: @@ -672,19 +697,19 @@ class FakeHTTPClient(base_client.HTTPClient): def get_flavors_detail(self, **kw): flavors = {'flavors': [ - {'id': 1, 'name': '256 mb server', 'ram': 256, 'disk': 10, + {'id': 1, 'name': '256 MB Server', 'ram': 256, 'disk': 10, 'OS-FLV-EXT-DATA:ephemeral': 10, 'os-flavor-access:is_public': True, 'links': {}}, - {'id': 2, 'name': '512 mb server', 'ram': 512, 'disk': 20, + {'id': 2, 'name': '512 MB Server', 'ram': 512, 'disk': 20, 'OS-FLV-EXT-DATA:ephemeral': 20, 'os-flavor-access:is_public': False, 'links': {}}, - {'id': 4, 'name': '1024 mb server', 'ram': 1024, 'disk': 10, + {'id': 4, 'name': '1024 MB Server', 'ram': 1024, 'disk': 10, 'OS-FLV-EXT-DATA:ephemeral': 10, 'os-flavor-access:is_public': True, 'links': {}}, - {'id': 'aa1', 'name': '128 mb server', 'ram': 128, 'disk': 0, + {'id': 'aa1', 'name': '128 MB Server', 'ram': 128, 'disk': 0, 'OS-FLV-EXT-DATA:ephemeral': 0, 'os-flavor-access:is_public': True, 'links': {}} @@ -736,16 +761,16 @@ class FakeHTTPClient(base_client.HTTPClient): {}, {'flavor': { 'id': 3, - 'name': '256 mb server', + 'name': '256 MB Server', 'ram': 256, 'disk': 10, }}, ) - def get_flavors_512_mb_server(self, **kw): + def get_flavors_512_MB_Server(self, **kw): raise exceptions.NotFound('404') - def get_flavors_128_mb_server(self, **kw): + def get_flavors_128_MB_Server(self, **kw): raise exceptions.NotFound('404') def get_flavors_aa1(self, **kw): @@ -2196,7 +2221,6 @@ class FakeSessionMockClient(base_client.SessionClient, FakeHTTPClient): self.callstack = [] self.auth = mock.Mock() self.session = mock.Mock() - self.service_type = 'service_type' self.auth.get_auth_ref.return_value.project_id = 'tenant_id' diff --git a/awx/lib/site-packages/novaclient/tests/unit/v2/test_auth.py b/awx/lib/site-packages/novaclient/tests/unit/v2/test_auth.py index 696428a4a9..95e96ded19 100644 --- a/awx/lib/site-packages/novaclient/tests/unit/v2/test_auth.py +++ b/awx/lib/site-packages/novaclient/tests/unit/v2/test_auth.py @@ -363,7 +363,6 @@ class AuthenticationTests(utils.TestCase): "project_id", utils.AUTH_URL) http_client = cs.client http_client.management_url = '' - http_client.get_service_url = mock.Mock(return_value='') mock_request = mock.Mock(return_value=(None, None)) @mock.patch.object(http_client, 'request', mock_request) diff --git a/awx/lib/site-packages/novaclient/tests/unit/v2/test_flavors.py b/awx/lib/site-packages/novaclient/tests/unit/v2/test_flavors.py index 504590277c..27b77c068f 100644 --- a/awx/lib/site-packages/novaclient/tests/unit/v2/test_flavors.py +++ b/awx/lib/site-packages/novaclient/tests/unit/v2/test_flavors.py @@ -70,7 +70,7 @@ class FlavorsTest(utils.TestCase): self.assertEqual(256, f.ram) self.assertEqual(10, f.disk) self.assertEqual(10, f.ephemeral) - self.assertTrue(f.is_public) + self.assertEqual(True, f.is_public) def test_get_flavor_details_alphanum_id(self): f = self.cs.flavors.get('aa1') @@ -79,7 +79,7 @@ class FlavorsTest(utils.TestCase): self.assertEqual(128, f.ram) self.assertEqual(0, f.disk) self.assertEqual(0, f.ephemeral) - self.assertTrue(f.is_public) + self.assertEqual(True, f.is_public) def test_get_flavor_details_diablo(self): f = self.cs.flavors.get(3) @@ -93,10 +93,10 @@ class FlavorsTest(utils.TestCase): def test_find(self): f = self.cs.flavors.find(ram=256) self.cs.assert_called('GET', '/flavors/detail') - self.assertEqual('256 mb server', f.name) + self.assertEqual('256 MB Server', f.name) f = self.cs.flavors.find(disk=0) - self.assertEqual('128 mb server', f.name) + self.assertEqual('128 MB Server', f.name) self.assertRaises(exceptions.NotFound, self.cs.flavors.find, disk=12345) diff --git a/awx/lib/site-packages/novaclient/tests/unit/v2/test_fping.py b/awx/lib/site-packages/novaclient/tests/unit/v2/test_fping.py index 0dd7cd30d7..b9ecc39911 100644 --- a/awx/lib/site-packages/novaclient/tests/unit/v2/test_fping.py +++ b/awx/lib/site-packages/novaclient/tests/unit/v2/test_fping.py @@ -34,7 +34,7 @@ class FpingTest(utils.FixturedTestCase): for f in fl: self.assertIsInstance(f, fping.Fping) self.assertEqual("fake-project", f.project_id) - self.assertTrue(f.alive) + self.assertEqual(True, f.alive) def test_list_fpings_all_tenants(self): fl = self.cs.fping.list(all_tenants=True) @@ -59,4 +59,4 @@ class FpingTest(utils.FixturedTestCase): self.assert_called('GET', '/os-fping/1') self.assertIsInstance(f, fping.Fping) self.assertEqual("fake-project", f.project_id) - self.assertTrue(f.alive) + self.assertEqual(True, f.alive) diff --git a/awx/lib/site-packages/novaclient/tests/unit/v2/test_images.py b/awx/lib/site-packages/novaclient/tests/unit/v2/test_images.py index 0cd110fb53..1f8104edbb 100644 --- a/awx/lib/site-packages/novaclient/tests/unit/v2/test_images.py +++ b/awx/lib/site-packages/novaclient/tests/unit/v2/test_images.py @@ -26,7 +26,6 @@ class ImagesTest(utils.FixturedTestCase): il = self.cs.images.list() self.assert_called('GET', '/images/detail') [self.assertIsInstance(i, images.Image) for i in il] - self.assertEqual(2, len(il)) def test_list_images_undetailed(self): il = self.cs.images.list(detailed=False) diff --git a/awx/lib/site-packages/novaclient/tests/unit/v2/test_limits.py b/awx/lib/site-packages/novaclient/tests/unit/v2/test_limits.py index 9dc9dec436..94a62c5c0c 100644 --- a/awx/lib/site-packages/novaclient/tests/unit/v2/test_limits.py +++ b/awx/lib/site-packages/novaclient/tests/unit/v2/test_limits.py @@ -47,7 +47,7 @@ class LimitsTest(utils.FixturedTestCase): self.assertEqual(len(abs_limits), len(expected)) for limit in abs_limits: - self.assertIn(limit, expected) + self.assertTrue(limit in expected) def test_absolute_limits_reserved(self): obj = self.cs.limits.get(reserved=True) @@ -65,7 +65,7 @@ class LimitsTest(utils.FixturedTestCase): self.assertEqual(len(abs_limits), len(expected)) for limit in abs_limits: - self.assertIn(limit, expected) + self.assertTrue(limit in expected) def test_rate_limits(self): obj = self.cs.limits.get() @@ -85,4 +85,4 @@ class LimitsTest(utils.FixturedTestCase): self.assertEqual(len(rate_limits), len(expected)) for limit in rate_limits: - self.assertIn(limit, expected) + self.assertTrue(limit in expected) diff --git a/awx/lib/site-packages/novaclient/tests/unit/v2/test_servers.py b/awx/lib/site-packages/novaclient/tests/unit/v2/test_servers.py index c43aed422b..c9b15f30a9 100644 --- a/awx/lib/site-packages/novaclient/tests/unit/v2/test_servers.py +++ b/awx/lib/site-packages/novaclient/tests/unit/v2/test_servers.py @@ -212,7 +212,7 @@ class ServersTest(utils.FixturedTestCase): # verify disk config param was used in the request: body = jsonutils.loads(self.requests.last_request.body) server = body['server'] - self.assertIn('OS-DCF:diskConfig', server) + self.assertTrue('OS-DCF:diskConfig' in server) self.assertEqual(disk_config, server['OS-DCF:diskConfig']) def test_create_server_disk_config_auto(self): @@ -302,7 +302,7 @@ class ServersTest(utils.FixturedTestCase): body = jsonutils.loads(self.requests.last_request.body) d = body[operation] - self.assertIn('OS-DCF:diskConfig', d) + self.assertTrue('OS-DCF:diskConfig' in d) self.assertEqual(disk_config, d['OS-DCF:diskConfig']) def test_rebuild_server_disk_config_auto(self): @@ -318,7 +318,7 @@ class ServersTest(utils.FixturedTestCase): body = jsonutils.loads(self.requests.last_request.body) d = body['rebuild'] self.assertIn('preserve_ephemeral', d) - self.assertTrue(d['preserve_ephemeral']) + self.assertEqual(True, d['preserve_ephemeral']) def test_rebuild_server_name_meta_files(self): files = {'/etc/passwd': 'some data'} @@ -560,11 +560,11 @@ class ServersTest(utils.FixturedTestCase): def test_get_server_diagnostics(self): s = self.cs.servers.get(1234) diagnostics = s.diagnostics() - self.assertIsNotNone(diagnostics) + self.assertTrue(diagnostics is not None) self.assert_called('GET', '/servers/1234/diagnostics') diagnostics_from_manager = self.cs.servers.diagnostics(1234) - self.assertIsNotNone(diagnostics_from_manager) + self.assertTrue(diagnostics_from_manager is not None) self.assert_called('GET', '/servers/1234/diagnostics') self.assertEqual(diagnostics[1], diagnostics_from_manager[1]) diff --git a/awx/lib/site-packages/novaclient/tests/unit/v2/test_shell.py b/awx/lib/site-packages/novaclient/tests/unit/v2/test_shell.py index b7e8b9e9d4..71eb85ee8b 100644 --- a/awx/lib/site-packages/novaclient/tests/unit/v2/test_shell.py +++ b/awx/lib/site-packages/novaclient/tests/unit/v2/test_shell.py @@ -661,10 +661,10 @@ class ShellTest(utils.TestCase): def test_boot_named_flavor(self): self.run_command(["boot", "--image", "1", - "--flavor", "512 mb server", + "--flavor", "512 MB Server", "--max-count", "3", "server"]) self.assert_called('GET', '/images/1', pos=0) - self.assert_called('GET', '/flavors/512 mb server', pos=1) + self.assert_called('GET', '/flavors/512 MB Server', pos=1) self.assert_called('GET', '/flavors?is_public=None', pos=2) self.assert_called('GET', '/flavors/2', pos=3) self.assert_called( @@ -701,15 +701,15 @@ class ShellTest(utils.TestCase): self.assert_called_anytime('GET', '/flavors/aa1') def test_flavor_show_by_name(self): - self.run_command(['flavor-show', '128 mb server']) - self.assert_called('GET', '/flavors/128 mb server', pos=0) + self.run_command(['flavor-show', '128 MB Server']) + self.assert_called('GET', '/flavors/128 MB Server', pos=0) self.assert_called('GET', '/flavors?is_public=None', pos=1) self.assert_called('GET', '/flavors/aa1', pos=2) self.assert_called('GET', '/flavors/aa1/os-extra_specs', pos=3) def test_flavor_show_by_name_priv(self): - self.run_command(['flavor-show', '512 mb server']) - self.assert_called('GET', '/flavors/512 mb server', pos=0) + self.run_command(['flavor-show', '512 MB Server']) + self.assert_called('GET', '/flavors/512 MB Server', pos=0) self.assert_called('GET', '/flavors?is_public=None', pos=1) self.assert_called('GET', '/flavors/2', pos=2) self.assert_called('GET', '/flavors/2/os-extra_specs', pos=3) @@ -746,7 +746,7 @@ class ShellTest(utils.TestCase): {'addTenantAccess': {'tenant': 'proj2'}}) def test_flavor_access_add_by_name(self): - self.run_command(['flavor-access-add', '512 mb server', 'proj2']) + self.run_command(['flavor-access-add', '512 MB Server', 'proj2']) self.assert_called('POST', '/flavors/2/action', {'addTenantAccess': {'tenant': 'proj2'}}) @@ -756,7 +756,7 @@ class ShellTest(utils.TestCase): {'removeTenantAccess': {'tenant': 'proj2'}}) def test_flavor_access_remove_by_name(self): - self.run_command(['flavor-access-remove', '512 mb server', 'proj2']) + self.run_command(['flavor-access-remove', '512 MB Server', 'proj2']) self.assert_called('POST', '/flavors/2/action', {'removeTenantAccess': {'tenant': 'proj2'}}) @@ -2145,7 +2145,7 @@ class ShellTest(utils.TestCase): def test_volume_show(self): self.run_command('volume-show Work') - self.assert_called('GET', '/volumes?display_name=Work', pos=-2) + self.assert_called('GET', '/volumes?name=Work', pos=-2) self.assert_called( 'GET', '/volumes/15e59938-07d5-11e1-90e3-e3dffe0c5983', diff --git a/awx/lib/site-packages/novaclient/tests/utils.py b/awx/lib/site-packages/novaclient/tests/utils.py index be251998cd..036702ec5a 100644 --- a/awx/lib/site-packages/novaclient/tests/utils.py +++ b/awx/lib/site-packages/novaclient/tests/utils.py @@ -14,8 +14,8 @@ import os import fixtures -import httpretty import requests +from requests_mock.contrib import fixture as requests_mock_fixture import six import testscenarios import testtools @@ -52,24 +52,26 @@ class FixturedTestCase(testscenarios.TestWithScenarios, TestCase): def setUp(self): super(FixturedTestCase, self).setUp() - httpretty.reset() + self.requests = self.useFixture(requests_mock_fixture.Fixture()) self.data_fixture = None self.client_fixture = None self.cs = None if self.client_fixture_class: - self.client_fixture = self.useFixture(self.client_fixture_class()) + fix = self.client_fixture_class(self.requests) + self.client_fixture = self.useFixture(fix) self.cs = self.client_fixture.client if self.data_fixture_class: - self.data_fixture = self.useFixture(self.data_fixture_class()) + fix = self.data_fixture_class(self.requests) + self.data_fixture = self.useFixture(fix) def assert_called(self, method, path, body=None): - self.assertEqual(httpretty.last_request().method, method) - self.assertEqual(httpretty.last_request().path, path) + self.assertEqual(self.requests.last_request.method, method) + self.assertEqual(self.requests.last_request.path_url, path) if body: - req_data = httpretty.last_request().body + req_data = self.requests.last_request.body if isinstance(req_data, six.binary_type): req_data = req_data.decode('utf-8') if not isinstance(body, six.string_types): diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/contrib/test_baremetal.py b/awx/lib/site-packages/novaclient/tests/v1_1/contrib/test_baremetal.py index 6e2399f243..6a88d580c1 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/contrib/test_baremetal.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/contrib/test_baremetal.py @@ -15,10 +15,9 @@ from novaclient import extension -from novaclient.v1_1.contrib import baremetal - from novaclient.tests import utils from novaclient.tests.v1_1.contrib import fakes +from novaclient.v1_1.contrib import baremetal extensions = [ diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/contrib/test_instance_actions.py b/awx/lib/site-packages/novaclient/tests/v1_1/contrib/test_instance_actions.py index cdfa14985f..baaa1d0401 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/contrib/test_instance_actions.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/contrib/test_instance_actions.py @@ -12,11 +12,11 @@ # 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 novaclient import extension -from novaclient.v1_1.contrib import instance_action +from novaclient import extension from novaclient.tests import utils from novaclient.tests.v1_1.contrib import fakes +from novaclient.v1_1.contrib import instance_action extensions = [ diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/contrib/test_list_extensions.py b/awx/lib/site-packages/novaclient/tests/v1_1/contrib/test_list_extensions.py index 2d4426fc03..77b6a3420a 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/contrib/test_list_extensions.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/contrib/test_list_extensions.py @@ -12,10 +12,9 @@ # under the License. from novaclient import extension -from novaclient.v1_1.contrib import list_extensions - from novaclient.tests import utils from novaclient.tests.v1_1 import fakes +from novaclient.v1_1.contrib import list_extensions extensions = [ diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/contrib/test_tenant_networks.py b/awx/lib/site-packages/novaclient/tests/v1_1/contrib/test_tenant_networks.py index f2a077005f..1289928b8a 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/contrib/test_tenant_networks.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/contrib/test_tenant_networks.py @@ -14,10 +14,9 @@ # under the License. from novaclient import extension -from novaclient.v1_1.contrib import tenant_networks - from novaclient.tests import utils from novaclient.tests.v1_1.contrib import fakes +from novaclient.v1_1.contrib import tenant_networks extensions = [ diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/fakes.py b/awx/lib/site-packages/novaclient/tests/v1_1/fakes.py index f61a02e928..3e82eb053f 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/fakes.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/fakes.py @@ -14,14 +14,14 @@ # See the License for the specific language governing permissions and # limitations under the License. -from datetime import datetime +import datetime +from oslo.utils import strutils import six from six.moves.urllib import parse from novaclient import client as base_client from novaclient import exceptions -from novaclient.openstack.common import strutils from novaclient.tests import fakes from novaclient.tests import utils from novaclient.v1_1 import client @@ -636,7 +636,9 @@ class FakeHTTPClient(base_client.HTTPClient): keys = list(body[action]) if 'adminPass' in keys: keys.remove('adminPass') - assert set(keys) == set(['host', 'onSharedStorage']) + if 'host' in keys: + keys.remove('host') + assert set(keys) == set(['onSharedStorage']) else: raise AssertionError("Unexpected server action: %s" % action) return (resp, _headers, _body) @@ -710,14 +712,14 @@ class FakeHTTPClient(base_client.HTTPClient): if filter_is_public is not None: if filter_is_public: flavors['flavors'] = [ - v for v in flavors['flavors'] - if v['os-flavor-access:is_public'] - ] + v for v in flavors['flavors'] + if v['os-flavor-access:is_public'] + ] else: flavors['flavors'] = [ - v for v in flavors['flavors'] - if not v['os-flavor-access:is_public'] - ] + v for v in flavors['flavors'] + if not v['os-flavor-access:is_public'] + ] return (200, {}, flavors) @@ -1311,7 +1313,7 @@ class FakeHTTPClient(base_client.HTTPClient): def get_os_security_group_rules(self, **kw): return (200, {}, {"security_group_rules": [ {'id': 1, 'parent_group_id': 1, 'group_id': 2, - 'ip_protocol': 'TCP', 'from_port': '22', 'to_port': 22, + 'ip_protocol': 'TCP', 'from_port': 22, 'to_port': 22, 'cidr': '10.0.0.0/8'} ]}) @@ -1334,6 +1336,34 @@ class FakeHTTPClient(base_client.HTTPClient): self.get_os_security_group_rules()[2]['security_group_rules'][0]} return (202, {}, r) + # + # Security Group Default Rules + # + def get_os_security_group_default_rules(self, **kw): + return (200, {}, {"security_group_default_rules": [ + {'id': 1, 'ip_protocol': 'TCP', 'from_port': 22, + 'to_port': 22, 'cidr': '10.0.0.0/8'} + ]}) + + def delete_os_security_group_default_rules_1(self, **kw): + return (202, {}, None) + + def delete_os_security_group_default_rules_11(self, **kw): + return (202, {}, None) + + def delete_os_security_group_default_rules_12(self, **kw): + return (202, {}, None) + + def post_os_security_group_default_rules(self, body, **kw): + assert list(body) == ['security_group_default_rule'] + fakes.assert_has_keys(body['security_group_default_rule'], + optional=['ip_protocol', 'from_port', + 'to_port', 'cidr']) + rules = self.get_os_security_group_default_rules() + r = {'security_group_default_rule': + rules[2]['security_group_default_rules'][0]} + return (202, {}, r) + # # Tenant Usage # @@ -1344,7 +1374,7 @@ class FakeHTTPClient(base_client.HTTPClient): six.u('total_vcpus_usage'): 49.71047423333333, six.u('total_hours'): 49.71047423333333, six.u('tenant_id'): - six.u('7b0a1d73f8fb41718f3343c207597869'), + six.u('7b0a1d73f8fb41718f3343c207597869'), six.u('stop'): six.u('2012-01-22 19:48:41.750722'), six.u('server_usages'): [{ six.u('hours'): 49.71047423333333, @@ -1353,7 +1383,7 @@ class FakeHTTPClient(base_client.HTTPClient): six.u('ended_at'): None, six.u('name'): six.u('f15image1'), six.u('tenant_id'): - six.u('7b0a1d73f8fb41718f3343c207597869'), + six.u('7b0a1d73f8fb41718f3343c207597869'), six.u('vcpus'): 1, six.u('memory_mb'): 512, six.u('state'): six.u('active'), @@ -1370,7 +1400,7 @@ class FakeHTTPClient(base_client.HTTPClient): six.u('total_vcpus_usage'): 49.71047423333333, six.u('total_hours'): 49.71047423333333, six.u('tenant_id'): - six.u('7b0a1d73f8fb41718f3343c207597869'), + six.u('7b0a1d73f8fb41718f3343c207597869'), six.u('stop'): six.u('2012-01-22 19:48:41.750722'), six.u('server_usages'): [{ six.u('hours'): 49.71047423333333, @@ -1456,15 +1486,25 @@ class FakeHTTPClient(base_client.HTTPClient): {'id': '2', 'name': 'test2', 'availability_zone': 'nova1'}, + {'id': '3', + 'name': 'test3', + 'metadata': {'test': "dup", "none_key": "Nine"}}, ]}) def _return_aggregate(self): r = {'aggregate': self.get_os_aggregates()[2]['aggregates'][0]} return (200, {}, r) + def _return_aggregate_3(self): + r = {'aggregate': self.get_os_aggregates()[2]['aggregates'][2]} + return (200, {}, r) + def get_os_aggregates_1(self, **kw): return self._return_aggregate() + def get_os_aggregates_3(self, **kw): + return self._return_aggregate_3() + def post_os_aggregates(self, body, **kw): return self._return_aggregate() @@ -1474,12 +1514,18 @@ class FakeHTTPClient(base_client.HTTPClient): def put_os_aggregates_2(self, body, **kw): return self._return_aggregate() + def put_os_aggregates_3(self, body, **kw): + return self._return_aggregate_3() + def post_os_aggregates_1_action(self, body, **kw): return self._return_aggregate() def post_os_aggregates_2_action(self, body, **kw): return self._return_aggregate() + def post_os_aggregates_3_action(self, body, **kw): + return self._return_aggregate_3() + def delete_os_aggregates_1(self, **kw): return (202, {}, None) @@ -1495,13 +1541,17 @@ class FakeHTTPClient(base_client.HTTPClient): 'zone': 'nova', 'status': 'enabled', 'state': 'up', - 'updated_at': datetime(2012, 10, 29, 13, 42, 2)}, + 'updated_at': datetime.datetime( + 2012, 10, 29, 13, 42, 2 + )}, {'binary': binary, 'host': host, 'zone': 'nova', 'status': 'disabled', 'state': 'down', - 'updated_at': datetime(2012, 9, 18, 8, 3, 38)}, + 'updated_at': datetime.datetime( + 2012, 9, 18, 8, 3, 38 + )}, ]}) def put_os_services_enable(self, body, **kw): @@ -1708,13 +1758,17 @@ class FakeHTTPClient(base_client.HTTPClient): def get_os_networks(self, **kw): return (200, {}, {'networks': [{"label": "1", "cidr": "10.0.0.0/24", 'project_id': '4ffc664c198e435e9853f2538fbcd7a7', - 'id': '1'}]}) + 'id': '1', 'vlan': '1234'}]}) + + def delete_os_networks_1(self, **kw): + return (202, {}, None) def post_os_networks(self, **kw): return (202, {}, {'network': kw}) def get_os_networks_1(self, **kw): - return (200, {}, {'network': {"label": "1", "cidr": "10.0.0.0/24"}}) + return (200, {}, {'network': {"label": "1", "cidr": "10.0.0.0/24", + "id": "1"}}) def delete_os_networks_networkdelete(self, **kw): return (202, {}, None) @@ -1781,7 +1835,9 @@ class FakeHTTPClient(base_client.HTTPClient): "nova-compute": {"active": True, "available": True, "updated_at": - datetime(2012, 12, 26, 14, 45, 25, 0)}}}}, + datetime.datetime( + 2012, 12, 26, 14, 45, 25, 0 + )}}}}, {"zoneName": "internal", "zoneState": {"available": True}, "hosts": { @@ -1790,13 +1846,17 @@ class FakeHTTPClient(base_client.HTTPClient): "active": True, "available": True, "updated_at": - datetime(2012, 12, 26, 14, 45, 25, 0)}}, + datetime.datetime( + 2012, 12, 26, 14, 45, 25, 0 + )}}, "fake_host-2": { "nova-network": { "active": True, "available": False, "updated_at": - datetime(2012, 12, 26, 14, 45, 24, 0)}}}}, + datetime.datetime( + 2012, 12, 26, 14, 45, 24, 0 + )}}}}, {"zoneName": "zone-2", "zoneState": {"available": False}, "hosts": None}]}) diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/test_agents.py b/awx/lib/site-packages/novaclient/tests/v1_1/test_agents.py index 46efe839a1..1a580f5f19 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/test_agents.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/test_agents.py @@ -13,9 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import httpretty - -from novaclient.openstack.common import jsonutils from novaclient.tests.fixture_data import agents as data from novaclient.tests.fixture_data import client from novaclient.tests import utils @@ -53,9 +50,10 @@ class AgentsTest(utils.FixturedTestCase): ] } - httpretty.register_uri(httpretty.GET, self.data_fixture.url(), - body=jsonutils.dumps(get_os_agents), - content_type='application/json') + headers = {'Content-Type': 'application/json'} + self.requests.register_uri('GET', self.data_fixture.url(), + json=get_os_agents, + headers=headers) def test_list_agents(self): self.stub_hypervisors() @@ -63,7 +61,7 @@ class AgentsTest(utils.FixturedTestCase): self.assert_called('GET', '/os-agents') for a in ags: self.assertIsInstance(a, agents.Agent) - self.assertEqual(a.hypervisor, 'kvm') + self.assertEqual('kvm', a.hypervisor) def test_list_agents_with_hypervisor(self): self.stub_hypervisors('xen') @@ -71,7 +69,7 @@ class AgentsTest(utils.FixturedTestCase): self.assert_called('GET', '/os-agents?hypervisor=xen') for a in ags: self.assertIsInstance(a, agents.Agent) - self.assertEqual(a.hypervisor, 'xen') + self.assertEqual('xen', a.hypervisor) def test_agents_create(self): ag = self.cs.agents.create('win', 'x86', '7.0', diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/test_auth.py b/awx/lib/site-packages/novaclient/tests/v1_1/test_auth.py index c3df530b40..6355b3245d 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/test_auth.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/test_auth.py @@ -14,6 +14,7 @@ import copy import json +from keystoneclient import fixture import mock import requests @@ -23,33 +24,21 @@ from novaclient.v1_1 import client class AuthenticateAgainstKeystoneTests(utils.TestCase): + + def get_token(self, **kwargs): + resp = fixture.V2Token(**kwargs) + resp.set_scope() + + s = resp.add_service('compute') + s.add_endpoint('http://localhost:8774/v1.1', region='RegionOne') + + return resp + def test_authenticate_success(self): cs = client.Client("username", "password", "project_id", utils.AUTH_URL_V2, service_type='compute') - resp = { - "access": { - "token": { - "expires": "12345", - "id": "FAKE_ID", - "tenant": { - "id": "FAKE_TENANT_ID", - } - }, - "serviceCatalog": [ - { - "type": "compute", - "endpoints": [ - { - "region": "RegionOne", - "adminURL": "http://localhost:8774/v1.1", - "internalURL": "http://localhost:8774/v1.1", - "publicURL": "http://localhost:8774/v1.1/", - }, - ], - }, - ], - }, - } + resp = self.get_token() + auth_response = utils.TestResponse({ "status_code": 200, "text": json.dumps(resp), @@ -112,30 +101,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): def test_v1_auth_redirect(self): cs = client.Client("username", "password", "project_id", utils.AUTH_URL_V1, service_type='compute') - dict_correct_response = { - "access": { - "token": { - "expires": "12345", - "id": "FAKE_ID", - "tenant": { - "id": "FAKE_TENANT_ID", - } - }, - "serviceCatalog": [ - { - "type": "compute", - "endpoints": [ - { - "adminURL": "http://localhost:8774/v1.1", - "region": "RegionOne", - "internalURL": "http://localhost:8774/v1.1", - "publicURL": "http://localhost:8774/v1.1/", - }, - ], - }, - ], - }, - } + dict_correct_response = self.get_token() correct_response = json.dumps(dict_correct_response) dict_responses = [ {"headers": {'location': 'http://127.0.0.1:5001'}, @@ -173,9 +139,9 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): 'passwordCredentials': { 'username': cs.client.user, 'password': cs.client.password, - }, - 'tenantName': cs.client.projectid, - }, + }, + 'tenantName': cs.client.projectid, + }, } token_url = cs.client.auth_url + "/tokens" @@ -200,30 +166,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): def test_v2_auth_redirect(self): cs = client.Client("username", "password", "project_id", utils.AUTH_URL_V2, service_type='compute') - dict_correct_response = { - "access": { - "token": { - "expires": "12345", - "id": "FAKE_ID", - "tenant": { - "id": "FAKE_TENANT_ID", - } - }, - "serviceCatalog": [ - { - "type": "compute", - "endpoints": [ - { - "adminURL": "http://localhost:8774/v1.1", - "region": "RegionOne", - "internalURL": "http://localhost:8774/v1.1", - "publicURL": "http://localhost:8774/v1.1/", - }, - ], - }, - ], - }, - } + dict_correct_response = self.get_token() correct_response = json.dumps(dict_correct_response) dict_responses = [ {"headers": {'location': 'http://127.0.0.1:5001'}, @@ -261,9 +204,9 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): 'passwordCredentials': { 'username': cs.client.user, 'password': cs.client.password, - }, - 'tenantName': cs.client.projectid, - }, + }, + 'tenantName': cs.client.projectid, + }, } token_url = cs.client.auth_url + "/tokens" @@ -288,42 +231,12 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): def test_ambiguous_endpoints(self): cs = client.Client("username", "password", "project_id", utils.AUTH_URL_V2, service_type='compute') - resp = { - "access": { - "token": { - "expires": "12345", - "id": "FAKE_ID", - "tenant": { - "id": "FAKE_TENANT_ID", - } - }, - "serviceCatalog": [ - { - "adminURL": "http://localhost:8774/v1.1", - "type": "compute", - "name": "Compute CLoud", - "endpoints": [ - { - "region": "RegionOne", - "internalURL": "http://localhost:8774/v1.1", - "publicURL": "http://localhost:8774/v1.1/", - }, - ], - }, - { - "adminURL": "http://localhost:8774/v1.1", - "type": "compute", - "name": "Hyper-compute Cloud", - "endpoints": [ - { - "internalURL": "http://localhost:8774/v1.1", - "publicURL": "http://localhost:8774/v1.1/", - }, - ], - }, - ], - }, - } + resp = self.get_token() + + # duplicate existing service + s = resp.add_service('compute') + s.add_endpoint('http://localhost:8774/v1.1', region='RegionOne') + auth_response = utils.TestResponse({ "status_code": 200, "text": json.dumps(resp), @@ -342,30 +255,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase): cs = client.Client("username", None, "project_id", utils.AUTH_URL_V2, service_type='compute') cs.client.auth_token = "FAKE_ID" - resp = { - "access": { - "token": { - "expires": "12345", - "id": "FAKE_ID", - "tenant": { - "id": "FAKE_TENANT_ID", - } - }, - "serviceCatalog": [ - { - "type": "compute", - "endpoints": [ - { - "region": "RegionOne", - "adminURL": "http://localhost:8774/v1.1", - "internalURL": "http://localhost:8774/v1.1", - "publicURL": "http://localhost:8774/v1.1/", - }, - ], - }, - ], - }, - } + resp = self.get_token(token_id="FAKE_ID") auth_response = utils.TestResponse({ "status_code": 200, "text": json.dumps(resp), diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/test_availability_zone.py b/awx/lib/site-packages/novaclient/tests/v1_1/test_availability_zone.py index 2144d8c19d..6851d9aeac 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/test_availability_zone.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/test_availability_zone.py @@ -58,7 +58,7 @@ class AvailabilityZoneTest(utils.FixturedTestCase): z0 = self.shell._treeizeAvailabilityZone(zones[0]) z1 = self.shell._treeizeAvailabilityZone(zones[1]) - self.assertEqual((len(z0), len(z1)), (1, 1)) + self.assertEqual((1, 1), (len(z0), len(z1))) self._assertZone(z0[0], l0[0], l0[1]) self._assertZone(z1[0], l1[0], l1[1]) @@ -89,7 +89,7 @@ class AvailabilityZoneTest(utils.FixturedTestCase): z1 = self.shell._treeizeAvailabilityZone(zones[1]) z2 = self.shell._treeizeAvailabilityZone(zones[2]) - self.assertEqual((len(z0), len(z1), len(z2)), (3, 5, 1)) + self.assertEqual((3, 5, 1), (len(z0), len(z1), len(z2))) self._assertZone(z0[0], l0[0], l0[1]) self._assertZone(z0[1], l1[0], l1[1]) diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/test_fixed_ips.py b/awx/lib/site-packages/novaclient/tests/v1_1/test_fixed_ips.py index a081a8ecc9..5cfecf3335 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/test_fixed_ips.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/test_fixed_ips.py @@ -28,10 +28,10 @@ class FixedIpsTest(utils.FixturedTestCase): def test_get_fixed_ip(self): info = self.cs.fixed_ips.get(fixed_ip='192.168.1.1') self.assert_called('GET', '/os-fixed-ips/192.168.1.1') - self.assertEqual(info.cidr, '192.168.1.0/24') - self.assertEqual(info.address, '192.168.1.1') - self.assertEqual(info.hostname, 'foo') - self.assertEqual(info.host, 'bar') + self.assertEqual('192.168.1.0/24', info.cidr) + self.assertEqual('192.168.1.1', info.address) + self.assertEqual('foo', info.hostname) + self.assertEqual('bar', info.host) def test_reserve_fixed_ip(self): body = {"reserve": None} diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/test_flavors.py b/awx/lib/site-packages/novaclient/tests/v1_1/test_flavors.py index 8028cf0eef..8f5aea0079 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/test_flavors.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/test_flavors.py @@ -67,36 +67,36 @@ class FlavorsTest(utils.TestCase): f = self.cs.flavors.get(1) self.cs.assert_called('GET', '/flavors/1') self.assertIsInstance(f, self.flavor_type) - self.assertEqual(f.ram, 256) - self.assertEqual(f.disk, 10) - self.assertEqual(f.ephemeral, 10) - self.assertEqual(f.is_public, True) + self.assertEqual(256, f.ram) + self.assertEqual(10, f.disk) + self.assertEqual(10, f.ephemeral) + self.assertEqual(True, f.is_public) def test_get_flavor_details_alphanum_id(self): f = self.cs.flavors.get('aa1') self.cs.assert_called('GET', '/flavors/aa1') self.assertIsInstance(f, self.flavor_type) - self.assertEqual(f.ram, 128) - self.assertEqual(f.disk, 0) - self.assertEqual(f.ephemeral, 0) - self.assertEqual(f.is_public, True) + self.assertEqual(128, f.ram) + self.assertEqual(0, f.disk) + self.assertEqual(0, f.ephemeral) + self.assertEqual(True, f.is_public) def test_get_flavor_details_diablo(self): f = self.cs.flavors.get(3) self.cs.assert_called('GET', '/flavors/3') self.assertIsInstance(f, self.flavor_type) - self.assertEqual(f.ram, 256) - self.assertEqual(f.disk, 10) - self.assertEqual(f.ephemeral, 'N/A') - self.assertEqual(f.is_public, 'N/A') + self.assertEqual(256, f.ram) + self.assertEqual(10, f.disk) + self.assertEqual('N/A', f.ephemeral) + self.assertEqual('N/A', f.is_public) def test_find(self): f = self.cs.flavors.find(ram=256) self.cs.assert_called('GET', '/flavors/detail') - self.assertEqual(f.name, '256 MB Server') + self.assertEqual('256 MB Server', f.name) f = self.cs.flavors.find(disk=0) - self.assertEqual(f.name, '128 MB Server') + self.assertEqual('128 MB Server', f.name) self.assertRaises(exceptions.NotFound, self.cs.flavors.find, disk=12345) diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/test_floating_ip_dns.py b/awx/lib/site-packages/novaclient/tests/v1_1/test_floating_ip_dns.py index df310b2690..2ded1725e3 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/test_floating_ip_dns.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/test_floating_ip_dns.py @@ -25,13 +25,13 @@ class FloatingIPDNSDomainTest(utils.FixturedTestCase): def test_dns_domains(self): domainlist = self.cs.dns_domains.domains() - self.assertEqual(len(domainlist), 2) + self.assertEqual(2, len(domainlist)) for entry in domainlist: self.assertIsInstance(entry, floating_ip_dns.FloatingIPDNSDomain) - self.assertEqual(domainlist[1].domain, 'example.com') + self.assertEqual('example.com', domainlist[1].domain) def test_create_private_domain(self): self.cs.dns_domains.create_private(self.testdomain, 'test_avzone') @@ -61,13 +61,13 @@ class FloatingIPDNSEntryTest(utils.FixturedTestCase): def test_get_dns_entries_by_ip(self): entries = self.cs.dns_entries.get_for_ip(self.testdomain, ip=self.testip) - self.assertEqual(len(entries), 2) + self.assertEqual(2, len(entries)) for entry in entries: self.assertIsInstance(entry, floating_ip_dns.FloatingIPDNSEntry) - self.assertEqual(entries[1].dns_entry['name'], 'host2') + self.assertEqual('host2', entries[1].dns_entry['name']) self.assertEqual(entries[1].dns_entry['ip'], self.testip) def test_get_dns_entry_by_name(self): diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/test_floating_ips.py b/awx/lib/site-packages/novaclient/tests/v1_1/test_floating_ips.py index d14f8a7105..93cc733bad 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/test_floating_ips.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/test_floating_ips.py @@ -55,5 +55,5 @@ class FloatingIPsTest(utils.FixturedTestCase): def test_create_floating_ip_with_pool(self): fl = self.cs.floating_ips.create('nova') self.assert_called('POST', '/os-floating-ips') - self.assertEqual(fl.pool, 'nova') + self.assertEqual('nova', fl.pool) self.assertIsInstance(fl, floating_ips.FloatingIP) diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/test_fping.py b/awx/lib/site-packages/novaclient/tests/v1_1/test_fping.py index 87f9af29e2..5a3fb6401d 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/test_fping.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/test_fping.py @@ -26,15 +26,15 @@ class FpingTest(utils.FixturedTestCase): def test_fping_repr(self): r = self.cs.fping.get(1) - self.assertEqual(repr(r), "") + self.assertEqual("", repr(r)) def test_list_fpings(self): fl = self.cs.fping.list() self.assert_called('GET', '/os-fping') for f in fl: self.assertIsInstance(f, fping.Fping) - self.assertEqual(f.project_id, "fake-project") - self.assertEqual(f.alive, True) + self.assertEqual("fake-project", f.project_id) + self.assertEqual(True, f.alive) def test_list_fpings_all_tenants(self): fl = self.cs.fping.list(all_tenants=True) @@ -58,5 +58,5 @@ class FpingTest(utils.FixturedTestCase): f = self.cs.fping.get(1) self.assert_called('GET', '/os-fping/1') self.assertIsInstance(f, fping.Fping) - self.assertEqual(f.project_id, "fake-project") - self.assertEqual(f.alive, True) + self.assertEqual("fake-project", f.project_id) + self.assertEqual(True, f.alive) diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/test_hosts.py b/awx/lib/site-packages/novaclient/tests/v1_1/test_hosts.py index d8291c6aed..8fcdf5a76f 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/test_hosts.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/test_hosts.py @@ -78,3 +78,7 @@ class HostsTest(utils.FixturedTestCase): host.shutdown() self.assert_called( 'GET', '/os-hosts/sample_host/shutdown') + + def test_hosts_repr(self): + hs = self.cs.hosts.get('host') + self.assertEqual('', repr(hs[0])) diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/test_images.py b/awx/lib/site-packages/novaclient/tests/v1_1/test_images.py index 6ff8fe9c7f..e237bcd5cb 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/test_images.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/test_images.py @@ -40,8 +40,8 @@ class ImagesTest(utils.FixturedTestCase): i = self.cs.images.get(1) self.assert_called('GET', '/images/1') self.assertIsInstance(i, images.Image) - self.assertEqual(i.id, 1) - self.assertEqual(i.name, 'CentOS 5.2') + self.assertEqual(1, i.id) + self.assertEqual('CentOS 5.2', i.name) def test_delete_image(self): self.cs.images.delete(1) @@ -58,9 +58,9 @@ class ImagesTest(utils.FixturedTestCase): def test_find(self): i = self.cs.images.find(name="CentOS 5.2") - self.assertEqual(i.id, 1) + self.assertEqual(1, i.id) self.assert_called('GET', '/images/1') iml = self.cs.images.findall(status='SAVING') - self.assertEqual(len(iml), 1) - self.assertEqual(iml[0].name, 'My Server Backup') + self.assertEqual(1, len(iml)) + self.assertEqual('My Server Backup', iml[0].name) diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/test_keypairs.py b/awx/lib/site-packages/novaclient/tests/v1_1/test_keypairs.py index 7ebb23c2fb..9e8e63fd52 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/test_keypairs.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/test_keypairs.py @@ -37,7 +37,7 @@ class KeypairsTest(utils.FixturedTestCase): kp = self.cs.keypairs.get('test') self.assert_called('GET', '/%s/test' % self.keypair_prefix) self.assertIsInstance(kp, keypairs.Keypair) - self.assertEqual(kp.name, 'test') + self.assertEqual('test', kp.name) def test_list_keypairs(self): kps = self.cs.keypairs.list() diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/test_networks.py b/awx/lib/site-packages/novaclient/tests/v1_1/test_networks.py index 8a63c443a9..1fbd112ce2 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/test_networks.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/test_networks.py @@ -59,7 +59,13 @@ class NetworksTest(utils.FixturedTestCase): 'project_id': '1', 'vlan': 5, 'vlan_start': 1, - 'vpn_start': 1 + 'vpn_start': 1, + 'mtu': 1500, + 'enable_dhcp': 'T', + 'dhcp_server': '1920.2.2', + 'share_address': 'T', + 'allowed_start': '192.0.2.10', + 'allowed_end': '192.0.2.20', } f = self.cs.networks.create(**params) diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/test_servers.py b/awx/lib/site-packages/novaclient/tests/v1_1/test_servers.py index 728732474f..a8a3aeabdf 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/test_servers.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/test_servers.py @@ -16,48 +16,55 @@ import mock import six from novaclient import exceptions +from novaclient.openstack.common import jsonutils +from novaclient.tests.fixture_data import client +from novaclient.tests.fixture_data import floatingips +from novaclient.tests.fixture_data import servers as data from novaclient.tests import utils -from novaclient.tests.v1_1 import fakes from novaclient.v1_1 import servers -cs = fakes.FakeClient() +class ServersTest(utils.FixturedTestCase): + client_fixture_class = client.V1 + data_fixture_class = data.V1 -class ServersTest(utils.TestCase): + def setUp(self): + super(ServersTest, self).setUp() + self.useFixture(floatingips.FloatingFixture(self.requests)) def test_list_servers(self): - sl = cs.servers.list() - cs.assert_called('GET', '/servers/detail') + sl = self.cs.servers.list() + self.assert_called('GET', '/servers/detail') [self.assertIsInstance(s, servers.Server) for s in sl] def test_list_servers_undetailed(self): - sl = cs.servers.list(detailed=False) - cs.assert_called('GET', '/servers') + sl = self.cs.servers.list(detailed=False) + self.assert_called('GET', '/servers') [self.assertIsInstance(s, servers.Server) for s in sl] def test_list_servers_with_marker_limit(self): - sl = cs.servers.list(marker=1234, limit=2) - cs.assert_called('GET', '/servers/detail?limit=2&marker=1234') + sl = self.cs.servers.list(marker=1234, limit=2) + self.assert_called('GET', '/servers/detail?limit=2&marker=1234') for s in sl: self.assertIsInstance(s, servers.Server) def test_get_server_details(self): - s = cs.servers.get(1234) - cs.assert_called('GET', '/servers/1234') + s = self.cs.servers.get(1234) + self.assert_called('GET', '/servers/1234') self.assertIsInstance(s, servers.Server) - self.assertEqual(s.id, 1234) - self.assertEqual(s.status, 'BUILD') + self.assertEqual(1234, s.id) + self.assertEqual('BUILD', s.status) def test_get_server_promote_details(self): - s1 = cs.servers.list(detailed=False)[0] - s2 = cs.servers.list(detailed=True)[0] + s1 = self.cs.servers.list(detailed=False)[0] + s2 = self.cs.servers.list(detailed=True)[0] self.assertNotEqual(s1._info, s2._info) s1.get() self.assertEqual(s1._info, s2._info) def test_create_server(self): - s = cs.servers.create( + s = self.cs.servers.create( name="My server", image=1, flavor=1, @@ -69,11 +76,11 @@ class ServersTest(utils.TestCase): '/tmp/foo.txt': six.StringIO('data'), # a stream } ) - cs.assert_called('POST', '/servers') + self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) def test_create_server_boot_from_volume_with_nics(self): - old_boot = cs.servers._boot + old_boot = self.cs.servers._boot nics = [{'net-id': '11111111-1111-1111-1111-111111111111', 'v4-fixed-ip': '10.0.0.7'}] @@ -87,9 +94,9 @@ class ServersTest(utils.TestCase): self.assertEqual(boot_kwargs['nics'], nics) return old_boot(url, key, *boot_args, **boot_kwargs) - @mock.patch.object(cs.servers, '_boot', wrapped_boot) + @mock.patch.object(self.cs.servers, '_boot', wrapped_boot) def test_create_server_from_volume(): - s = cs.servers.create( + s = self.cs.servers.create( name="My server", image=1, flavor=1, @@ -99,13 +106,13 @@ class ServersTest(utils.TestCase): block_device_mapping=bdm, nics=nics ) - cs.assert_called('POST', '/os-volumes_boot') + self.assert_called('POST', '/os-volumes_boot') self.assertIsInstance(s, servers.Server) test_create_server_from_volume() def test_create_server_boot_with_nics_ipv6(self): - old_boot = cs.servers._boot + old_boot = self.cs.servers._boot nics = [{'net-id': '11111111-1111-1111-1111-111111111111', 'v6-fixed-ip': '2001:db9:0:1::10'}] @@ -113,8 +120,8 @@ class ServersTest(utils.TestCase): self.assertEqual(boot_kwargs['nics'], nics) return old_boot(url, key, *boot_args, **boot_kwargs) - with mock.patch.object(cs.servers, '_boot', wrapped_boot): - s = cs.servers.create( + with mock.patch.object(self.cs.servers, '_boot', wrapped_boot): + s = self.cs.servers.create( name="My server", image=1, flavor=1, @@ -123,11 +130,11 @@ class ServersTest(utils.TestCase): key_name="fakekey", nics=nics ) - cs.assert_called('POST', '/servers') + self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) def test_create_server_userdata_file_object(self): - s = cs.servers.create( + s = self.cs.servers.create( name="My server", image=1, flavor=1, @@ -138,11 +145,11 @@ class ServersTest(utils.TestCase): '/tmp/foo.txt': six.StringIO('data'), # a stream }, ) - cs.assert_called('POST', '/servers') + self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) def test_create_server_userdata_unicode(self): - s = cs.servers.create( + s = self.cs.servers.create( name="My server", image=1, flavor=1, @@ -154,11 +161,11 @@ class ServersTest(utils.TestCase): '/tmp/foo.txt': six.StringIO('data'), # a stream }, ) - cs.assert_called('POST', '/servers') + self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) def test_create_server_userdata_utf8(self): - s = cs.servers.create( + s = self.cs.servers.create( name="My server", image=1, flavor=1, @@ -170,22 +177,21 @@ class ServersTest(utils.TestCase): '/tmp/foo.txt': six.StringIO('data'), # a stream }, ) - cs.assert_called('POST', '/servers') + self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) def _create_disk_config(self, disk_config): - s = cs.servers.create( + s = self.cs.servers.create( name="My server", image=1, flavor=1, disk_config=disk_config ) - cs.assert_called('POST', '/servers') + self.assert_called('POST', '/servers') self.assertIsInstance(s, servers.Server) # verify disk config param was used in the request: - last_request = cs.client.callstack[-1] - body = last_request[-1] + body = jsonutils.loads(self.requests.last_request.body) server = body['server'] self.assertTrue('OS-DCF:diskConfig' in server) self.assertEqual(disk_config, server['OS-DCF:diskConfig']) @@ -197,86 +203,84 @@ class ServersTest(utils.TestCase): self._create_disk_config('MANUAL') def test_update_server(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) # Update via instance s.update(name='hi') - cs.assert_called('PUT', '/servers/1234') + self.assert_called('PUT', '/servers/1234') s.update(name='hi') - cs.assert_called('PUT', '/servers/1234') + self.assert_called('PUT', '/servers/1234') # Silly, but not an error s.update() # Update via manager - cs.servers.update(s, name='hi') - cs.assert_called('PUT', '/servers/1234') + self.cs.servers.update(s, name='hi') + self.assert_called('PUT', '/servers/1234') def test_delete_server(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.delete() - cs.assert_called('DELETE', '/servers/1234') - cs.servers.delete(1234) - cs.assert_called('DELETE', '/servers/1234') - cs.servers.delete(s) - cs.assert_called('DELETE', '/servers/1234') + self.assert_called('DELETE', '/servers/1234') + self.cs.servers.delete(1234) + self.assert_called('DELETE', '/servers/1234') + self.cs.servers.delete(s) + self.assert_called('DELETE', '/servers/1234') def test_delete_server_meta(self): - cs.servers.delete_meta(1234, ['test_key']) - cs.assert_called('DELETE', '/servers/1234/metadata/test_key') + self.cs.servers.delete_meta(1234, ['test_key']) + self.assert_called('DELETE', '/servers/1234/metadata/test_key') def test_set_server_meta(self): - cs.servers.set_meta(1234, {'test_key': 'test_value'}) - cs.assert_called('POST', '/servers/1234/metadata', - {'metadata': {'test_key': 'test_value'}}) + self.cs.servers.set_meta(1234, {'test_key': 'test_value'}) + self.assert_called('POST', '/servers/1234/metadata', + {'metadata': {'test_key': 'test_value'}}) def test_set_server_meta_item(self): - cs.servers.set_meta_item(1234, 'test_key', 'test_value') - cs.assert_called('PUT', '/servers/1234/metadata/test_key', - {'meta': {'test_key': 'test_value'}}) + self.cs.servers.set_meta_item(1234, 'test_key', 'test_value') + self.assert_called('PUT', '/servers/1234/metadata/test_key', + {'meta': {'test_key': 'test_value'}}) def test_find(self): - server = cs.servers.find(name='sample-server') - cs.assert_called('GET', '/servers', pos=-2) - cs.assert_called('GET', '/servers/1234', pos=-1) - self.assertEqual(server.name, 'sample-server') + server = self.cs.servers.find(name='sample-server') + self.assert_called('GET', '/servers/1234') + self.assertEqual('sample-server', server.name) - self.assertRaises(exceptions.NoUniqueMatch, cs.servers.find, + self.assertRaises(exceptions.NoUniqueMatch, self.cs.servers.find, flavor={"id": 1, "name": "256 MB Server"}) - sl = cs.servers.findall(flavor={"id": 1, "name": "256 MB Server"}) - self.assertEqual([s.id for s in sl], [1234, 5678, 9012]) + sl = self.cs.servers.findall(flavor={"id": 1, "name": "256 MB Server"}) + self.assertEqual([1234, 5678, 9012], [s.id for s in sl]) def test_reboot_server(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.reboot() - cs.assert_called('POST', '/servers/1234/action') - cs.servers.reboot(s, reboot_type='HARD') - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.reboot(s, reboot_type='HARD') + self.assert_called('POST', '/servers/1234/action') def test_rebuild_server(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.rebuild(image=1) - cs.assert_called('POST', '/servers/1234/action') - cs.servers.rebuild(s, image=1) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.rebuild(s, image=1) + self.assert_called('POST', '/servers/1234/action') s.rebuild(image=1, password='5678') - cs.assert_called('POST', '/servers/1234/action') - cs.servers.rebuild(s, image=1, password='5678') - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.rebuild(s, image=1, password='5678') + self.assert_called('POST', '/servers/1234/action') def _rebuild_resize_disk_config(self, disk_config, operation="rebuild"): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) if operation == "rebuild": s.rebuild(image=1, disk_config=disk_config) elif operation == "resize": s.resize(flavor=1, disk_config=disk_config) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') # verify disk config param was used in the request: - last_request = cs.client.callstack[-1] - body = last_request[-1] + body = jsonutils.loads(self.requests.last_request.body) d = body[operation] self.assertTrue('OS-DCF:diskConfig' in d) @@ -289,20 +293,31 @@ class ServersTest(utils.TestCase): self._rebuild_resize_disk_config('MANUAL') def test_rebuild_server_preserve_ephemeral(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.rebuild(image=1, preserve_ephemeral=True) - cs.assert_called('POST', '/servers/1234/action') - body = cs.client.callstack[-1][-1] + self.assert_called('POST', '/servers/1234/action') + body = jsonutils.loads(self.requests.last_request.body) d = body['rebuild'] self.assertIn('preserve_ephemeral', d) - self.assertEqual(d['preserve_ephemeral'], True) + self.assertEqual(True, d['preserve_ephemeral']) + + def test_rebuild_server_name_meta_files(self): + files = {'/etc/passwd': 'some data'} + s = self.cs.servers.get(1234) + s.rebuild(image=1, name='new', meta={'foo': 'bar'}, files=files) + body = jsonutils.loads(self.requests.last_request.body) + d = body['rebuild'] + self.assertEqual('new', d['name']) + self.assertEqual({'foo': 'bar'}, d['metadata']) + self.assertEqual('/etc/passwd', + d['personality'][0]['path']) def test_resize_server(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.resize(flavor=1) - cs.assert_called('POST', '/servers/1234/action') - cs.servers.resize(s, flavor=1) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.resize(s, flavor=1) + self.assert_called('POST', '/servers/1234/action') def test_resize_server_disk_config_auto(self): self._rebuild_resize_disk_config('AUTO', 'resize') @@ -311,162 +326,163 @@ class ServersTest(utils.TestCase): self._rebuild_resize_disk_config('MANUAL', 'resize') def test_confirm_resized_server(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.confirm_resize() - cs.assert_called('POST', '/servers/1234/action') - cs.servers.confirm_resize(s) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.confirm_resize(s) + self.assert_called('POST', '/servers/1234/action') def test_revert_resized_server(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.revert_resize() - cs.assert_called('POST', '/servers/1234/action') - cs.servers.revert_resize(s) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.revert_resize(s) + self.assert_called('POST', '/servers/1234/action') def test_migrate_server(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.migrate() - cs.assert_called('POST', '/servers/1234/action') - cs.servers.migrate(s) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.migrate(s) + self.assert_called('POST', '/servers/1234/action') def test_add_fixed_ip(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.add_fixed_ip(1) - cs.assert_called('POST', '/servers/1234/action') - cs.servers.add_fixed_ip(s, 1) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.add_fixed_ip(s, 1) + self.assert_called('POST', '/servers/1234/action') def test_remove_fixed_ip(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.remove_fixed_ip('10.0.0.1') - cs.assert_called('POST', '/servers/1234/action') - cs.servers.remove_fixed_ip(s, '10.0.0.1') - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.remove_fixed_ip(s, '10.0.0.1') + self.assert_called('POST', '/servers/1234/action') def test_add_floating_ip(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.add_floating_ip('11.0.0.1') - cs.assert_called('POST', '/servers/1234/action') - cs.servers.add_floating_ip(s, '11.0.0.1') - cs.assert_called('POST', '/servers/1234/action') - f = cs.floating_ips.list()[0] - cs.servers.add_floating_ip(s, f) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.add_floating_ip(s, '11.0.0.1') + self.assert_called('POST', '/servers/1234/action') + f = self.cs.floating_ips.list()[0] + self.cs.servers.add_floating_ip(s, f) + self.assert_called('POST', '/servers/1234/action') s.add_floating_ip(f) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') def test_add_floating_ip_to_fixed(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.add_floating_ip('11.0.0.1', fixed_address='12.0.0.1') - cs.assert_called('POST', '/servers/1234/action') - cs.servers.add_floating_ip(s, '11.0.0.1', + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.add_floating_ip(s, '11.0.0.1', fixed_address='12.0.0.1') - cs.assert_called('POST', '/servers/1234/action') - f = cs.floating_ips.list()[0] - cs.servers.add_floating_ip(s, f) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + f = self.cs.floating_ips.list()[0] + self.cs.servers.add_floating_ip(s, f) + self.assert_called('POST', '/servers/1234/action') s.add_floating_ip(f) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') def test_remove_floating_ip(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.remove_floating_ip('11.0.0.1') - cs.assert_called('POST', '/servers/1234/action') - cs.servers.remove_floating_ip(s, '11.0.0.1') - cs.assert_called('POST', '/servers/1234/action') - f = cs.floating_ips.list()[0] - cs.servers.remove_floating_ip(s, f) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.remove_floating_ip(s, '11.0.0.1') + self.assert_called('POST', '/servers/1234/action') + f = self.cs.floating_ips.list()[0] + self.cs.servers.remove_floating_ip(s, f) + self.assert_called('POST', '/servers/1234/action') s.remove_floating_ip(f) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') def test_stop(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.stop() - cs.assert_called('POST', '/servers/1234/action') - cs.servers.stop(s) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.stop(s) + self.assert_called('POST', '/servers/1234/action') def test_force_delete(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.force_delete() - cs.assert_called('POST', '/servers/1234/action') - cs.servers.force_delete(s) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.force_delete(s) + self.assert_called('POST', '/servers/1234/action') def test_restore(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.restore() - cs.assert_called('POST', '/servers/1234/action') - cs.servers.restore(s) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.restore(s) + self.assert_called('POST', '/servers/1234/action') def test_start(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.start() - cs.assert_called('POST', '/servers/1234/action') - cs.servers.start(s) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.start(s) + self.assert_called('POST', '/servers/1234/action') def test_rescue(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.rescue() - cs.assert_called('POST', '/servers/1234/action') - cs.servers.rescue(s) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.rescue(s) + self.assert_called('POST', '/servers/1234/action') def test_unrescue(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.unrescue() - cs.assert_called('POST', '/servers/1234/action') - cs.servers.unrescue(s) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.unrescue(s) + self.assert_called('POST', '/servers/1234/action') def test_lock(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.lock() - cs.assert_called('POST', '/servers/1234/action') - cs.servers.lock(s) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.lock(s) + self.assert_called('POST', '/servers/1234/action') def test_unlock(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.unlock() - cs.assert_called('POST', '/servers/1234/action') - cs.servers.unlock(s) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.unlock(s) + self.assert_called('POST', '/servers/1234/action') def test_backup(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.backup('back1', 'daily', 1) - cs.assert_called('POST', '/servers/1234/action') - cs.servers.backup(s, 'back1', 'daily', 2) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.backup(s, 'back1', 'daily', 2) + self.assert_called('POST', '/servers/1234/action') def test_get_console_output_without_length(self): success = 'foo' - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.get_console_output() - self.assertEqual(s.get_console_output(), success) - cs.assert_called('POST', '/servers/1234/action') + self.assertEqual(success, s.get_console_output()) + self.assert_called('POST', '/servers/1234/action') - cs.servers.get_console_output(s) - self.assertEqual(cs.servers.get_console_output(s), success) - cs.assert_called('POST', '/servers/1234/action') + self.cs.servers.get_console_output(s) + self.assertEqual(success, self.cs.servers.get_console_output(s)) + self.assert_called('POST', '/servers/1234/action') def test_get_console_output_with_length(self): success = 'foo' - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.get_console_output(length=50) - self.assertEqual(s.get_console_output(length=50), success) - cs.assert_called('POST', '/servers/1234/action') + self.assertEqual(success, s.get_console_output(length=50)) + self.assert_called('POST', '/servers/1234/action') - cs.servers.get_console_output(s, length=50) - self.assertEqual(cs.servers.get_console_output(s, length=50), success) - cs.assert_called('POST', '/servers/1234/action') + self.cs.servers.get_console_output(s, length=50) + self.assertEqual(success, + self.cs.servers.get_console_output(s, length=50)) + self.assert_called('POST', '/servers/1234/action') # Testing password methods with the following password and key # @@ -483,126 +499,135 @@ class ServersTest(utils.TestCase): # Hi/fmZZNQQqj1Ijq0caOIw== def test_get_password(self): - s = cs.servers.get(1234) - self.assertEqual(s.get_password('novaclient/tests/idfake.pem'), - b'FooBar123') - cs.assert_called('GET', '/servers/1234/os-server-password') + s = self.cs.servers.get(1234) + self.assertEqual(b'FooBar123', + s.get_password('novaclient/tests/idfake.pem')) + self.assert_called('GET', '/servers/1234/os-server-password') def test_get_password_without_key(self): - s = cs.servers.get(1234) - self.assertEqual(s.get_password(), + s = self.cs.servers.get(1234) + self.assertEqual( 'OIuEuQttO8Rk93BcKlwHQsziDAnkAm/V6V8VPToA8ZeUaUBWwS0gwo2K6Y61Z96r' 'qG447iRz0uTEEYq3RAYJk1mh3mMIRVl27t8MtIecR5ggVVbz1S9AwXJQypDKl0ho' 'QFvhCBcMWPohyGewDJOhDbtuN1IoFI9G55ZvFwCm5y7m7B2aVcoLeIsJZE4PLsIw' '/y5a6Z3/AoJZYGG7IH5WN88UROU3B9JZGFB2qtPLQTOvDMZLUhoPRIJeHiVSlo1N' 'tI2/++UsXVg3ow6ItqCJGgdNuGG5JB+bslDHWPxROpesEIHdczk46HCpHQN8f1sk' - 'Hi/fmZZNQQqj1Ijq0caOIw==') - cs.assert_called('GET', '/servers/1234/os-server-password') + 'Hi/fmZZNQQqj1Ijq0caOIw==', s.get_password()) + self.assert_called('GET', '/servers/1234/os-server-password') def test_clear_password(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.clear_password() - cs.assert_called('DELETE', '/servers/1234/os-server-password') + self.assert_called('DELETE', '/servers/1234/os-server-password') def test_get_server_diagnostics(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) diagnostics = s.diagnostics() self.assertTrue(diagnostics is not None) - cs.assert_called('GET', '/servers/1234/diagnostics') + self.assert_called('GET', '/servers/1234/diagnostics') - diagnostics_from_manager = cs.servers.diagnostics(1234) + diagnostics_from_manager = self.cs.servers.diagnostics(1234) self.assertTrue(diagnostics_from_manager is not None) - cs.assert_called('GET', '/servers/1234/diagnostics') + self.assert_called('GET', '/servers/1234/diagnostics') - self.assertEqual(diagnostics, diagnostics_from_manager) + self.assertEqual(diagnostics[1], diagnostics_from_manager[1]) def test_get_vnc_console(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.get_vnc_console('fake') - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') - cs.servers.get_vnc_console(s, 'fake') - cs.assert_called('POST', '/servers/1234/action') + self.cs.servers.get_vnc_console(s, 'fake') + self.assert_called('POST', '/servers/1234/action') def test_get_spice_console(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.get_spice_console('fake') - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') - cs.servers.get_spice_console(s, 'fake') - cs.assert_called('POST', '/servers/1234/action') + self.cs.servers.get_spice_console(s, 'fake') + self.assert_called('POST', '/servers/1234/action') + + def test_get_serial_console(self): + s = self.cs.servers.get(1234) + s.get_serial_console('fake') + self.assert_called('POST', '/servers/1234/action') + + self.cs.servers.get_serial_console(s, 'fake') + self.assert_called('POST', '/servers/1234/action') def test_get_rdp_console(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.get_rdp_console('fake') - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') - cs.servers.get_rdp_console(s, 'fake') - cs.assert_called('POST', '/servers/1234/action') + self.cs.servers.get_rdp_console(s, 'fake') + self.assert_called('POST', '/servers/1234/action') def test_create_image(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.create_image('123') - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') s.create_image('123', {}) - cs.assert_called('POST', '/servers/1234/action') - cs.servers.create_image(s, '123') - cs.assert_called('POST', '/servers/1234/action') - cs.servers.create_image(s, '123', {}) + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.create_image(s, '123') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.create_image(s, '123', {}) def test_live_migrate_server(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.live_migrate(host='hostname', block_migration=False, disk_over_commit=False) - cs.assert_called('POST', '/servers/1234/action') - cs.servers.live_migrate(s, host='hostname', block_migration=False, + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.live_migrate(s, host='hostname', block_migration=False, disk_over_commit=False) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') def test_reset_state(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.reset_state('newstate') - cs.assert_called('POST', '/servers/1234/action') - cs.servers.reset_state(s, 'newstate') - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.reset_state(s, 'newstate') + self.assert_called('POST', '/servers/1234/action') def test_reset_network(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.reset_network() - cs.assert_called('POST', '/servers/1234/action') - cs.servers.reset_network(s) - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.reset_network(s) + self.assert_called('POST', '/servers/1234/action') def test_add_security_group(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.add_security_group('newsg') - cs.assert_called('POST', '/servers/1234/action') - cs.servers.add_security_group(s, 'newsg') - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.add_security_group(s, 'newsg') + self.assert_called('POST', '/servers/1234/action') def test_remove_security_group(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.remove_security_group('oldsg') - cs.assert_called('POST', '/servers/1234/action') - cs.servers.remove_security_group(s, 'oldsg') - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.remove_security_group(s, 'oldsg') + self.assert_called('POST', '/servers/1234/action') def test_list_security_group(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.list_security_group() - cs.assert_called('GET', '/servers/1234/os-security-groups') + self.assert_called('GET', '/servers/1234/os-security-groups') def test_evacuate(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.evacuate('fake_target_host', 'True') - cs.assert_called('POST', '/servers/1234/action') - cs.servers.evacuate(s, 'fake_target_host', 'False', 'NewAdminPassword') - cs.assert_called('POST', '/servers/1234/action') + self.assert_called('POST', '/servers/1234/action') + self.cs.servers.evacuate(s, 'fake_target_host', + 'False', 'NewAdminPassword') + self.assert_called('POST', '/servers/1234/action') def test_interface_list(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.interface_list() - cs.assert_called('GET', '/servers/1234/os-interface') + self.assert_called('GET', '/servers/1234/os-interface') def test_interface_list_result_string_representable(self): """Test for bugs.launchpad.net/python-novaclient/+bug/1280453.""" @@ -631,11 +656,11 @@ class ServersTest(utils.TestCase): self.assertEqual('', '%r' % s) def test_interface_attach(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.interface_attach(None, None, None) - cs.assert_called('POST', '/servers/1234/os-interface') + self.assert_called('POST', '/servers/1234/os-interface') def test_interface_detach(self): - s = cs.servers.get(1234) + s = self.cs.servers.get(1234) s.interface_detach('port-id') - cs.assert_called('DELETE', '/servers/1234/os-interface/port-id') + self.assert_called('DELETE', '/servers/1234/os-interface/port-id') diff --git a/awx/lib/site-packages/novaclient/tests/v1_1/test_services.py b/awx/lib/site-packages/novaclient/tests/v1_1/test_services.py index d5242e52b0..acd25152fa 100644 --- a/awx/lib/site-packages/novaclient/tests/v1_1/test_services.py +++ b/awx/lib/site-packages/novaclient/tests/v1_1/test_services.py @@ -35,8 +35,8 @@ class ServicesTest(utils.TestCase): self.cs.assert_called('GET', '/os-services') for s in svs: self.assertIsInstance(s, self._get_service_type()) - self.assertEqual(s.binary, 'nova-compute') - self.assertEqual(s.host, 'host1') + self.assertEqual('nova-compute', s.binary) + self.assertEqual('host1', s.host) self.assertTrue(str(s).startswith('>> client = Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL) + Or, alternatively, you can create a client instance using the + keystoneclient.session API:: + + >>> from keystoneclient.auth.identity import v2 + >>> from keystoneclient import session + >>> from novaclient.client import Client + >>> auth = v2.Password(auth_url=AUTH_URL, + username=USERNAME, + password=PASSWORD, + tenant_name=PROJECT_ID) + >>> sess = session.Session(auth=auth) + >>> nova = client.Client(VERSION, session=sess) + Then call methods on its managers:: >>> client.servers.list() @@ -107,6 +122,7 @@ class Client(object): self.images = images.ImageManager(self) self.limits = limits.LimitsManager(self) self.servers = servers.ServerManager(self) + self.versions = versions.VersionManager(self) # extensions self.agents = agents.AgentsManager(self) @@ -127,6 +143,8 @@ class Client(object): self.security_groups = security_groups.SecurityGroupManager(self) self.security_group_rules = \ security_group_rules.SecurityGroupRuleManager(self) + self.security_group_default_rules = \ + security_group_default_rules.SecurityGroupDefaultRuleManager(self) self.usage = usage.UsageManager(self) self.virtual_interfaces = \ virtual_interfaces.VirtualInterfaceManager(self) diff --git a/awx/lib/site-packages/novaclient/v1_1/cloudpipe.py b/awx/lib/site-packages/novaclient/v1_1/cloudpipe.py index 74fed75c77..6e05f4a20f 100644 --- a/awx/lib/site-packages/novaclient/v1_1/cloudpipe.py +++ b/awx/lib/site-packages/novaclient/v1_1/cloudpipe.py @@ -19,7 +19,7 @@ from novaclient import base class Cloudpipe(base.Resource): - """A cloudpipe instance is a VPN attached to a proejct's VLAN.""" + """A cloudpipe instance is a VPN attached to a project's VLAN.""" def __repr__(self): return "" % self.project_id diff --git a/awx/lib/site-packages/novaclient/v1_1/contrib/host_evacuate.py b/awx/lib/site-packages/novaclient/v1_1/contrib/host_evacuate.py index dd1aef2676..12ad602fa8 100644 --- a/awx/lib/site-packages/novaclient/v1_1/contrib/host_evacuate.py +++ b/awx/lib/site-packages/novaclient/v1_1/contrib/host_evacuate.py @@ -26,8 +26,8 @@ def _server_evacuate(cs, server, args): success = True error_message = "" try: - cs.servers.evacuate(server['uuid'], args.target_host, - args.on_shared_storage) + cs.servers.evacuate(server=server['uuid'], host=args.target_host, + on_shared_storage=args.on_shared_storage) except Exception as e: success = False error_message = _("Error while evacuating instance: %s") % e @@ -41,7 +41,9 @@ def _server_evacuate(cs, server, args): @utils.arg('--target_host', metavar='', default=None, - help=_('Name of target host.')) + help=_('Name of target host. ' + 'If no host is specified the scheduler' + ' will select a target.')) @utils.arg('--on-shared-storage', dest='on_shared_storage', action="store_true", @@ -49,7 +51,7 @@ def _server_evacuate(cs, server, args): help=_('Specifies whether all instances files are on shared ' ' storage')) def do_host_evacuate(cs, args): - """Evacuate all instances from failed host to specified one.""" + """Evacuate all instances from failed host.""" hypervisors = cs.hypervisors.search(args.host, servers=True) response = [] for hyper in hypervisors: diff --git a/awx/lib/site-packages/novaclient/v1_1/flavors.py b/awx/lib/site-packages/novaclient/v1_1/flavors.py index 30536fc680..e66e0b9e23 100644 --- a/awx/lib/site-packages/novaclient/v1_1/flavors.py +++ b/awx/lib/site-packages/novaclient/v1_1/flavors.py @@ -16,12 +16,12 @@ Flavor interface. """ +from oslo.utils import strutils from six.moves.urllib import parse from novaclient import base from novaclient import exceptions from novaclient.openstack.common.gettextutils import _ -from novaclient.openstack.common import strutils from novaclient import utils diff --git a/awx/lib/site-packages/novaclient/v1_1/hosts.py b/awx/lib/site-packages/novaclient/v1_1/hosts.py index 0b641d44c9..af1756ad2c 100644 --- a/awx/lib/site-packages/novaclient/v1_1/hosts.py +++ b/awx/lib/site-packages/novaclient/v1_1/hosts.py @@ -21,7 +21,7 @@ from novaclient import base class Host(base.Resource): def __repr__(self): - return "" % self.host_name + return "" % self.host def _add_details(self, info): dico = 'resource' in info and info['resource'] or info diff --git a/awx/lib/site-packages/novaclient/v1_1/networks.py b/awx/lib/site-packages/novaclient/v1_1/networks.py index d4dc78b8ee..a275028689 100644 --- a/awx/lib/site-packages/novaclient/v1_1/networks.py +++ b/awx/lib/site-packages/novaclient/v1_1/networks.py @@ -26,7 +26,7 @@ class Network(base.Resource): """ A network. """ - HUMAN_ID = False + HUMAN_ID = True NAME_ATTR = "label" def __repr__(self): @@ -89,8 +89,14 @@ class NetworkManager(base.ManagerWithFind): :param vlan: int :param vlan_start: int :param vpn_start: int + :param mtu: int + :param enable_dhcp: int + :param dhcp_server: str + :param share_address: int + :param allowed_start: str + :param allowed_end: str - :rtype: list of :class:`Network` + :rtype: object of :class:`Network` """ body = {"network": kwargs} return self._create('/os-networks', body, 'network') diff --git a/awx/lib/site-packages/novaclient/v1_1/security_group_default_rules.py b/awx/lib/site-packages/novaclient/v1_1/security_group_default_rules.py new file mode 100644 index 0000000000..5208cb0612 --- /dev/null +++ b/awx/lib/site-packages/novaclient/v1_1/security_group_default_rules.py @@ -0,0 +1,81 @@ +# 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. + +""" +Security group default rules interface. +""" + +from novaclient import base +from novaclient import exceptions +from novaclient.openstack.common.gettextutils import _ + + +class SecurityGroupDefaultRule(base.Resource): + def __str__(self): + return str(self.id) + + def delete(self): + self.manager.delete(self) + + +class SecurityGroupDefaultRuleManager(base.Manager): + resource_class = SecurityGroupDefaultRule + + def create(self, ip_protocol=None, from_port=None, to_port=None, + cidr=None): + """ + Create a security group default rule + + :param ip_protocol: IP protocol, one of 'tcp', 'udp' or 'icmp' + :param from_port: Source port + :param to_port: Destination port + :param cidr: Destination IP address(es) in CIDR notation + """ + + try: + from_port = int(from_port) + except (TypeError, ValueError): + raise exceptions.CommandError(_("From port must be an integer.")) + try: + to_port = int(to_port) + except (TypeError, ValueError): + raise exceptions.CommandError(_("To port must be an integer.")) + if ip_protocol.upper() not in ['TCP', 'UDP', 'ICMP']: + raise exceptions.CommandError(_("Ip protocol must be 'tcp', 'udp'" + ", or 'icmp'.")) + + body = {"security_group_default_rule": { + "ip_protocol": ip_protocol, + "from_port": from_port, + "to_port": to_port, + "cidr": cidr}} + + return self._create('/os-security-group-default-rules', body, + 'security_group_default_rule') + + def delete(self, rule): + """ + Delete a security group default rule + + :param rule: The security group default rule to delete (ID or Class) + """ + self._delete('/os-security-group-default-rules/%s' % base.getid(rule)) + + def list(self): + """ + Get a list of all security group default rules + + :rtype: list of :class:`SecurityGroupDefaultRule` + """ + + return self._list('/os-security-group-default-rules', + 'security_group_default_rules') diff --git a/awx/lib/site-packages/novaclient/v1_1/servers.py b/awx/lib/site-packages/novaclient/v1_1/servers.py index 4009990490..38bde8c620 100644 --- a/awx/lib/site-packages/novaclient/v1_1/servers.py +++ b/awx/lib/site-packages/novaclient/v1_1/servers.py @@ -21,14 +21,15 @@ Server interface. import base64 +from oslo.utils import encodeutils import six from six.moves.urllib import parse from novaclient import base from novaclient import crypto from novaclient.openstack.common.gettextutils import _ -from novaclient.openstack.common import strutils -from novaclient.v1_1.security_groups import SecurityGroup +from novaclient.v1_1 import security_groups + REBOOT_SOFT, REBOOT_HARD = 'SOFT', 'HARD' @@ -85,6 +86,14 @@ class Server(base.Resource): """ return self.manager.get_rdp_console(self, console_type) + def get_serial_console(self, console_type): + """ + Get serial console for a Server. + + :param console_type: Type of console ('serial') + """ + return self.manager.get_serial_console(self, console_type) + def get_password(self, private_key=None): """ Get password for a Server. @@ -364,7 +373,7 @@ class Server(base.Resource): """ return self.manager.list_security_group(self) - def evacuate(self, host, on_shared_storage, password=None): + def evacuate(self, host=None, on_shared_storage=True, password=None): """ Evacuate an instance from failed host to specified host. @@ -419,7 +428,7 @@ class ServerManager(base.BootingManagerWithFind): 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 + :param return_raw: If True, don't try to coerce 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 @@ -453,7 +462,7 @@ class ServerManager(base.BootingManagerWithFind): if six.PY3: userdata = userdata.encode("utf-8") else: - userdata = strutils.safe_encode(userdata) + userdata = encodeutils.safe_encode(userdata) userdata_b64 = base64.b64encode(userdata).decode('utf-8') body["server"]["user_data"] = userdata_b64 @@ -674,6 +683,17 @@ class ServerManager(base.BootingManagerWithFind): return self._action('os-getRDPConsole', server, {'type': console_type})[1] + def get_serial_console(self, server, console_type): + """ + Get a serial console for an instance + + :param server: The :class:`Server` (or its ID) to add an IP to. + :param console_type: Type of serial console to get ('serial') + """ + + return self._action('os-getSerialConsole', server, + {'type': console_type})[1] + def get_password(self, server, private_key=None): """ Get password for an instance @@ -922,7 +942,8 @@ class ServerManager(base.BootingManagerWithFind): self._action('reboot', server, {'type': reboot_type}) def rebuild(self, server, image, password=None, disk_config=None, - preserve_ephemeral=False, **kwargs): + preserve_ephemeral=False, name=None, meta=None, files=None, + **kwargs): """ Rebuild -- shut down and then re-image -- a server. @@ -933,6 +954,15 @@ class ServerManager(base.BootingManagerWithFind): Valid values are 'AUTO' or 'MANUAL' :param preserve_ephemeral: If True, request that any ephemeral device be preserved when rebuilding the instance. Defaults to False. + :param name: Something to name the server. + :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. """ body = {'imageRef': base.getid(image)} if password is not None: @@ -941,6 +971,24 @@ class ServerManager(base.BootingManagerWithFind): body['OS-DCF:diskConfig'] = disk_config if preserve_ephemeral is not False: body['preserve_ephemeral'] = True + if name is not None: + body['name'] = name + if meta: + body['metadata'] = meta + if files: + personality = body['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 + + cont = base64.b64encode(data.encode('utf-8')).decode('utf-8') + personality.append({ + 'path': filepath, + 'contents': cont, + }) _resp, body = self._action('rebuild', server, body, **kwargs) return Server(self, body['server']) @@ -1119,9 +1167,11 @@ class ServerManager(base.BootingManagerWithFind): """ return self._list('/servers/%s/os-security-groups' % - base.getid(server), 'security_groups', SecurityGroup) + base.getid(server), 'security_groups', + security_groups.SecurityGroup) - def evacuate(self, server, host, on_shared_storage, password=None): + def evacuate(self, server, host=None, on_shared_storage=True, + password=None): """ Evacuate a server instance. @@ -1131,10 +1181,10 @@ class ServerManager(base.BootingManagerWithFind): on shared storage :param password: string to set as password on the evacuated server. """ - body = { - 'host': host, - 'onSharedStorage': on_shared_storage, - } + + body = {'onSharedStorage': on_shared_storage} + if host is not None: + body['host'] = host if password is not None: body['adminPass'] = password diff --git a/awx/lib/site-packages/novaclient/v1_1/shell.py b/awx/lib/site-packages/novaclient/v1_1/shell.py index ed43277d8c..457ebc1a10 100644 --- a/awx/lib/site-packages/novaclient/v1_1/shell.py +++ b/awx/lib/site-packages/novaclient/v1_1/shell.py @@ -23,16 +23,18 @@ import copy import datetime import getpass import locale +import logging import os import sys import time +from oslo.utils import encodeutils +from oslo.utils import strutils +from oslo.utils import timeutils import six from novaclient import exceptions from novaclient.openstack.common.gettextutils import _ -from novaclient.openstack.common import strutils -from novaclient.openstack.common import timeutils from novaclient.openstack.common import uuidutils from novaclient import utils from novaclient.v1_1 import availability_zones @@ -40,6 +42,9 @@ from novaclient.v1_1 import quotas from novaclient.v1_1 import servers +logger = logging.getLogger(__name__) + + CLIENT_BDM2_KEYS = { 'id': 'uuid', 'source': 'source_type', @@ -356,6 +361,7 @@ def _boot(cs, args): help=_("Store arbitrary files from locally to " "on the new server. You may store up to 5 files.")) @utils.arg('--key-name', + default=os.environ.get('NOVACLIENT_DEFAULT_KEY_NAME'), metavar='', help=_("Key name of keypair that should be created earlier with \ the command keypair-add")) @@ -506,7 +512,7 @@ def _poll_for_status(poll_fn, obj_id, action, final_ok_states, sys.stdout.flush() if not silent: - print + print() while True: obj = poll_fn(obj_id) @@ -586,7 +592,7 @@ def _print_flavor_list(flavors, show_extra_specs=False): 'Memory_MB', 'Disk', 'Ephemeral', - 'Swap_MB', + 'Swap', 'VCPUs', 'RXTX_Factor', 'Is_Public', @@ -778,10 +784,25 @@ def do_scrub(cs, args): cs.security_groups.delete(group) -def do_network_list(cs, _args): +@utils.arg('--fields', + default=None, + metavar='', + help='Comma-separated list of fields to display. ' + 'Use the show command to see which fields are available.') +def do_network_list(cs, args): """Print a list of available networks.""" network_list = cs.networks.list() columns = ['ID', 'Label', 'Cidr'] + + formatters = {} + field_titles = [] + if args.fields: + for field in args.fields.split(','): + field_title, formatter = utils._make_field_formatter(field, {}) + field_titles.append(field_title) + formatters[field_title] = formatter + + columns = columns + field_titles utils.print_list(network_list, columns) @@ -794,6 +815,15 @@ def do_network_show(cs, args): utils.print_dict(network._info) +@utils.arg('network', + metavar='', + help=_("uuid or label of network")) +def do_network_delete(cs, args): + """Delete network by label or id.""" + network = utils.find_resource(cs.networks, args.network) + network.delete() + + @utils.arg('--host-only', dest='host_only', metavar='<0|1>', @@ -844,7 +874,8 @@ def _filter_network_create_options(args): valid_args = ['label', 'cidr', 'vlan_start', 'vpn_start', 'cidr_v6', 'gateway', 'gateway_v6', 'bridge', 'bridge_interface', 'multi_host', 'dns1', 'dns2', 'uuid', 'fixed_cidr', - 'project_id', 'priority', 'vlan'] + 'project_id', 'priority', 'vlan', 'mtu', 'dhcp_server', + 'allowed_start', 'allowed_end'] kwargs = {} for k, v in args.__dict__.items(): if k in valid_args and v is not None: @@ -865,15 +896,18 @@ def _filter_network_create_options(args): help=_('IPv6 subnet (ex: fe80::/64')) @utils.arg('--vlan', dest='vlan', + type=int, metavar='', - help=_("vlan id to be assigned to project")) + help=_("The vlan ID to be assigned to the project.")) @utils.arg('--vlan-start', dest='vlan_start', + type=int, metavar='', - help=_('First vlan ID to be assigned to project. Subsequent vlan' - ' IDs will be assigned incrementally')) + help=_('First vlan ID to be assigned to the project. Subsequent vlan ' + 'IDs will be assigned incrementally.')) @utils.arg('--vpn', dest='vpn_start', + type=int, metavar='', help=_("vpn start")) @utils.arg('--gateway', @@ -881,15 +915,15 @@ def _filter_network_create_options(args): help=_('gateway')) @utils.arg('--gateway-v6', dest="gateway_v6", - help=_('ipv6 gateway')) + help=_('IPv6 gateway')) @utils.arg('--bridge', dest="bridge", metavar='', - help=_('VIFs on this network are connected to this bridge')) + help=_('VIFs on this network are connected to this bridge.')) @utils.arg('--bridge-interface', dest="bridge_interface", metavar='', - help=_('the bridge is connected to this interface')) + help=_('The bridge is connected to this interface.')) @utils.arg('--multi-host', dest="multi_host", metavar="<'T'|'F'>", @@ -908,25 +942,52 @@ def _filter_network_create_options(args): @utils.arg('--fixed-cidr', dest="fixed_cidr", metavar='', - help=_('IPv4 subnet for fixed IPS (ex: 10.20.0.0/16)')) + help=_('IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)')) @utils.arg('--project-id', dest="project_id", metavar="", - help=_('Project id')) + help=_('Project ID')) @utils.arg('--priority', dest="priority", metavar="", help=_('Network interface priority')) +@utils.arg('--mtu', + dest="mtu", + type=int, + help=_('MTU for network')) +@utils.arg('--enable-dhcp', + dest="enable_dhcp", + metavar="<'T'|'F'>", + help=_('Enable dhcp')) +@utils.arg('--dhcp-server', + dest="dhcp_server", + help=_('Dhcp-server (defaults to gateway address)')) +@utils.arg('--share-address', + dest="share_address", + metavar="<'T'|'F'>", + help=_('Share address')) +@utils.arg('--allowed-start', + dest="allowed_start", + help=_('Start of allowed addresses for instances')) +@utils.arg('--allowed-end', + dest="allowed_end", + help=_('End of allowed addresses for instances')) def do_network_create(cs, args): """Create a network.""" if not (args.cidr or args.cidr_v6): raise exceptions.CommandError( - _("Must specify eith fixed_range_v4 or fixed_range_v6")) + _("Must specify either fixed_range_v4 or fixed_range_v6")) kwargs = _filter_network_create_options(args) if args.multi_host is not None: kwargs['multi_host'] = bool(args.multi_host == 'T' or strutils.bool_from_string(args.multi_host)) + if args.enable_dhcp is not None: + kwargs['enable_dhcp'] = bool(args.enable_dhcp == 'T' or + strutils.bool_from_string(args.enable_dhcp)) + if args.share_address is not None: + kwargs['share_address'] = bool(args.share_address == 'T' or + strutils.bool_from_string(args.share_address)) cs.networks.create(**kwargs) @@ -934,7 +995,7 @@ def do_network_create(cs, args): @utils.arg('--limit', dest="limit", metavar="", - help=_('number of images to return per request')) + help=_('Number of images to return per request.')) def do_image_list(cs, _args): """Print a list of available images to boot from.""" limit = _args.limit @@ -1110,7 +1171,7 @@ def do_image_delete(cs, args): const=1, help=argparse.SUPPRESS) @utils.arg('--tenant', - #nova db searches by project_id + # nova db searches by project_id dest='tenant', metavar='', nargs='?', @@ -1242,6 +1303,23 @@ def do_reboot(cs, args): action="store_true", default=False, help='Preserve the default ephemeral storage partition on rebuild.') +@utils.arg('--name', + metavar='', + default=None, + help=_('Name for the new server')) +@utils.arg('--meta', + metavar="", + action='append', + default=[], + help=_("Record arbitrary key/value metadata to /meta.js " + "on the new server. Can be specified multiple times.")) +@utils.arg('--file', + metavar="", + action='append', + dest='files', + default=[], + help=_("Store arbitrary files from locally to " + "on the new server. You may store up to 5 files.")) def do_rebuild(cs, args): """Shutdown, re-image, and re-boot a server.""" server = _find_server(cs, args.server) @@ -1254,6 +1332,25 @@ def do_rebuild(cs, args): kwargs = utils.get_resource_manager_extra_kwargs(do_rebuild, args) kwargs['preserve_ephemeral'] = args.preserve_ephemeral + kwargs['name'] = args.name + meta = dict(v.split('=', 1) for v in args.meta) + kwargs['meta'] = meta + + files = {} + for f in args.files: + try: + dst, src = f.split('=', 1) + with open(src, 'r') as s: + files[dst] = s.read() + except IOError as e: + raise exceptions.CommandError(_("Can't open '%(src)s': %(exc)s") % + {'src': src, 'exc': e}) + except ValueError as e: + raise exceptions.CommandError(_("Invalid file argument '%s'. " + "File arguments must be of the " + "form '--file " + "'") % f) + kwargs['files'] = files server = server.rebuild(image, _password, **kwargs) _print_server(cs, args, server) @@ -1529,8 +1626,11 @@ def _print_server(cs, args, server=None): flavor_id) if 'security_groups' in info: - info['security_groups'] = \ - ', '.join(group['name'] for group in info['security_groups']) + # when we have multiple nics the info will include the + # security groups N times where N == number of nics. Be nice + # and only display it once. + info['security_groups'] = ', '.join( + sorted(set(group['name'] for group in info['security_groups']))) image = info.get('image', {}) if image: @@ -1567,19 +1667,19 @@ def do_show(cs, args): help=_('Name or ID of server(s).')) def do_delete(cs, args): """Immediately shut down and delete specified server(s).""" - failure_count = 0 + failure_flag = False for server in args.server: try: _find_server(cs, server).delete() print(_("Request to delete server %s has been accepted.") % server) except Exception as e: - failure_count += 1 + failure_flag = True print(e) - if failure_count == len(args.server): - raise exceptions.CommandError(_("Unable to delete any of the " - "specified servers.")) + if failure_flag: + raise exceptions.CommandError(_("Unable to delete the " + "specified server(s).")) def _find_server(cs, server): @@ -1940,6 +2040,27 @@ def do_get_rdp_console(cs, args): utils.print_list([RDPConsole(data['console'])], ['Type', 'Url']) +@utils.arg('server', metavar='', help=_('Name or ID of server.')) +@utils.arg('--console_type', default='serial', + help=_('Type of serial console, default="serial".')) +def do_get_serial_console(cs, args): + """Get a serial console to a server.""" + if args.console_type not in ('serial',): + raise exceptions.CommandError( + _("Invalid parameter value for 'console_type', " + "currently supported 'serial'.")) + + server = _find_server(cs, args.server) + data = server.get_serial_console(args.console_type) + + class SerialConsole: + def __init__(self, console_dict): + self.type = console_dict['type'] + self.url = console_dict['url'] + + utils.print_list([SerialConsole(data['console'])], ['Type', 'Url']) + + @utils.arg('server', metavar='', help='Name or ID of server.') @utils.arg('private_key', metavar='', @@ -2194,7 +2315,7 @@ def do_dns_create_public_domain(cs, args): args.project) -def _print_secgroup_rules(rules): +def _print_secgroup_rules(rules, show_source_group=True): class FormattedRule: def __init__(self, obj): items = (obj if isinstance(obj, dict) else obj._info).items() @@ -2210,8 +2331,10 @@ def _print_secgroup_rules(rules): setattr(self, k, v) rules = [FormattedRule(rule) for rule in rules] - utils.print_list(rules, ['IP Protocol', 'From Port', 'To Port', - 'IP Range', 'Source Group']) + headers = ['IP Protocol', 'From Port', 'To Port', 'IP Range'] + if show_source_group: + headers.append('Source Group') + utils.print_list(rules, headers) def _print_secgroups(secgroups): @@ -2220,7 +2343,7 @@ def _print_secgroups(secgroups): def _get_secgroup(cs, secgroup): # Check secgroup is an ID (nova-network) or UUID (neutron) - if (utils.is_integer_like(strutils.safe_encode(secgroup)) + if (utils.is_integer_like(encodeutils.safe_encode(secgroup)) or uuidutils.is_uuid_like(secgroup)): try: return cs.security_groups.get(secgroup) @@ -2504,7 +2627,7 @@ def _find_keypair(cs, keypair): @utils.arg('--tenant', - #nova db searches by project_id + # nova db searches by project_id dest='tenant', metavar='', nargs='?', @@ -2786,6 +2909,14 @@ def do_aggregate_set_metadata(cs, args): """Update the metadata associated with the aggregate.""" aggregate = _find_aggregate(cs, args.aggregate) metadata = _extract_metadata(args) + currentmetadata = getattr(aggregate, 'metadata', {}) + if set(metadata.items()) & set(currentmetadata.items()): + raise exceptions.CommandError(_("metadata already exists")) + for key, value in metadata.items(): + if value is None and key not in currentmetadata: + raise exceptions.CommandError(_("metadata key %s does not exist" + " hence can not be deleted") + % key) aggregate = cs.aggregates.set_metadata(aggregate.id, metadata) print(_("Metadata has been successfully updated for aggregate %s.") % aggregate.id) @@ -3102,9 +3233,41 @@ def ensure_service_catalog_present(cs): def do_endpoints(cs, _args): """Discover endpoints that get returned from the authenticate services.""" ensure_service_catalog_present(cs) + catalog = cs.client.service_catalog.catalog - for e in catalog['access']['serviceCatalog']: - utils.print_dict(e['endpoints'][0], e['name']) + region = cs.client.region_name + + for service in catalog['access']['serviceCatalog']: + name, endpoints = service["name"], service["endpoints"] + + try: + endpoint = _get_first_endpoint(endpoints, region) + utils.print_dict(endpoint, name) + except LookupError: + print(_("WARNING: %(service)s has no endpoint in %(region)s! " + "Available endpoints for this service:") % + {'service': name, 'region': region}) + for other_endpoint in endpoints: + utils.print_dict(other_endpoint, name) + + +def _get_first_endpoint(endpoints, region): + """Find the first suitable endpoint in endpoints. + + If there is only one endpoint, return it. If there is more than + one endpoint, return the first one with the given region. If there + are no endpoints, or there is more than one endpoint but none of + them match the given region, raise KeyError. + + """ + if len(endpoints) == 1: + return endpoints[0] + else: + for candidate_endpoint in endpoints: + if candidate_endpoint["region"] == region: + return candidate_endpoint + + raise LookupError("No suitable endpoint found") @utils.arg('--wrap', dest='wrap', metavar='', default=64, @@ -3130,9 +3293,16 @@ def do_credentials(cs, _args): dest='private', action='store_true', default=False, - help=_('Optional flag to indicate whether to only use private address ' - 'attached to an instance. (Default=False). If no public address is ' - 'found try private address')) + help=argparse.SUPPRESS) +@utils.arg('--address-type', + dest='address_type', + action='store', + type=str, + default='floating', + help=_('Optional flag to indicate which IP type to use. Possible values ' + 'includes fixed and floating (the Default).')) +@utils.arg('--network', metavar='', + help=_('Network to use for the ssh.'), default=None) @utils.arg('--ipv6', dest='ipv6', action='store_true', @@ -3157,42 +3327,65 @@ def do_ssh(cs, args): args.server = server addresses = _find_server(cs, args.server).addresses - address_type = "private" if args.private else "public" + address_type = "fixed" if args.private else args.address_type version = 6 if args.ipv6 else 4 + pretty_version = 'IPv%d' % version - if (address_type == "public" and address_type not in addresses and - "private" in addresses): - address_type = "private" + # Select the network to use. + if args.network: + network_addresses = addresses.get(args.network) + if not network_addresses: + msg = _("Server '%(server)s' is not attached to network " + "'%(network)s'") + raise exceptions.ResourceNotFound( + msg % {'server': args.server, 'network': args.network}) + else: + if len(addresses) > 1: + msg = _("Server '%(server)s' is attached to more than one network." + " Please pick the network to use.") + raise exceptions.CommandError(msg % {'server': args.server}) + elif not addresses: + msg = _("Server '%(server)s' is not attached to any network.") + raise exceptions.CommandError(msg % {'server': args.server}) + else: + network_addresses = list(six.itervalues(addresses))[0] - if address_type not in addresses: - print(_("ERROR: No %(addr_type)s addresses found for '%(server)s'.") % - {'addr_type': address_type, 'server': args.server}) - return - - ip_address = None - for address in addresses[address_type]: - if address['version'] == version: - ip_address = address['addr'] - break + # Select the address in the selected network. + # If the extension is not present, we assume the address to be floating. + match = lambda addr: all(( + addr.get('version') == version, + addr.get('OS-EXT-IPS:type', 'floating') == address_type)) + matching_addresses = [address.get('addr') for address in network_addresses + if match(address)] + if not any(matching_addresses): + msg = _("No address that would match network '%(network)s'" + " and type '%(address_type)s' of version %(pretty_version)s " + "has been found for server '%(server)s'.") + raise exceptions.ResourceNotFound(msg % { + 'network': args.network, 'address_type': address_type, + 'pretty_version': pretty_version, 'server': args.server}) + elif len(matching_addresses) > 1: + msg = _("More than one %(pretty_version)s %(address_type)s address" + "found.") + raise exceptions.CommandError(msg % {'pretty_version': pretty_version, + 'address_type': address_type}) + else: + ip_address = matching_addresses[0] identity = '-i %s' % args.identity if len(args.identity) else '' - if ip_address: - os.system("ssh -%d -p%d %s %s@%s %s" % (version, args.port, identity, - args.login, ip_address, - args.extra)) - else: - pretty_version = "IPv%d" % version - print(_("ERROR: No %(addr_type)s %(pretty_version)s address found.") % - {'addr_type': address_type, 'pretty_version': pretty_version}) - return + cmd = "ssh -%d -p%d %s %s@%s %s" % (version, args.port, identity, + args.login, ip_address, args.extra) + logger.debug("Executing cmd '%s'", cmd) + os.system(cmd) _quota_resources = ['instances', 'cores', 'ram', 'floating_ips', 'fixed_ips', 'metadata_items', 'injected_files', 'injected_file_content_bytes', 'injected_file_path_bytes', 'key_pairs', - 'security_groups', 'security_group_rules'] + 'security_groups', 'security_group_rules', + 'server_groups', 'server_group_members'] def _quota_show(quotas): @@ -3337,6 +3530,16 @@ def do_quota_defaults(cs, args): type=int, default=None, help=_('New value for the "security-group-rules" quota.')) +@utils.arg('--server-groups', + metavar='', + type=int, + default=None, + help=_('New value for the "server-groups" quota.')) +@utils.arg('--server-group-members', + metavar='', + type=int, + default=None, + help=_('New value for the "server-group-members" quota.')) @utils.arg('--force', dest='force', action="store_true", @@ -3351,6 +3554,7 @@ def do_quota_update(cs, args): @utils.arg('--tenant', metavar='', + required=True, help=_('ID of tenant to delete quota for.')) @utils.arg('--user', metavar='', @@ -3444,6 +3648,16 @@ def do_quota_class_show(cs, args): type=int, default=None, help=_('New value for the "security-group-rules" quota.')) +@utils.arg('--server-groups', + metavar='', + type=int, + default=None, + help=_('New value for the "server-groups" quota.')) +@utils.arg('--server-group-members', + metavar='', + type=int, + default=None, + help=_('New value for the "server-group-members" quota.')) def do_quota_class_update(cs, args): """Update the quotas for a quota class.""" @@ -3451,11 +3665,12 @@ def do_quota_class_update(cs, args): @utils.arg('server', metavar='', help=_('Name or ID of server.')) -@utils.arg('host', metavar='', help=_('Name or ID of target host.')) +@utils.arg('host', metavar='', nargs='?', + help=_("Name or ID of the target host. " + "If no host is specified, the scheduler will choose one.")) @utils.arg('--password', dest='password', metavar='', - default=None, help=_("Set the provided password on the evacuated server. Not applicable " "with on-shared-storage flag")) @utils.arg('--on-shared-storage', @@ -3464,7 +3679,8 @@ def do_quota_class_update(cs, args): default=False, help=_('Specifies whether server files are located on shared storage')) def do_evacuate(cs, args): - """Evacuate server from failed host to specified one.""" + """Evacuate server from failed host.""" + server = _find_server(cs, args.server) res = server.evacuate(args.host, args.on_shared_storage, args.password)[1] @@ -3597,6 +3813,55 @@ def do_server_group_list(cs, args): _print_server_group_details(server_groups) +def do_secgroup_list_default_rules(cs, args): + """List rules for the default security group.""" + _print_secgroup_rules(cs.security_group_default_rules.list(), + show_source_group=False) + + +@utils.arg('ip_proto', + metavar='', + help=_('IP protocol (icmp, tcp, udp).')) +@utils.arg('from_port', + metavar='', + help=_('Port at start of range.')) +@utils.arg('to_port', + metavar='', + help=_('Port at end of range.')) +@utils.arg('cidr', metavar='', help=_('CIDR for address range.')) +def do_secgroup_add_default_rule(cs, args): + """Add a rule to the default security group.""" + rule = cs.security_group_default_rules.create(args.ip_proto, + args.from_port, + args.to_port, + args.cidr) + _print_secgroup_rules([rule], show_source_group=False) + + +@utils.arg('ip_proto', + metavar='', + help=_('IP protocol (icmp, tcp, udp).')) +@utils.arg('from_port', + metavar='', + help=_('Port at start of range.')) +@utils.arg('to_port', + metavar='', + help=_('Port at end of range.')) +@utils.arg('cidr', metavar='', help=_('CIDR for address range.')) +def do_secgroup_delete_default_rule(cs, args): + """Delete a rule from the default security group.""" + for rule in cs.security_group_default_rules.list(): + if (rule.ip_protocol and + rule.ip_protocol.upper() == args.ip_proto.upper() and + rule.from_port == int(args.from_port) and + rule.to_port == int(args.to_port) and + rule.ip_range['cidr'] == args.cidr): + _print_secgroup_rules([rule], show_source_group=False) + return cs.security_group_default_rules.delete(rule.id) + + raise exceptions.CommandError(_("Rule not found")) + + @utils.arg('name', metavar='', help='Server group name.') # NOTE(wingwj): The '--policy' way is still reserved here for preserving # the backwards compatibility of CLI, even if a user won't get this usage @@ -3654,3 +3919,10 @@ def do_server_group_get(cs, args): """Get a specific server group.""" server_group = cs.server_groups.get(args.id) _print_server_group_details([server_group]) + + +def do_version_list(cs, args): + """List all API versions.""" + result = cs.versions.list() + columns = ["Id", "Status", "Updated"] + utils.print_list(result, columns) diff --git a/awx/lib/site-packages/novaclient/v1_1/versions.py b/awx/lib/site-packages/novaclient/v1_1/versions.py new file mode 100644 index 0000000000..5ab09a103a --- /dev/null +++ b/awx/lib/site-packages/novaclient/v1_1/versions.py @@ -0,0 +1,35 @@ +# Copyright 2014 NEC Corporation. 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. + +""" +version interface +""" + +from novaclient import base + + +class Version(base.Resource): + """ + Compute REST API information + """ + def __repr__(self): + return "" + + +class VersionManager(base.ManagerWithFind): + resource_class = Version + + def list(self): + """List all versions.""" + return self._list(None, "versions") diff --git a/awx/lib/site-packages/novaclient/v2/client.py b/awx/lib/site-packages/novaclient/v2/client.py index bbb8949d94..e087ac68c1 100644 --- a/awx/lib/site-packages/novaclient/v2/client.py +++ b/awx/lib/site-packages/novaclient/v2/client.py @@ -104,36 +104,6 @@ class Client(object): cacert=None, tenant_id=None, user_id=None, connection_pool=False, session=None, auth=None, **kwargs): - """ - :param str username: Username - :param str api_key: API Key - :param str project_id: Project ID - :param str auth_url: Auth URL - :param bool insecure: Allow insecure - :param float timeout: API timeout, None or 0 disables - :param str proxy_tenant_id: Tenant ID - :param str proxy_token: Proxy Token - :param str region_name: Region Name - :param str endpoint_type: Endpoint Type - :param str extensions: Exensions - :param str service_type: Service Type - :param str service_name: Service Name - :param str volume_service_name: Volume Service Name - :param bool timings: Timings - :param str bypass_url: Bypass URL - :param bool os_cache: OS cache - :param bool no_cache: No cache - :param bool http_log_debug: Enable debugging for HTTP connections - :param str auth_system: Auth system - :param str auth_plugin: Auth plugin - :param str auth_token: Auth token - :param str cacert: cacert - :param str tenant_id: Tenant ID - :param str user_id: User ID - :param bool connection_pool: Use a connection pool - :param str session: Session - :param str auth: Auth - """ # FIXME(comstud): Rename the api_key argument above when we # know it's not being used as keyword argument diff --git a/awx/lib/site-packages/novaclient/v2/servers.py b/awx/lib/site-packages/novaclient/v2/servers.py index 397a12ac11..8055f15092 100644 --- a/awx/lib/site-packages/novaclient/v2/servers.py +++ b/awx/lib/site-packages/novaclient/v2/servers.py @@ -423,6 +423,40 @@ class ServerManager(base.BootingManagerWithFind): 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 coerce 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) If True, enable config drive + on the server. + :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, @@ -839,15 +873,10 @@ class ServerManager(base.BootingManagerWithFind): 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 min_count: (optional extension) The minimum number of - servers to launch. - :param max_count: (optional extension) The maximum number of - servers to launch. - :param security_groups: A list of security group names :param userdata: user data to pass to be exposed by the metadata server this can be a file type object as well or a string. + :param reservation_id: a UUID for the set of servers being requested. :param key_name: (optional extension) name of previously created keypair to inject into the instance. :param availability_zone: Name of the availability zone for instance diff --git a/awx/lib/site-packages/novaclient/v2/shell.py b/awx/lib/site-packages/novaclient/v2/shell.py index 7e3a531001..f030e768a0 100644 --- a/awx/lib/site-packages/novaclient/v2/shell.py +++ b/awx/lib/site-packages/novaclient/v2/shell.py @@ -372,7 +372,7 @@ def _boot(cs, args): dest='files', default=[], help=_("Store arbitrary files from locally to " - "on the new server. Limited by the injected_files quota value.")) + "on the new server. You may store up to 5 files.")) @cliutils.arg( '--key-name', default=os.environ.get('NOVACLIENT_DEFAULT_KEY_NAME'), @@ -1897,17 +1897,6 @@ def _find_image(cs, image): def _find_flavor(cs, flavor): """Get a flavor by name, ID, or RAM size.""" try: - # isinstance() is being used to check if flavor is an instance of - # integer. It will help us to check if the user has entered flavor - # name or flavorid. If flavor name has been entered it is being - # converted to lowercase using lower(). Incase it is an ID the user - # has passed it will not go through the "flavor = flavor.lower()" - # code.The reason for checking if it is a flavor name or flavorid is - # that int has no lower() so it will give an error. - if isinstance(flavor, six.integer_types): - pass - else: - flavor = flavor.lower() return utils.find_resource(cs.flavors, flavor, is_public=None) except exceptions.NotFound: return cs.flavors.find(ram=flavor) @@ -1983,6 +1972,7 @@ def _translate_availability_zone_keys(collection): type=int, const=1, help=argparse.SUPPRESS) +@cliutils.service_type('volume') def do_volume_list(cs, args): """List all the volumes.""" search_opts = {'all_tenants': args.all_tenants} @@ -2001,6 +1991,7 @@ def do_volume_list(cs, args): 'volume', metavar='', help=_('Name or ID of the volume.')) +@cliutils.service_type('volume') def do_volume_show(cs, args): """Show details about a volume.""" volume = _find_volume(cs, args.volume) @@ -2053,6 +2044,7 @@ def do_volume_show(cs, args): '--availability-zone', metavar='', help=_('Optional Availability Zone for volume. (Default=None)'), default=None) +@cliutils.service_type('volume') def do_volume_create(cs, args): """Add a new volume.""" volume = cs.volumes.create(args.size, @@ -2069,6 +2061,7 @@ def do_volume_create(cs, args): 'volume', metavar='', nargs='+', help=_('Name or ID of the volume(s) to delete.')) +@cliutils.service_type('volume') def do_volume_delete(cs, args): """Remove volume(s).""" for volume in args.volume: @@ -2135,6 +2128,7 @@ def do_volume_detach(cs, args): args.attachment_id) +@cliutils.service_type('volume') def do_volume_snapshot_list(cs, _args): """List all the snapshots.""" snapshots = cs.volume_snapshots.list() @@ -2147,6 +2141,7 @@ def do_volume_snapshot_list(cs, _args): 'snapshot', metavar='', help=_('Name or ID of the snapshot.')) +@cliutils.service_type('volume') def do_volume_snapshot_show(cs, args): """Show details about a snapshot.""" snapshot = _find_volume_snapshot(cs, args.snapshot) @@ -2179,6 +2174,7 @@ def do_volume_snapshot_show(cs, args): @cliutils.arg( '--display_description', help=argparse.SUPPRESS) +@cliutils.service_type('volume') def do_volume_snapshot_create(cs, args): """Add a new snapshot.""" snapshot = cs.volume_snapshots.create(args.volume_id, @@ -2192,6 +2188,7 @@ def do_volume_snapshot_create(cs, args): 'snapshot', metavar='', help=_('Name or ID of the snapshot to delete.')) +@cliutils.service_type('volume') def do_volume_snapshot_delete(cs, args): """Remove a snapshot.""" snapshot = _find_volume_snapshot(cs, args.snapshot) @@ -2202,6 +2199,7 @@ def _print_volume_type_list(vtypes): utils.print_list(vtypes, ['ID', 'Name']) +@cliutils.service_type('volume') def do_volume_type_list(cs, args): """Print a list of available 'volume types'.""" vtypes = cs.volume_types.list() @@ -2212,6 +2210,7 @@ def do_volume_type_list(cs, args): 'name', metavar='', help=_("Name of the new volume type")) +@cliutils.service_type('volume') def do_volume_type_create(cs, args): """Create a new volume type.""" vtype = cs.volume_types.create(args.name) @@ -2222,6 +2221,7 @@ def do_volume_type_create(cs, args): 'id', metavar='', help=_("Unique ID of the volume type to delete")) +@cliutils.service_type('volume') def do_volume_type_delete(cs, args): """Delete a specific volume type.""" cs.volume_types.delete(args.id) @@ -4326,9 +4326,7 @@ def do_server_group_list(cs, args): def do_secgroup_list_default_rules(cs, args): - """List rules that will be added to the 'default' security group for - new tenants. - """ + """List rules for the default security group.""" _print_secgroup_rules(cs.security_group_default_rules.list(), show_source_group=False) @@ -4347,9 +4345,7 @@ def do_secgroup_list_default_rules(cs, args): help=_('Port at end of range.')) @cliutils.arg('cidr', metavar='', help=_('CIDR for address range.')) def do_secgroup_add_default_rule(cs, args): - """Add a rule to the set of rules that will be added to the 'default' - security group for new tenants. - """ + """Add a rule to the default security group.""" rule = cs.security_group_default_rules.create(args.ip_proto, args.from_port, args.to_port, @@ -4371,9 +4367,7 @@ def do_secgroup_add_default_rule(cs, args): help=_('Port at end of range.')) @cliutils.arg('cidr', metavar='', help=_('CIDR for address range.')) def do_secgroup_delete_default_rule(cs, args): - """Delete a rule from the set of rules that will be added to the - 'default' security group for new tenants. - """ + """Delete a rule from the default security group.""" for rule in cs.security_group_default_rules.list(): if (rule.ip_protocol and rule.ip_protocol.upper() == args.ip_proto.upper() and diff --git a/awx/lib/site-packages/novaclient/v2/volume_snapshots.py b/awx/lib/site-packages/novaclient/v2/volume_snapshots.py index 6ff6deb9e1..d3bc91808f 100644 --- a/awx/lib/site-packages/novaclient/v2/volume_snapshots.py +++ b/awx/lib/site-packages/novaclient/v2/volume_snapshots.py @@ -55,12 +55,11 @@ class SnapshotManager(base.ManagerWithFind): :param display_description: Description of the snapshot :rtype: :class:`Snapshot` """ - with self.alternate_service_type('volume'): - body = {'snapshot': {'volume_id': volume_id, - 'force': force, - 'display_name': display_name, - 'display_description': display_description}} - return self._create('/snapshots', body, 'snapshot') + body = {'snapshot': {'volume_id': volume_id, + 'force': force, + 'display_name': display_name, + 'display_description': display_description}} + return self._create('/snapshots', body, 'snapshot') def get(self, snapshot_id): """ @@ -69,8 +68,7 @@ class SnapshotManager(base.ManagerWithFind): :param snapshot_id: The ID of the snapshot to get. :rtype: :class:`Snapshot` """ - with self.alternate_service_type('volume'): - return self._get("/snapshots/%s" % snapshot_id, "snapshot") + return self._get("/snapshots/%s" % snapshot_id, "snapshot") def list(self, detailed=True): """ @@ -78,11 +76,10 @@ class SnapshotManager(base.ManagerWithFind): :rtype: list of :class:`Snapshot` """ - with self.alternate_service_type('volume'): - if detailed is True: - return self._list("/snapshots/detail", "snapshots") - else: - return self._list("/snapshots", "snapshots") + if detailed is True: + return self._list("/snapshots/detail", "snapshots") + else: + return self._list("/snapshots", "snapshots") def delete(self, snapshot): """ @@ -90,5 +87,4 @@ class SnapshotManager(base.ManagerWithFind): :param snapshot: The :class:`Snapshot` to delete. """ - with self.alternate_service_type('volume'): - self._delete("/snapshots/%s" % base.getid(snapshot)) + self._delete("/snapshots/%s" % base.getid(snapshot)) diff --git a/awx/lib/site-packages/novaclient/v2/volume_types.py b/awx/lib/site-packages/novaclient/v2/volume_types.py index e36225b458..3d1c7f5316 100644 --- a/awx/lib/site-packages/novaclient/v2/volume_types.py +++ b/awx/lib/site-packages/novaclient/v2/volume_types.py @@ -41,8 +41,7 @@ class VolumeTypeManager(base.ManagerWithFind): :rtype: list of :class:`VolumeType`. """ - with self.alternate_service_type('volume'): - return self._list("/types", "volume_types") + return self._list("/types", "volume_types") def get(self, volume_type): """ @@ -51,9 +50,7 @@ class VolumeTypeManager(base.ManagerWithFind): :param volume_type: The ID of the :class:`VolumeType` to get. :rtype: :class:`VolumeType` """ - with self.alternate_service_type('volume'): - return self._get("/types/%s" % base.getid(volume_type), - "volume_type") + return self._get("/types/%s" % base.getid(volume_type), "volume_type") def delete(self, volume_type): """ @@ -61,8 +58,7 @@ class VolumeTypeManager(base.ManagerWithFind): :param volume_type: The ID of the :class:`VolumeType` to get. """ - with self.alternate_service_type('volume'): - self._delete("/types/%s" % base.getid(volume_type)) + self._delete("/types/%s" % base.getid(volume_type)) def create(self, name): """ @@ -71,10 +67,11 @@ class VolumeTypeManager(base.ManagerWithFind): :param name: Descriptive name of the volume type :rtype: :class:`VolumeType` """ - with self.alternate_service_type('volume'): - body = { - "volume_type": { - "name": name, - } + + body = { + "volume_type": { + "name": name, } - return self._create("/types", body, "volume_type") + } + + return self._create("/types", body, "volume_type") diff --git a/awx/lib/site-packages/novaclient/v2/volumes.py b/awx/lib/site-packages/novaclient/v2/volumes.py index 182698ff51..9b9cf18862 100644 --- a/awx/lib/site-packages/novaclient/v2/volumes.py +++ b/awx/lib/site-packages/novaclient/v2/volumes.py @@ -60,16 +60,14 @@ class VolumeManager(base.ManagerWithFind): :rtype: :class:`Volume` :param imageRef: reference to an image stored in glance """ - # NOTE(melwitt): Ensure we use the volume endpoint for this call - with self.alternate_service_type('volume'): - body = {'volume': {'size': size, - 'snapshot_id': snapshot_id, - 'display_name': display_name, - 'display_description': display_description, - 'volume_type': volume_type, - 'availability_zone': availability_zone, - 'imageRef': imageRef}} - return self._create('/volumes', body, 'volume') + body = {'volume': {'size': size, + 'snapshot_id': snapshot_id, + 'display_name': display_name, + 'display_description': display_description, + 'volume_type': volume_type, + 'availability_zone': availability_zone, + 'imageRef': imageRef}} + return self._create('/volumes', body, 'volume') def get(self, volume_id): """ @@ -78,8 +76,7 @@ class VolumeManager(base.ManagerWithFind): :param volume_id: The ID of the volume to get. :rtype: :class:`Volume` """ - with self.alternate_service_type('volume'): - return self._get("/volumes/%s" % volume_id, "volume") + return self._get("/volumes/%s" % volume_id, "volume") def list(self, detailed=True, search_opts=None): """ @@ -87,21 +84,16 @@ class VolumeManager(base.ManagerWithFind): :rtype: list of :class:`Volume` """ - with self.alternate_service_type('volume'): - search_opts = search_opts or {} + search_opts = search_opts or {} - if 'name' in search_opts.keys(): - search_opts['display_name'] = search_opts.pop('name') + qparams = dict((k, v) for (k, v) in six.iteritems(search_opts) if v) - qparams = dict((k, v) for (k, v) in - six.iteritems(search_opts) if v) + query_string = '?%s' % parse.urlencode(qparams) if qparams else '' - query_str = '?%s' % parse.urlencode(qparams) if qparams else '' - - if detailed is True: - return self._list("/volumes/detail%s" % query_str, "volumes") - else: - return self._list("/volumes%s" % query_str, "volumes") + if detailed is True: + return self._list("/volumes/detail%s" % query_string, "volumes") + else: + return self._list("/volumes%s" % query_string, "volumes") def delete(self, volume): """ @@ -109,8 +101,7 @@ class VolumeManager(base.ManagerWithFind): :param volume: The :class:`Volume` to delete. """ - with self.alternate_service_type('volume'): - self._delete("/volumes/%s" % base.getid(volume)) + self._delete("/volumes/%s" % base.getid(volume)) def create_server_volume(self, server_id, volume_id, device): """ diff --git a/awx/lib/site-packages/novaclient/v3/client.py b/awx/lib/site-packages/novaclient/v3/client.py index 28ac908b73..a30f9191e1 100644 --- a/awx/lib/site-packages/novaclient/v3/client.py +++ b/awx/lib/site-packages/novaclient/v3/client.py @@ -40,6 +40,19 @@ class Client(object): >>> client = Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL) + Or, alternatively, you can create a client instance using the + keystoneclient.session API:: + + >>> from keystoneclient.auth.identity import v2 + >>> from keystoneclient import session + >>> from novaclient.client import Client + >>> auth = v2.Password(auth_url=AUTH_URL, + username=USERNAME, + password=PASSWORD, + tenant_name=PROJECT_ID) + >>> sess = session.Session(auth=auth) + >>> nova = client.Client(VERSION, session=sess) + Then call methods on its managers:: >>> client.servers.list() @@ -84,7 +97,7 @@ class Client(object): self.tenant_id = tenant_id self.user_id = user_id self.os_cache = os_cache or not no_cache - #TODO(bnemec): Add back in v3 extensions + # TODO(bnemec): Add back in v3 extensions self.agents = agents.AgentsManager(self) self.aggregates = aggregates.AggregateManager(self) self.availability_zones = \ diff --git a/awx/lib/site-packages/novaclient/v3/flavors.py b/awx/lib/site-packages/novaclient/v3/flavors.py index 60553b2f59..e65f2f1f4e 100644 --- a/awx/lib/site-packages/novaclient/v3/flavors.py +++ b/awx/lib/site-packages/novaclient/v3/flavors.py @@ -97,7 +97,7 @@ class FlavorManager(flavors.FlavorManager): "id": id, "swap": swap, "ephemeral": ephemeral, - "rxtx_factor": rxtx_factor, + "os-flavor-rxtx:rxtx_factor": rxtx_factor, "flavor-access:is_public": is_public, } } diff --git a/awx/lib/site-packages/novaclient/v3/hosts.py b/awx/lib/site-packages/novaclient/v3/hosts.py index 5327a2bf35..174b623342 100644 --- a/awx/lib/site-packages/novaclient/v3/hosts.py +++ b/awx/lib/site-packages/novaclient/v3/hosts.py @@ -33,3 +33,19 @@ class HostManager(hosts.HostManager): """Perform an action on a host.""" url = '/os-hosts/{0}/{1}'.format(host, action) return self._get(url, response_key='host') + + def list(self, zone=None, service=None): + """List cloud hosts.""" + + filters = [] + if zone: + filters.append('zone=%s' % zone) + if service: + filters.append('service=%s' % service) + + if filters: + url = '/os-hosts?%s' % '&'.join(filters) + else: + url = '/os-hosts' + + return self._list(url, "hosts") diff --git a/awx/lib/site-packages/novaclient/v3/images.py b/awx/lib/site-packages/novaclient/v3/images.py index b78d9af552..7bc3d584c7 100644 --- a/awx/lib/site-packages/novaclient/v3/images.py +++ b/awx/lib/site-packages/novaclient/v3/images.py @@ -17,10 +17,11 @@ Image interface. """ +from oslo.utils import encodeutils +from oslo.utils import strutils from six.moves.urllib import parse from novaclient import base -from novaclient.openstack.common import strutils class Image(base.Resource): @@ -51,7 +52,7 @@ class ImageManager(base.ManagerWithFind): def _image_meta_from_headers(self, headers): meta = {'properties': {}} - safe_decode = strutils.safe_decode + safe_decode = encodeutils.safe_decode for key, value in headers.items(): value = safe_decode(value, incoming='utf-8') if key.startswith('x-image-meta-property-'): diff --git a/awx/lib/site-packages/novaclient/v3/servers.py b/awx/lib/site-packages/novaclient/v3/servers.py index 0e028b5da3..70bc5a54bf 100644 --- a/awx/lib/site-packages/novaclient/v3/servers.py +++ b/awx/lib/site-packages/novaclient/v3/servers.py @@ -21,13 +21,13 @@ Server interface. import base64 +from oslo.utils import encodeutils import six from six.moves.urllib import parse from novaclient import base from novaclient import crypto from novaclient.openstack.common.gettextutils import _ -from novaclient.openstack.common import strutils REBOOT_SOFT, REBOOT_HARD = 'SOFT', 'HARD' @@ -320,7 +320,7 @@ class Server(base.Resource): """ self.manager.reset_network(self) - def evacuate(self, host, on_shared_storage, password=None): + def evacuate(self, host=None, on_shared_storage=True, password=None): """ Evacuate an instance from failed host to specified host. @@ -402,10 +402,10 @@ class ServerManager(base.BootingManagerWithFind): if six.PY3: userdata = userdata.encode("utf-8") else: - userdata = strutils.safe_encode(userdata) + userdata = encodeutils.safe_encode(userdata) data = base64.b64encode(userdata).decode('utf-8') - body["server"]["os-user-data:user_data"] = data + body["server"]["user_data"] = data if meta: body["server"]["metadata"] = meta if reservation_id: @@ -428,7 +428,7 @@ class ServerManager(base.BootingManagerWithFind): body["server"]["os-multiple-create:max_count"] = max_count if security_groups: - body["server"]["os-security-groups:security_groups"] = \ + body["server"]["security_groups"] = \ [{'name': sg} for sg in security_groups] if availability_zone: @@ -437,11 +437,12 @@ class ServerManager(base.BootingManagerWithFind): # Block device mappings are passed as a list of dictionaries if block_device_mapping: - bdm_param = 'os-block-device-mapping:block_device_mapping' + bdm_param = 'block_device_mapping' body['server'][bdm_param] = \ 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 + bdm_param = 'block_device_mapping_v2' if image: bdm_dict = {'uuid': image.id, 'source_type': 'image', 'destination_type': 'local', 'boot_index': 0, @@ -950,7 +951,8 @@ class ServerManager(base.BootingManagerWithFind): """ self._action('reset_network', server) - def evacuate(self, server, host, on_shared_storage, password=None): + def evacuate(self, server, host=None, + on_shared_storage=True, password=None): """ Evacuate a server instance. @@ -960,10 +962,9 @@ class ServerManager(base.BootingManagerWithFind): on shared storage :param password: string to set as password on the evacuated server. """ - body = { - 'host': host, - 'on_shared_storage': on_shared_storage, - } + body = {'on_shared_storage': on_shared_storage} + if host is not None: + body['host'] = host if password is not None: body['admin_password'] = password diff --git a/awx/lib/site-packages/novaclient/v3/shell.py b/awx/lib/site-packages/novaclient/v3/shell.py index 9f4b13bdeb..3d17e28906 100644 --- a/awx/lib/site-packages/novaclient/v3/shell.py +++ b/awx/lib/site-packages/novaclient/v3/shell.py @@ -27,11 +27,12 @@ import os import sys import time +from oslo.utils import strutils +from oslo.utils import timeutils import six from novaclient import exceptions -from novaclient.openstack.common import strutils -from novaclient.openstack.common import timeutils +from novaclient.openstack.common.gettextutils import _ from novaclient.openstack.common import uuidutils from novaclient import utils from novaclient.v3 import availability_zones @@ -258,6 +259,7 @@ def _boot(cs, args): help="Store arbitrary files from locally to " "on the new server. You may store up to 5 files.") @utils.arg('--key-name', + default=os.environ.get('NOVACLIENT_DEFAULT_KEY_NAME'), metavar='', help="Key name of keypair that should be created earlier with \ the command keypair-add") @@ -352,7 +354,7 @@ def _poll_for_status(poll_fn, obj_id, action, final_ok_states, sys.stdout.flush() if not silent: - print + print() while True: obj = poll_fn(obj_id) @@ -722,15 +724,15 @@ def _filter_network_create_options(args): help='gateway') @utils.arg('--gateway-v6', dest="gateway_v6", - help='ipv6 gateway') + help='IPv6 gateway') @utils.arg('--bridge', dest="bridge", metavar='', - help='VIFs on this network are connected to this bridge') + help='VIFs on this network are connected to this bridge.') @utils.arg('--bridge-interface', dest="bridge_interface", metavar='', - help='the bridge is connected to this interface') + help='The bridge is connected to this interface.') @utils.arg('--multi-host', dest="multi_host", metavar="<'T'|'F'>", @@ -749,11 +751,11 @@ def _filter_network_create_options(args): @utils.arg('--fixed-cidr', dest="fixed_cidr", metavar='', - help='IPv4 subnet for fixed IPS (ex: 10.20.0.0/16)') + help='IPv4 subnet for fixed IPs (ex: 10.20.0.0/16)') @utils.arg('--project-id', dest="project_id", metavar="", - help='Project id') + help='Project ID') @utils.arg('--priority', dest="priority", metavar="", @@ -763,7 +765,7 @@ def do_network_create(cs, args): if not (args.cidr or args.cidr_v6): raise exceptions.CommandError( - "Must specify eith fixed_range_v4 or fixed_range_v6") + "Must specify either fixed_range_v4 or fixed_range_v6") kwargs = _filter_network_create_options(args) if args.multi_host is not None: kwargs['multi_host'] = bool(args.multi_host == 'T' or @@ -775,7 +777,7 @@ def do_network_create(cs, args): @utils.arg('--limit', dest="limit", metavar="", - help='number of images to return per request') + help='Number of images to return per request.') @utils.service_type('image') def do_image_list(cs, _args): """Print a list of available images to boot from.""" @@ -948,7 +950,7 @@ def do_image_delete(cs, args): const=1, help=argparse.SUPPRESS) @utils.arg('--tenant', - #nova db searches by project_id + # nova db searches by project_id dest='tenant', metavar='', nargs='?', @@ -1332,6 +1334,13 @@ def _print_server(cs, args, server=None): info['flavor'] = '%s (%s)' % (_find_flavor(cs, flavor_id).name, flavor_id) + if 'security_groups' in info: + # when we have multiple nics the info will include the + # security groups N times where N == number of nics. Be nice + # and only display it once. + info['security_groups'] = ', '.join( + sorted(set(group['name'] for group in info['security_groups']))) + image = info.get('image', {}) if image: image_id = image.get('id', '') @@ -1367,19 +1376,19 @@ def do_show(cs, args): help='Name or ID of server(s).') def do_delete(cs, args): """Immediately shut down and delete specified server(s).""" - failure_count = 0 + failure_flag = False for server in args.server: try: _find_server(cs, server).delete() print("Request to delete server %s has been accepted." % server) except Exception as e: - failure_count += 1 + failure_flag = True print(e) - if failure_count == len(args.server): - raise exceptions.CommandError("Unable to delete any of the specified " - "servers.") + if failure_flag: + raise exceptions.CommandError("Unable to delete the specified " + "server(s).") def _find_server(cs, server): @@ -2303,6 +2312,14 @@ def do_aggregate_set_metadata(cs, args): """Update the metadata associated with the aggregate.""" aggregate = _find_aggregate(cs, args.aggregate) metadata = _extract_metadata(args) + currentmetadata = getattr(aggregate, 'metadata', {}) + if set(metadata.items()) & set(currentmetadata.items()): + raise exceptions.CommandError("metadata already exists") + for key, value in metadata.items(): + if value is None and key not in currentmetadata: + raise exceptions.CommandError("metadata key %s does not exist" + " hence can not be deleted" + % key) aggregate = cs.aggregates.set_metadata(aggregate.id, metadata) print("Metadata has been successfully updated for aggregate %s." % aggregate.id) @@ -2492,10 +2509,13 @@ def do_host_describe(cs, args): @utils.arg('--zone', metavar='', default=None, help='Filters the list, returning only those ' 'hosts in the availability zone .') +@utils.arg('--service-name', metavar='', default=None, + help='Filters the list, returning only those ' + 'hosts providing service .') def do_host_list(cs, args): """List all hosts by service.""" columns = ["host_name", "service", "zone"] - result = cs.hosts.list(args.zone) + result = cs.hosts.list(args.zone, args.service_name) utils.print_list(result, columns) @@ -2626,9 +2646,41 @@ def ensure_service_catalog_present(cs): def do_endpoints(cs, _args): """Discover endpoints that get returned from the authenticate services.""" ensure_service_catalog_present(cs) + catalog = cs.client.service_catalog.catalog - for e in catalog['access']['serviceCatalog']: - utils.print_dict(e['endpoints'][0], e['name']) + region = cs.client.region_name + + for service in catalog['access']['serviceCatalog']: + name, endpoints = service["name"], service["endpoints"] + + try: + endpoint = _get_first_endpoint(endpoints, region) + utils.print_dict(endpoint, name) + except LookupError: + print(_("WARNING: %(service)s has no endpoint in %(region)s! " + "Available endpoints for this service:") % + {'service': name, 'region': region}) + for other_endpoint in endpoints: + utils.print_dict(other_endpoint, name) + + +def _get_first_endpoint(endpoints, region): + """Find the first suitable endpoint in endpoints. + + If there is only one endpoint, return it. If there is more than + one endpoint, return the first one with the given region. If there + are no endpoints, or there is more than one endpoint but none of + them match the given region, raise KeyError. + + """ + if len(endpoints) == 1: + return endpoints[0] + else: + for candidate_endpoint in endpoints: + if candidate_endpoint["region"] == region: + return candidate_endpoint + + raise LookupError("No suitable endpoint found") @utils.arg('--wrap', dest='wrap', metavar='', default=64, @@ -2663,8 +2715,16 @@ def do_extension_list(cs, _args): dest='private', action='store_true', default=False, - help='Optional flag to indicate whether to use private address ' - 'attached to a server. (Default=False)') + help=argparse.SUPPRESS) +@utils.arg('--address-type', + dest='address_type', + action='store', + type=str, + default='floating', + help='Optional flag to indicate which IP type to use. Possible values ' + 'includes fixed and floating (the Default).') +@utils.arg('--network', metavar='', + help='Network to use for the ssh.', default=None) @utils.arg('--ipv6', dest='ipv6', action='store_true', @@ -2688,35 +2748,62 @@ def do_ssh(cs, args): args.server = server addresses = _find_server(cs, args.server).addresses - address_type = "private" if args.private else "public" + address_type = "fixed" if args.private else args.address_type version = 6 if args.ipv6 else 4 + pretty_version = 'IPv%d' % version - if address_type not in addresses: - print("ERROR: No %s addresses found for '%s'." % (address_type, - args.server)) - return + # Select the network to use. + if args.network: + network_addresses = addresses.get(args.network) + if not network_addresses: + msg = _("Server '%(server)s' is not attached to network " + "'%(network)s'") + raise exceptions.ResourceNotFound( + msg % {'server': args.server, 'network': args.network}) + else: + if len(addresses) > 1: + msg = _("Server '%(server)s' is attached to more than one network." + " Please pick the network to use.") + raise exceptions.CommandError(msg % {'server': args.server}) + elif not addresses: + msg = _("Server '%(server)s' is not attached to any network.") + raise exceptions.CommandError(msg % {'server': args.server}) + else: + network_addresses = list(six.itervalues(addresses))[0] - ip_address = None - for address in addresses[address_type]: - if address['version'] == version: - ip_address = address['addr'] - break + # Select the address in the selected network. + # If the extension is not present, we assume the address to be floating. + match = lambda addr: all(( + addr.get('version') == version, + addr.get('OS-EXT-IPS:type', 'floating') == address_type)) + matching_addresses = [address.get('addr') for address in network_addresses + if match(address)] + if not any(matching_addresses): + msg = _("No address that would match network '%(network)s'" + " and type '%(address_type)s' of version %(pretty_version)s " + "has been found for server '%(server)s'.") + raise exceptions.ResourceNotFound(msg % { + 'network': args.network, 'address_type': address_type, + 'pretty_version': pretty_version, 'server': args.server}) + elif len(matching_addresses) > 1: + msg = _("More than one %(pretty_version)s %(address_type)s address" + "found.") + raise exceptions.CommandError(msg % {'pretty_version': pretty_version, + 'address_type': address_type}) + else: + ip_address = matching_addresses[0] identity = '-i %s' % args.identity if len(args.identity) else '' - if ip_address: - os.system("ssh -%d -p%d %s %s@%s %s" % (version, args.port, identity, - args.login, ip_address, - args.extra)) - else: - pretty_version = "IPv%d" % version - print("ERROR: No %s %s address found." % (address_type, - pretty_version)) - return + cmd = "ssh -%d -p%d %s %s@%s %s" % (version, args.port, identity, + args.login, ip_address, args.extra) + logger.debug("Executing cmd '%s'", cmd) + os.system(cmd) _quota_resources = ['instances', 'cores', 'ram', - 'fixed_ips', 'metadata_items', 'key_pairs'] + 'fixed_ips', 'metadata_items', 'key_pairs', + 'server_groups', 'server_group_members'] def _quota_show(quotas): @@ -2833,6 +2920,16 @@ def do_quota_defaults(cs, args): type=int, default=None, help='New value for the "key-pairs" quota.') +@utils.arg('--server-groups', + metavar='', + type=int, + default=None, + help='New value for the "server-groups" quota.') +@utils.arg('--server-group-members', + metavar='', + type=int, + default=None, + help='New value for the "server-group-members" quota.') @utils.arg('--force', dest='force', action="store_true", @@ -2847,6 +2944,7 @@ def do_quota_update(cs, args): @utils.arg('--tenant', metavar='', + required=True, help='ID of tenant to delete quota for.') def do_quota_delete(cs, args): """Delete quota for a tenant so their quota will revert back to default.""" @@ -2855,11 +2953,12 @@ def do_quota_delete(cs, args): @utils.arg('server', metavar='', help='Name or ID of server.') -@utils.arg('host', metavar='', help='Name or ID of target host.') +@utils.arg('host', metavar='', nargs='?', + help="Name or ID of the target host. " + "If no host is specified, the scheduler will choose one.") @utils.arg('--password', dest='password', metavar='', - default=None, help="Set the provided password on the evacuated server. Not applicable " "with on-shared-storage flag") @utils.arg('--on-shared-storage', diff --git a/awx/lib/site-packages/pyrax/__init__.py b/awx/lib/site-packages/pyrax/__init__.py index b63526e5c0..7e95ca30c3 100644 --- a/awx/lib/site-packages/pyrax/__init__.py +++ b/awx/lib/site-packages/pyrax/__init__.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c)2012 Rackspace US, Inc. @@ -53,6 +52,7 @@ try: from . import exceptions as exc from . import http from . import version + __version__ = version.version from novaclient import exceptions as _cs_exceptions from novaclient import auth_plugin as _cs_auth_plugin diff --git a/awx/lib/site-packages/pyrax/autoscale.py b/awx/lib/site-packages/pyrax/autoscale.py index 91fab8e3f0..6528454a2d 100644 --- a/awx/lib/site-packages/pyrax/autoscale.py +++ b/awx/lib/site-packages/pyrax/autoscale.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c)2013 Rackspace US, Inc. @@ -107,7 +106,7 @@ class ScalingGroup(BaseResource): def update_launch_config(self, server_name=None, image=None, flavor=None, disk_config=None, metadata=None, personality=None, networks=None, - load_balancers=None, key_name=None): + load_balancers=None, key_name=None, config_drive=False, user_data=None): """ Updates the server launch configuration for this scaling group. One or more of the available attributes can be specified. @@ -119,7 +118,8 @@ class ScalingGroup(BaseResource): return self.manager.update_launch_config(self, server_name=server_name, image=image, flavor=flavor, disk_config=disk_config, metadata=metadata, personality=personality, networks=networks, - load_balancers=load_balancers, key_name=key_name) + load_balancers=load_balancers, key_name=key_name, + config_drive=config_drive, user_data=user_data) def update_launch_metadata(self, metadata): @@ -388,6 +388,11 @@ class ScalingGroupManager(BaseManager): """ Returns the launch configuration for the specified scaling group. """ + key_map = { + "OS-DCF:diskConfig": "disk_config", + "flavorRef": "flavor", + "imageRef": "image", + } uri = "/%s/%s/launch" % (self.uri_base, utils.get_id(scaling_group)) resp, resp_body = self.api.method_get(uri) ret = {} @@ -395,22 +400,16 @@ class ScalingGroupManager(BaseManager): ret["type"] = data.get("type") args = data.get("args", {}) ret["load_balancers"] = args.get("loadBalancers") - srv = args.get("server", {}) - ret["name"] = srv.get("name") - ret["flavor"] = srv.get("flavorRef") - ret["image"] = srv.get("imageRef") - ret["disk_config"] = srv.get("OS-DCF:diskConfig") - ret["metadata"] = srv.get("metadata") - ret["personality"] = srv.get("personality") - ret["networks"] = srv.get("networks") - ret["key_name"] = srv.get("key_name") + for key, value in args.get("server", {}).items(): + norm_key = key_map.get(key, key) + ret[norm_key] = value return ret def replace_launch_config(self, scaling_group, launch_config_type, server_name, image, flavor, disk_config=None, metadata=None, personality=None, networks=None, load_balancers=None, - key_name=None): + key_name=None, config_drive=False, user_data=None): """ Replace an existing launch configuration. All of the attributes must be specified. If you wish to delete any of the optional attributes, pass @@ -422,13 +421,15 @@ class ScalingGroupManager(BaseManager): launch_config_type=launch_config_type, server_name=server_name, image=image, flavor=flavor, disk_config=disk_config, metadata=metadata, personality=personality, networks=networks, - load_balancers=load_balancers, key_name=key_name) + load_balancers=load_balancers, key_name=key_name, + config_drive=config_drive, user_data=user_data) resp, resp_body = self.api.method_put(uri, body=body) def update_launch_config(self, scaling_group, server_name=None, image=None, flavor=None, disk_config=None, metadata=None, personality=None, - networks=None, load_balancers=None, key_name=None): + networks=None, load_balancers=None, key_name=None, config_drive=False, + user_data=None): """ Updates the server launch configuration for an existing scaling group. One or more of the available attributes can be specified. @@ -445,7 +446,13 @@ class ScalingGroupManager(BaseManager): lb_args = largs.get("loadBalancers", {}) flav = flavor or srv_args.get("flavorRef") dconf = disk_config or srv_args.get("OS-DCF:diskConfig", "AUTO") - pers = personality or srv_args.get("personality", []) + if personality is None: + personality = srv_args.get("personality", []) + cfg_drv = config_drive or srv_args.get("config_drive") + if user_data: + user_data = base64.b64encode(user_data) + usr_data = user_data or srv_args.get("user_data") + update_metadata = metadata or srv_args.get("metadata") body = {"type": "launch_server", "args": { "server": { @@ -454,16 +461,22 @@ class ScalingGroupManager(BaseManager): "flavorRef": flav, "OS-DCF:diskConfig": dconf, "networks": networks or srv_args.get("networks"), - "metadata": metadata or srv_args.get("metadata"), }, "loadBalancers": load_balancers or lb_args, }, } - if pers: - body["args"]["server"]["personality"] = pers + bas = body["args"]["server"] + if cfg_drv: + bas["config_drive"] = cfg_drv + if usr_data: + bas["user_data"] = usr_data + if personality: + bas["personality"] = self._encode_personality(personality) + if update_metadata: + bas["metadata"] = update_metadata key_name = key_name or srv_args.get("key_name") if key_name: - body["args"]["server"] = key_name + bas["key_name"] = key_name resp, resp_body = self.api.method_put(uri, body=body) return None @@ -753,25 +766,31 @@ class ScalingGroupManager(BaseManager): return lb_args + def _encode_personality(self, personality): + """ + Personality files must be base64-encoded before transmitting. + """ + if personality is None: + personality = [] + else: + personality = utils.coerce_to_list(personality) + for pfile in personality: + if "contents" in pfile: + pfile["contents"] = base64.b64encode(pfile["contents"]) + return personality + + def _create_body(self, name, cooldown, min_entities, max_entities, launch_config_type, server_name, image, flavor, disk_config=None, metadata=None, personality=None, networks=None, load_balancers=None, scaling_policies=None, group_metadata=None, - key_name=None): + key_name=None, config_drive=False, user_data=None): """ Used to create the dict required to create any of the following: A Scaling Group """ -# if disk_config is None: -# disk_config = "AUTO" if metadata is None: metadata = {} - if personality is None: - personality = [] - else: - for file in personality: - if "contents" in file: - file["contents"] = base64.b64encode(file["contents"]) if scaling_policies is None: scaling_policies = [] group_config = self._create_group_config_body(name, cooldown, @@ -779,7 +798,8 @@ class ScalingGroupManager(BaseManager): launch_config = self._create_launch_config_body(launch_config_type, server_name, image, flavor, disk_config=disk_config, metadata=metadata, personality=personality, networks=networks, - load_balancers=load_balancers, key_name=key_name) + load_balancers=load_balancers, key_name=key_name, + config_drive=config_drive, user_data=user_data) body = { "groupConfiguration": group_config, "launchConfiguration": launch_config, @@ -807,31 +827,38 @@ class ScalingGroupManager(BaseManager): def _create_launch_config_body(self, launch_config_type, server_name, image, flavor, disk_config=None, metadata=None, personality=None, networks=None, load_balancers=None, - key_name=None): + key_name=None, config_drive=False, user_data=None): + server_args = { "flavorRef": "%s" % flavor, "name": server_name, "imageRef": utils.get_id(image), } + if metadata is not None: server_args["metadata"] = metadata if personality is not None: - server_args["personality"] = personality + server_args["personality"] = self._encode_personality(personality) if networks is not None: server_args["networks"] = networks if disk_config is not None: server_args["OS-DCF:diskConfig"] = disk_config if key_name is not None: server_args["key_name"] = key_name + if config_drive is not False: + server_args['config_drive'] = config_drive + if user_data is not None: + server_args['user_data'] = base64.b64encode(user_data) + if load_balancers is None: load_balancers = [] load_balancer_args = self._resolve_lbs(load_balancers) + return {"type": launch_config_type, "args": {"server": server_args, "loadBalancers": load_balancer_args}} - class AutoScalePolicy(BaseResource): def __init__(self, manager, info, scaling_group, *args, **kwargs): super(AutoScalePolicy, self).__init__(manager, info, *args, **kwargs) @@ -1065,7 +1092,8 @@ class AutoScaleClient(BaseClient): def update_launch_config(self, scaling_group, server_name=None, image=None, flavor=None, disk_config=None, metadata=None, personality=None, - networks=None, load_balancers=None, key_name=None): + networks=None, load_balancers=None, key_name=None, config_drive=False, + user_data=None): """ Updates the server launch configuration for an existing scaling group. One or more of the available attributes can be specified. @@ -1078,7 +1106,8 @@ class AutoScaleClient(BaseClient): server_name=server_name, image=image, flavor=flavor, disk_config=disk_config, metadata=metadata, personality=personality, networks=networks, - load_balancers=load_balancers, key_name=key_name) + load_balancers=load_balancers, key_name=key_name, + config_drive=config_drive, user_data=user_data) def update_launch_metadata(self, scaling_group, metadata): diff --git a/awx/lib/site-packages/pyrax/base_identity.py b/awx/lib/site-packages/pyrax/base_identity.py index 580decbcc3..401a052d89 100644 --- a/awx/lib/site-packages/pyrax/base_identity.py +++ b/awx/lib/site-packages/pyrax/base_identity.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- @@ -606,13 +605,13 @@ class BaseIdentity(object): # Internal Server Error try: error_msg = resp_body[list(resp_body.keys())[0]]["message"] - except KeyError: + except (KeyError, AttributeError): error_msg = "Service Currently Unavailable" raise exc.InternalServerError(error_msg) elif resp.status_code > 299: try: msg = resp_body[list(resp_body.keys())[0]]["message"] - except KeyError: + except (KeyError, AttributeError): msg = None if msg: err = "%s - %s." % (resp.reason, msg) diff --git a/awx/lib/site-packages/pyrax/client.py b/awx/lib/site-packages/pyrax/client.py index 0b8ab0a3ad..e87d0b2bfb 100644 --- a/awx/lib/site-packages/pyrax/client.py +++ b/awx/lib/site-packages/pyrax/client.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright 2010 Jacob Kaplan-Moss @@ -29,7 +28,7 @@ import json import logging import requests import time -import six.moves.urllib as urllib +from six.moves import urllib import pyrax import pyrax.exceptions as exc diff --git a/awx/lib/site-packages/pyrax/cloudblockstorage.py b/awx/lib/site-packages/pyrax/cloudblockstorage.py index 9d13678a3c..67227553c9 100644 --- a/awx/lib/site-packages/pyrax/cloudblockstorage.py +++ b/awx/lib/site-packages/pyrax/cloudblockstorage.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c)2012 Rackspace US, Inc. @@ -246,7 +245,7 @@ class CloudBlockStorageManager(BaseManager): """ def _create_body(self, name, size=None, volume_type=None, description=None, metadata=None, snapshot_id=None, clone_id=None, - availability_zone=None): + availability_zone=None, image=None): """ Used to create the dict required to create a new volume """ @@ -260,6 +259,8 @@ class CloudBlockStorageManager(BaseManager): description = "" if metadata is None: metadata = {} + if image is not None: + image = utils.get_id(image) body = {"volume": { "size": size, "snapshot_id": snapshot_id, @@ -269,6 +270,7 @@ class CloudBlockStorageManager(BaseManager): "volume_type": volume_type, "metadata": metadata, "availability_zone": availability_zone, + "imageRef": image, }} return body diff --git a/awx/lib/site-packages/pyrax/clouddatabases.py b/awx/lib/site-packages/pyrax/clouddatabases.py index 0350c54a48..b2c0476a79 100644 --- a/awx/lib/site-packages/pyrax/clouddatabases.py +++ b/awx/lib/site-packages/pyrax/clouddatabases.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c)2012 Rackspace US, Inc. @@ -82,7 +81,7 @@ class CloudDatabaseManager(BaseManager): def _create_body(self, name, flavor=None, volume=None, databases=None, - users=None): + users=None, version=None, type=None): """ Used to create the dict required to create a Cloud Database instance. """ @@ -95,6 +94,7 @@ class CloudDatabaseManager(BaseManager): databases = [] if users is None: users = [] + body = {"instance": { "name": name, "flavorRef": flavor_ref, @@ -102,6 +102,14 @@ class CloudDatabaseManager(BaseManager): "databases": databases, "users": users, }} + + if type is not None or version is not None: + required = (type, version) + if all(required): + body['instance']['datastore'] = {"type": type, "version": version} + else: + raise exc.MissingCloudDatabaseParameter("Specifying a datastore" + " requires both the datastore type as well as the version.") return body @@ -140,20 +148,24 @@ class CloudDatabaseManager(BaseManager): return CloudDatabaseInstance(self, resp_body.get("instance", {})) - def list_backups(self, instance=None): + def list_backups(self, instance=None, marker=0, limit=20): """ - Returns a list of all backups by default, or just for a particular + Returns a paginated list of backups, or just for a particular instance. """ - return self.api._backup_manager.list(instance=instance) + return self.api._backup_manager.list(instance=instance, limit=limit, + marker=marker) - def _list_backups_for_instance(self, instance): + def _list_backups_for_instance(self, instance, marker=0, limit=20): """ Instance-specific backups are handled through the instance manager, not the backup manager. """ - uri = "/%s/%s/backups" % (self.uri_base, utils.get_id(instance)) + uri = "/%s/%s/backups?limit=%d&marker=%d" % (self.uri_base, + utils.get_id(instance), + int(limit), + int(marker)) resp, resp_body = self.api.method_get(uri) mgr = self.api._backup_manager return [CloudDatabaseBackup(mgr, backup) @@ -199,7 +211,7 @@ class CloudDatabaseUserManager(BaseManager): list of database names. If any of the supplied dbs do not exist, a NoSuchDatabase exception will be raised, unless you pass strict=False. """ - dbs = utils.coerce_string_to_list(dbs) + dbs = utils.coerce_to_list(dbs) db_names = [utils.get_name(db) for db in dbs] if strict: good_dbs = self.instance.list_databases() @@ -315,14 +327,15 @@ class CloudDatabaseBackupManager(BaseManager): return body - def list(self, instance=None): + def list(self, instance=None, limit=20, marker=0): """ - Return a list of all backups by default, or just for a particular + Return a paginated list of backups, or just for a particular instance. """ if instance is None: return super(CloudDatabaseBackupManager, self).list() - return self.api._manager._list_backups_for_instance(instance) + return self.api._manager._list_backups_for_instance(instance, limit=limit, + marker=marker) @@ -535,11 +548,12 @@ class CloudDatabaseInstance(BaseResource): self.manager.action(self, "resize", body=body) - def list_backups(self): + def list_backups(self, limit=20, marker=0): """ - Returns a list of all backups for this instance. + Returns a paginated list of backups for this instance. """ - return self.manager._list_backups_for_instance(self) + return self.manager._list_backups_for_instance(self, limit=limit, + marker=marker) def create_backup(self, name, description=None): @@ -872,12 +886,13 @@ class CloudDatabaseClient(BaseClient): return href - def list_backups(self, instance=None): + def list_backups(self, instance=None, limit=20, marker=0): """ - Returns a list of all backups by default, or just for a particular + Returns a paginated list of backups by default, or just for a particular instance. """ - return self._backup_manager.list(instance=instance) + return self._backup_manager.list(instance=instance, limit=limit, + marker=marker) def get_backup(self, backup): diff --git a/awx/lib/site-packages/pyrax/clouddns.py b/awx/lib/site-packages/pyrax/clouddns.py index f216059556..f2b00f7c6f 100644 --- a/awx/lib/site-packages/pyrax/clouddns.py +++ b/awx/lib/site-packages/pyrax/clouddns.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c)2012 Rackspace US, Inc. @@ -67,13 +66,12 @@ class CloudDNSRecord(BaseResource): ttl = None comment = None - def update(self, data=None, priority=None, ttl=None, comment=None): """ Modifies this record. """ return self.manager.update_record(self.domain_id, self, data=data, - priority=priority, ttl=ttl, comment=comment) + priority=priority, ttl=ttl, comment=comment) def get(self): @@ -232,7 +230,24 @@ class CloudDNSDomain(BaseResource): Modifies an existing record for this domain. """ return self.manager.update_record(self, record, data=data, - priority=priority, ttl=ttl, comment=comment) + priority=priority, ttl=ttl, comment=comment) + + + def update_records(self, records): + """ + Modifies multiple existing records for a domain. Each record to be + updated should be a dict with following required keys: + - id + - name + + Each record must also contain one or more of the following optional + keys: + - data + - ttl + - comment + - priority (optional for MX and SRV records; forbidden otherwise) + """ + return self.manager.update_records(self, records) def delete_record(self, record): @@ -242,6 +257,7 @@ class CloudDNSDomain(BaseResource): return self.manager.delete_record(self, record) + class CloudDNSPTRRecord(object): """ This represents a Cloud DNS PTR record (reverse DNS). @@ -461,10 +477,17 @@ class CloudDNSManager(BaseManager): "DELETE": self.api.method_delete, } api_method = api_methods[method] - if body is None: - resp, resp_body = api_method(uri, *args, **kwargs) - else: - resp, resp_body = api_method(uri, body=body, *args, **kwargs) + try: + if body is None: + resp, resp_body = api_method(uri, *args, **kwargs) + else: + resp, resp_body = api_method(uri, body=body, *args, **kwargs) + except Exception as e: + if error_class: + raise error_class(e) + else: + raise + callbackURL = resp_body["callbackUrl"].split("/status/")[-1] massagedURL = "/status/%s?showDetails=true" % callbackURL start = time.time() @@ -834,7 +857,7 @@ class CloudDNSManager(BaseManager): domain_id = utils.get_id(domain) uri = "/domains/%s/records/%s" % (domain_id, rec_id) resp, resp_body = self._retry_get(uri) - resp_body['domain_id'] = domain_id + resp_body["domain_id"] = domain_id return CloudDNSRecord(self, resp_body, loaded=False) @@ -843,14 +866,27 @@ class CloudDNSManager(BaseManager): """ Modifies an existing record for a domain. """ - rec_id = utils.get_id(record) - uri = "/domains/%s/records/%s" % (utils.get_id(domain), rec_id) - body = {"name": record.name} - all_opts = (("data", data), ("priority", priority), ("ttl", ttl), - ("comment", comment)) - opts = [(k, v) for k, v in all_opts if v is not None] - body.update(dict(opts)) - resp, resp_body = self._async_call(uri, method="PUT", body=body, + rdict = {"id": record.id, + "name": record.name, + } + pdict = {"data": data, + "priority": priority, + "ttl": ttl, + "comment": comment, + } + utils.params_to_dict(pdict, rdict) + return self.update_records(domain, [rdict]) + + + def update_records(self, domain, records): + """ + Modifies an existing records for a domain. + """ + if not isinstance(records, list): + raise TypeError("Expected records of type list") + uri = "/domains/%s/records" % utils.get_id(domain) + resp, resp_body = self._async_call(uri, method="PUT", + body={"records": records}, error_class=exc.DomainRecordUpdateFailed, has_response=False) return resp_body @@ -1276,13 +1312,31 @@ class CloudDNSClient(BaseClient): @assure_domain - def update_record(self, domain, record, data=None, priority=None, - ttl=None, comment=None): + def update_record(self, domain, record, data=None, priority=None, ttl=None, + comment=None): """ Modifies an existing record for a domain. """ - return domain.update_record(record, data=data, - priority=priority, ttl=ttl, comment=comment) + return domain.update_record(record, data=data, priority=priority, + ttl=ttl, comment=comment) + + + @assure_domain + def update_records(self, domain, records): + """ + Modifies multiple existing records for a domain. Each record to be + updated should be a dict with following required keys: + - id + - name + + Each record must also contain one or more of the following optional + keys: + - data + - ttl + - comment + - priority (optional for MX and SRV records; forbidden otherwise) + """ + return domain.update_records(records) @assure_domain diff --git a/awx/lib/site-packages/pyrax/cloudloadbalancers.py b/awx/lib/site-packages/pyrax/cloudloadbalancers.py index 1af39642f3..96b10a999d 100644 --- a/awx/lib/site-packages/pyrax/cloudloadbalancers.py +++ b/awx/lib/site-packages/pyrax/cloudloadbalancers.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c)2012 Rackspace US, Inc. @@ -488,8 +487,8 @@ class CloudLoadBalancerManager(BaseManager): if not all(required): raise exc.MissingLoadBalancerParameters("Load Balancer creation " "requires at least one virtual IP, a protocol, and a port.") - nodes = utils.coerce_string_to_list(nodes) - virtual_ips = utils.coerce_string_to_list(virtual_ips) + nodes = utils.coerce_to_list(nodes) + virtual_ips = utils.coerce_to_list(virtual_ips) bad_conditions = [node.condition for node in nodes if node.condition.upper() not in ("ENABLED", "DISABLED")] if bad_conditions: @@ -1110,7 +1109,7 @@ class Node(object): """ Returns the current metadata for the node. """ - return self.manager.get_metadata(self, node=self) + return self.parent.get_metadata_for_node(self) def set_metadata(self, metadata): @@ -1118,7 +1117,7 @@ class Node(object): Sets the metadata for the node to the supplied dictionary of values. Any existing metadata is cleared. """ - return self.manager.set_metadata(self, metadata, node=self) + return self.parent.set_metadata_for_node(self, metadata) def update_metadata(self, metadata): @@ -1126,7 +1125,7 @@ class Node(object): Updates the existing metadata for the node with the supplied dictionary. """ - return self.manager.update_metadata(self, metadata, node=self) + return self.parent.update_metadata_for_node(self, metadata) def delete_metadata(self, keys=None): @@ -1135,7 +1134,7 @@ class Node(object): this node. If no value for 'keys' is provided, all metadata is deleted. """ - return self.manager.delete_metadata(self, keys=keys, node=self) + return self.parent.delete_metadata_for_node(self, keys=keys) @assure_parent diff --git a/awx/lib/site-packages/pyrax/cloudmonitoring.py b/awx/lib/site-packages/pyrax/cloudmonitoring.py index b1b5489aca..033721a5e7 100644 --- a/awx/lib/site-packages/pyrax/cloudmonitoring.py +++ b/awx/lib/site-packages/pyrax/cloudmonitoring.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c)2013 Rackspace US, Inc. @@ -17,6 +16,7 @@ # License for the specific language governing permissions and limitations # under the License. +from functools import wraps import re from pyrax.client import BaseClient @@ -38,8 +38,46 @@ def _params_to_dict(params, dct, local_dict): return dct +def assure_check(fnc): + """ + Converts an checkID passed as the check to a CloudMonitorCheck object. + """ + @wraps(fnc) + def _wrapped(self, check, *args, **kwargs): + if not isinstance(check, CloudMonitorCheck): + # Must be the ID + check = self._check_manager.get(check) + return fnc(self, check, *args, **kwargs) + return _wrapped + + +def assure_entity(fnc): + """ + Converts an entityID passed as the entity to a CloudMonitorEntity object. + """ + @wraps(fnc) + def _wrapped(self, entity, *args, **kwargs): + if not isinstance(entity, CloudMonitorEntity): + # Must be the ID + entity = self._entity_manager.get(entity) + return fnc(self, entity, *args, **kwargs) + return _wrapped + + class CloudMonitorEntity(BaseResource): + def __init__(self, *args, **kwargs): + super(CloudMonitorEntity, self).__init__(*args, **kwargs) + self._check_manager = CloudMonitorCheckManager(self.manager.api, + uri_base="entities/%s/checks" % self.id, + resource_class=CloudMonitorCheck, response_key=None, + plural_response_key=None) + self._alarm_manager = CloudMonitorAlarmManager(self.manager.api, + uri_base="entities/%s/alarms" % self.id, + resource_class=CloudMonitorAlarm, response_key=None, + plural_response_key=None) + + def update(self, agent=None, metadata=None): """ Only the agent_id and metadata are able to be updated via the API. @@ -47,27 +85,99 @@ class CloudMonitorEntity(BaseResource): self.manager.update_entity(self, agent=agent, metadata=metadata) - def list_checks(self): + def get_check(self, check): """ - Returns a list of all CloudMonitorChecks defined for this entity. + Returns an instance of the specified check. """ - return self.manager.list_checks(self) + chk = self._check_manager.get(check) + chk.set_entity(self) + return chk + + + def list_checks(self, limit=None, marker=None, return_next=False): + """ + Returns a list of the checks defined for this account. By default the + number returned is limited to 100; you can define the number to return + by optionally passing a value for the 'limit' parameter. The value for + limit must be at least 1, and can be up to 1000. + + For pagination, you must also specify the 'marker' parameter. This is + the ID of the first item to return. To get this, pass True for the + 'return_next' parameter, and the response will be a 2-tuple, with the + first element being the list of checks, and the second the ID of the + next item. If there is no next item, the second element will be None. + """ + checks = self._check_manager.list(limit=limit, marker=marker, + return_next=return_next) + for check in checks: + check.set_entity(self) + return checks + + + def find_all_checks(self, **kwargs): + """ + Finds all checks for this entity with attributes matching ``**kwargs``. + + This isn't very efficient: it loads the entire list then filters on + the Python side. + """ + checks = self._check_manager.find_all_checks(**kwargs) + for check in checks: + check.set_entity(self) + return checks + + + def create_check(self, label=None, name=None, check_type=None, + disabled=False, metadata=None, details=None, + monitoring_zones_poll=None, timeout=None, period=None, + target_alias=None, target_hostname=None, target_receiver=None, + test_only=False, include_debug=False): + """ + Creates a check on this entity with the specified attributes. The + 'details' parameter should be a dict with the keys as the option name, + and the value as the desired setting. + """ + return self._check_manager.create_check(label=label, name=name, + check_type=check_type, disabled=disabled, metadata=metadata, + details=details, monitoring_zones_poll=monitoring_zones_poll, + timeout=timeout, period=period, target_alias=target_alias, + target_hostname=target_hostname, + target_receiver=target_receiver, test_only=test_only, + include_debug=include_debug) + + + def update_check(self, check, label=None, name=None, disabled=None, + metadata=None, monitoring_zones_poll=None, timeout=None, + period=None, target_alias=None, target_hostname=None, + target_receiver=None): + """ + Updates an existing check with any of the parameters. + """ + return self._check_manager.update(check, label=label, name=name, + disabled=disabled, metadata=metadata, + monitoring_zones_poll=monitoring_zones_poll, timeout=timeout, + period=period, target_alias=target_alias, + target_hostname=target_hostname, + target_receiver=target_receiver) def delete_check(self, check): """ Deletes the specified check from this entity. """ - return self.manager.delete_check(self, check) + return self._check_manager.delete(check) - def list_metrics(self, check): + @assure_check + def list_metrics(self, check, limit=None, marker=None, return_next=False): """ Returns a list of all the metrics associated with the specified check. """ - return self.manager.list_metrics(self, check) + return check.list_metrics(limit=limit, marker=marker, + return_next=return_next) + @assure_check def get_metric_data_points(self, check, metric, start, end, points=None, resolution=None, stats=None): """ @@ -95,8 +205,8 @@ class CloudMonitorEntity(BaseResource): min max """ - return self.manager.get_metric_data_points(self, check, metric, start, - end, points=points, resolution=resolution, stats=stats) + return check.get_metric_data_points(metric, start, end, points=points, + resolution=resolution, stats=stats) def create_alarm(self, check, notification_plan, criteria=None, @@ -105,7 +215,7 @@ class CloudMonitorEntity(BaseResource): Creates an alarm that binds the check on this entity with a notification plan. """ - return self.manager.create_alarm(self, check, notification_plan, + return self._alarm_manager.create(check, notification_plan, criteria=criteria, disabled=disabled, label=label, name=name, metadata=metadata) @@ -115,15 +225,16 @@ class CloudMonitorEntity(BaseResource): """ Updates an existing alarm on this entity. """ - return self.manager.update_alarm(self, alarm, criteria=criteria, + return self._alarm_manager.update(alarm, criteria=criteria, disabled=disabled, label=label, name=name, metadata=metadata) - def list_alarms(self): + def list_alarms(self, limit=None, marker=None, return_next=False): """ Returns a list of all the alarms created on this entity. """ - return self.manager.list_alarms(self) + return self._alarm_manager.list(limit=limit, marker=marker, + return_next=return_next) def get_alarm(self, alarm): @@ -132,14 +243,14 @@ class CloudMonitorEntity(BaseResource): CloudMonitorAlarm instance is passed, returns a new CloudMonitorAlarm object with the current state from the API. """ - return self.manager.get_alarm(self, alarm) + return self._alarm_manager.get(alarm) def delete_alarm(self, alarm): """ Deletes the specified alarm. """ - return self.manager.delete_alarm(self, alarm) + return self._alarm_manager.delete(alarm) @property @@ -148,7 +259,29 @@ class CloudMonitorEntity(BaseResource): -class CloudMonitorNotificationManager(BaseManager): +class _PaginationManager(BaseManager): + def list(self, limit=None, marker=None, return_next=False): + """ + This is necessary to handle pagination correctly, as the Monitoring + service defines 'marker' differently than most other services. For + monitoring, 'marker' represents the first item in the next page, + whereas other services define it as the ID of the last item in the + current page. + """ + kwargs = {} + if return_next: + kwargs["other_keys"] = "metadata" + ret = super(_PaginationManager, self).list(limit=limit, + marker=marker, **kwargs) + if return_next: + ents, meta = ret + return (ents, meta[0].get("next_marker")) + else: + return ret + + + +class CloudMonitorNotificationManager(_PaginationManager): """ Handles all of the requests dealing with notifications. """ @@ -228,7 +361,7 @@ class CloudMonitorNotificationManager(BaseManager): -class CloudMonitorNotificationPlanManager(BaseManager): +class CloudMonitorNotificationPlanManager(_PaginationManager): """ Handles all of the requests dealing with Notification Plans. """ @@ -246,23 +379,304 @@ class CloudMonitorNotificationPlanManager(BaseManager): body = {"label": label or name} def make_list_of_ids(parameter): - params = utils.coerce_string_to_list(parameter) + params = utils.coerce_to_list(parameter) return [utils.get_id(param) for param in params] if critical_state: - critical_state = utils.coerce_string_to_list(critical_state) + critical_state = utils.coerce_to_list(critical_state) body["critical_state"] = make_list_of_ids(critical_state) if warning_state: - warning_state = utils.coerce_string_to_list(warning_state) + warning_state = utils.coerce_to_list(warning_state) body["warning_state"] = make_list_of_ids(warning_state) if ok_state: - ok_state = utils.coerce_string_to_list(ok_state) + ok_state = utils.coerce_to_list(ok_state) body["ok_state"] = make_list_of_ids(ok_state) resp, resp_body = self.api.method_post(uri, body=body) return self.get(resp.headers["x-object-id"]) -class CloudMonitorEntityManager(BaseManager): + +class CloudMonitorMetricsManager(_PaginationManager): + def get_metric_data_points(self, metric, start, end, points=None, + resolution=None, stats=None): + """ + Returns the data points for a given metric for the given period. The + 'start' and 'end' times must be specified; they can be be either Python + date/datetime values, or a Unix timestamp. + + The 'points' parameter represents the number of points to return. The + 'resolution' parameter represents the granularity of the data. You must + specify either 'points' or 'resolution'. The allowed values for + resolution are: + FULL + MIN5 + MIN20 + MIN60 + MIN240 + MIN1440 + + Finally, the 'stats' parameter specifies the stats you want returned. + By default only the 'average' is returned. You omit this parameter, + pass in a single value, or pass in a list of values. The allowed values + are: + average + variance + min + max + """ + allowed_resolutions = ("FULL", "MIN5", "MIN20", "MIN60", "MIN240", + "MIN1440") + if not (points or resolution): + raise exc.MissingMonitoringCheckGranularity("You must specify " + "either the 'points' or 'resolution' parameter when " + "fetching metrics.") + if resolution: + if resolution.upper() not in allowed_resolutions: + raise exc.InvalidMonitoringMetricsResolution("The specified " + "resolution '%s' is not valid. The valid values are: " + "%s." % (resolution, str(allowed_resolutions))) + start_tm = utils.to_timestamp(start) + end_tm = utils.to_timestamp(end) + # NOTE: For some odd reason, the timestamps required for this must be + # in milliseconds, instead of the UNIX standard for timestamps, which + # is in seconds. So the values here are multiplied by 1000 to make it + # work. If the API is ever corrected, the next two lines should be + # removed. GitHub #176. + start_tm *= 1000 + end_tm *= 1000 + qparms = [] + # Timestamps with fractional seconds currently cause a 408 (timeout) + qparms.append("from=%s" % int(start_tm)) + qparms.append("to=%s" % int(end_tm)) + if points: + qparms.append("points=%s" % points) + if resolution: + qparms.append("resolution=%s" % resolution.upper()) + if stats: + stats = utils.coerce_to_list(stats) + for stat in stats: + qparms.append("select=%s" % stat) + qparm = "&".join(qparms) + uri = "/%s/%s/plot?%s" % (self.uri_base, metric, qparm) + try: + resp, resp_body = self.api.method_get(uri) + except exc.BadRequest as e: + msg = e.message + dtls = e.details + if msg.startswith("Validation error"): + raise exc.InvalidMonitoringMetricsRequest("Your request was " + "invalid: '%s'" % dtls) + else: + raise + return resp_body["values"] + + + +class CloudMonitorAlarmManager(_PaginationManager): + """ + Handles all of the alarm-specific requests. + """ + def create(self, check, notification_plan, criteria=None, + disabled=False, label=None, name=None, metadata=None): + """ + Creates an alarm that binds the check on the given entity with a + notification plan. + + Note that the 'criteria' parameter, if supplied, should be a string + representing the DSL for describing alerting conditions and their + output states. Pyrax does not do any validation of these criteria + statements; it is up to you as the developer to understand the language + and correctly form the statement. This alarm language is documented + online in the Cloud Monitoring section of http://docs.rackspace.com. + """ + uri = "/%s" % self.uri_base + body = {"check_id": utils.get_id(check), + "notification_plan_id": utils.get_id(notification_plan), + } + if criteria: + body["criteria"] = criteria + if disabled is not None: + body["disabled"] = disabled + label_name = label or name + if label_name: + body["label"] = label_name + if metadata: + body["metadata"] = metadata + resp, resp_body = self.api.method_post(uri, body=body) + if resp.status_code == 201: + alarm_id = resp.headers["x-object-id"] + return self.get(alarm_id) + + + def update(self, alarm, criteria=None, disabled=False, label=None, + name=None, metadata=None): + """ + Updates an existing alarm. See the comments on the 'create()' method + regarding the criteria parameter. + """ + uri = "/%s/%s" % (self.uri_base, utils.get_id(alarm)) + body = {} + if criteria: + body["criteria"] = criteria + if disabled is not None: + body["disabled"] = disabled + label_name = label or name + if label_name: + body["label"] = label_name + if metadata: + body["metadata"] = metadata + resp, resp_body = self.api.method_put(uri, body=body) + + + +class CloudMonitorCheckManager(_PaginationManager): + """ + Handles all of the check-specific requests. + """ + def create_check(self, label=None, name=None, check_type=None, + details=None, disabled=False, metadata=None, + monitoring_zones_poll=None, timeout=None, period=None, + target_alias=None, target_hostname=None, target_receiver=None, + test_only=False, include_debug=False): + """ + Creates a check on the entity with the specified attributes. The + 'details' parameter should be a dict with the keys as the option name, + and the value as the desired setting. + + If the 'test_only' parameter is True, then the check is not created; + instead, the check is run and the results of the test run returned. If + 'include_debug' is True, additional debug information is returned. + According to the current Cloud Monitoring docs: + "Currently debug information is only available for the + remote.http check and includes the response body." + """ + if details is None: + raise exc.MissingMonitoringCheckDetails("The required 'details' " + "parameter was not passed to the create_check() method.") + if not (target_alias or target_hostname): + raise exc.MonitoringCheckTargetNotSpecified("You must specify " + "either the 'target_alias' or 'target_hostname' when " + "creating a check.") + ctype = utils.get_id(check_type) + is_remote = ctype.startswith("remote") + monitoring_zones_poll = utils.coerce_to_list(monitoring_zones_poll) + monitoring_zones_poll = [utils.get_id(mzp) + for mzp in monitoring_zones_poll] + if is_remote and not monitoring_zones_poll: + raise exc.MonitoringZonesPollMissing("You must specify the " + "'monitoring_zones_poll' parameter for remote checks.") + body = {"label": label or name, + "details": details, + "disabled": disabled, + "type": utils.get_id(check_type), + } + params = ("monitoring_zones_poll", "timeout", "period", + "target_alias", "target_hostname", "target_receiver") + body = _params_to_dict(params, body, locals()) + if test_only: + uri = "/%s/test-check" % self.uri_base + if include_debug: + uri = "%s?debug=true" % uri + else: + uri = "/%s" % self.uri_base + try: + resp, resp_body = self.api.method_post(uri, body=body) + except exc.BadRequest as e: + msg = e.message + dtls = e.details + match = _invalid_key_pat.match(msg) + if match: + missing = match.groups()[0].replace("details.", "") + if missing in details: + errcls = exc.InvalidMonitoringCheckDetails + errmsg = "".join(["The value passed for '%s' in the ", + "details parameter is not valid."]) % missing + else: + errmsg = "".join(["The required value for the '%s' ", + "setting is missing from the 'details' ", + "parameter."]) % missing + utils.update_exc(e, errmsg) + raise e + else: + if msg == "Validation error": + # Info is in the 'details' + raise exc.InvalidMonitoringCheckDetails("Validation " + "failed. Error: '%s'." % dtls) + else: + if resp.status_code == 201: + check_id = resp.headers["x-object-id"] + return self.get(check_id) + + + def update(self, check, label=None, name=None, disabled=None, + metadata=None, monitoring_zones_poll=None, timeout=None, + period=None, target_alias=None, target_hostname=None, + target_receiver=None): + if monitoring_zones_poll: + monitoring_zones_poll = utils.coerce_to_list(monitoring_zones_poll) + monitoring_zones_poll = [utils.get_id(mzp) + for mzp in monitoring_zones_poll] + body = {} + local_dict = locals() + label = label or name + params = ("label", "disabled", "metadata", "monitoring_zones_poll", + "timeout", "period", "target_alias", "target_hostname", + "target_receiver") + body = _params_to_dict(params, body, locals()) + entity = check.entity + uri = "/%s/%s" % (self.uri_base, utils.get_id(check)) + try: + resp, resp_body = self.api.method_put(uri, body=body) + except exc.BadRequest as e: + msg = e.message + dtls = e.details + if msg.startswith("Validation error"): + raise exc.InvalidMonitoringCheckUpdate("The update failed " + "validation: %s: %s" % (msg, dtls)) + else: + # Some other issue. + raise + return resp_body + + + def find_all_checks(self, **kwargs): + """ + Finds all checks for a given entity with attributes matching + ``**kwargs``. + + This isn't very efficient: it loads the entire list then filters on + the Python side. + """ + found = [] + searches = kwargs.items() + for obj in self.list(): + try: + if all(getattr(obj, attr) == value + for (attr, value) in searches): + found.append(obj) + except AttributeError: + continue + return found + + + +class _EntityFilteringManger(BaseManager): + """ + Handles calls that can optionally filter requests based on an entity. + """ + def list(self, entity=None): + """ + Returns a dictionary of data, optionally filtered for a given entity. + """ + uri = "/%s" % self.uri_base + if entity: + uri = "%s?entityId=%s" % (uri, utils.get_id(entity)) + resp, resp_body = self._list(uri, return_raw=True) + return resp_body + + + +class CloudMonitorEntityManager(_PaginationManager): """ Handles all of the entity-specific requests. """ @@ -299,371 +713,28 @@ class CloudMonitorEntityManager(BaseManager): resp, body = self.api.method_put(uri, body=body) - def list_checks(self, entity): - """ - Returns a list of all CloudMonitorChecks defined for this entity. - """ - uri = "/%s/%s/checks" % (self.uri_base, utils.get_id(entity)) - resp, resp_body = self.api.method_get(uri) - return [CloudMonitorCheck(self, val, entity) - for val in resp_body["values"]] - - - def create_check(self, entity, label=None, name=None, check_type=None, - details=None, disabled=False, metadata=None, - monitoring_zones_poll=None, timeout=None, period=None, - target_alias=None, target_hostname=None, target_receiver=None, - test_only=False, include_debug=False): - """ - Creates a check on the entity with the specified attributes. The - 'details' parameter should be a dict with the keys as the option name, - and the value as the desired setting. - - If the 'test_only' parameter is True, then the check is not created; - instead, the check is run and the results of the test run returned. If - 'include_debug' is True, additional debug information is returned. - According to the current Cloud Monitoring docs: - "Currently debug information is only available for the - remote.http check and includes the response body." - """ - if details is None: - raise exc.MissingMonitoringCheckDetails("The required 'details' " - "parameter was not passed to the create_check() method.") - if not (target_alias or target_hostname): - raise exc.MonitoringCheckTargetNotSpecified("You must specify " - "either the 'target_alias' or 'target_hostname' when " - "creating a check.") - ctype = utils.get_id(check_type) - is_remote = ctype.startswith("remote") - monitoring_zones_poll = utils.coerce_string_to_list( - monitoring_zones_poll) - monitoring_zones_poll = [utils.get_id(mzp) - for mzp in monitoring_zones_poll] - if is_remote and not monitoring_zones_poll: - raise exc.MonitoringZonesPollMissing("You must specify the " - "'monitoring_zones_poll' parameter for remote checks.") - body = {"label": label or name, - "details": details, - "disabled": disabled, - "type": utils.get_id(check_type), - } - params = ("monitoring_zones_poll", "timeout", "period", - "target_alias", "target_hostname", "target_receiver") - body = _params_to_dict(params, body, locals()) - if test_only: - uri = "/%s/%s/test-check" % (self.uri_base, entity.id) - if include_debug: - uri = "%s?debug=true" % uri - else: - uri = "/%s/%s/checks" % (self.uri_base, entity.id) - try: - resp, resp_body = self.api.method_post(uri, body=body) - except exc.BadRequest as e: - msg = e.message - dtls = e.details - match = _invalid_key_pat.match(msg) - if match: - missing = match.groups()[0].replace("details.", "") - if missing in details: - errcls = exc.InvalidMonitoringCheckDetails - errmsg = "".join(["The value passed for '%s' in the ", - "details parameter is not valid."]) % missing - else: - errmsg = "".join(["The required value for the '%s' ", - "setting is missing from the 'details' ", - "parameter."]) % missing - utils.update_exc(e, errmsg) - raise e - else: - if msg == "Validation error": - # Info is in the 'details' - raise exc.InvalidMonitoringCheckDetails("Validation " - "failed. Error: '%s'." % dtls) - else: - if resp.status_code == 201: - check_id = resp.headers["x-object-id"] - return self.get_check(entity, check_id) - - - def find_all_checks(self, entity, **kwargs): - """ - Finds all checks for a given entity with attributes matching - ``**kwargs``. - - This isn't very efficient: it loads the entire list then filters on - the Python side. - """ - found = [] - searches = kwargs.items() - for obj in self.list_checks(entity): - try: - if all(getattr(obj, attr) == value - for (attr, value) in searches): - found.append(obj) - except AttributeError: - continue - return found - - - def update_check(self, check, label=None, name=None, disabled=None, - metadata=None, monitoring_zones_poll=None, timeout=None, - period=None, target_alias=None, target_hostname=None, - target_receiver=None): - if monitoring_zones_poll: - monitoring_zones_poll = utils.coerce_string_to_list( - monitoring_zones_poll) - monitoring_zones_poll = [utils.get_id(mzp) - for mzp in monitoring_zones_poll] - body = {} - local_dict = locals() - label = label or name - params = ("label", "disabled", "metadata", "monitoring_zones_poll", - "timeout", "period", "target_alias", "target_hostname", - "target_receiver") - body = _params_to_dict(params, body, locals()) - entity = check.entity - uri = "/%s/%s/checks/%s" % (self.uri_base, utils.get_id(entity), - utils.get_id(check)) - try: - resp, resp_body = self.api.method_put(uri, body=body) - except exc.BadRequest as e: - msg = e.message - dtls = e.details - if msg.startswith("Validation error"): - raise exc.InvalidMonitoringCheckUpdate("The update failed " - "validation: %s: %s" % (msg, dtls)) - else: - # Some other issue. - raise - return resp_body - - - def get_check(self, entity, check): - """ - Returns the current version of the check for the entity. - """ - uri = "/%s/%s/checks/%s" % (self.uri_base, utils.get_id(entity), - utils.get_id(check)) - resp, resp_body = self.api.method_get(uri) - return CloudMonitorCheck(self, resp_body, entity) - - - def delete_check(self, entity, check): - """ - Deletes the specified check from the entity. - """ - uri = "/%s/%s/checks/%s" % (self.uri_base, utils.get_id(entity), - utils.get_id(check)) - resp, resp_body = self.api.method_delete(uri) - - - def list_metrics(self, entity, check): - """ - Returns a list of all the metrics associated with the specified check. - """ - uri = "/%s/%s/checks/%s/metrics" % (self.uri_base, - utils.get_id(entity), utils.get_id(check)) - resp, resp_body = self.api.method_get(uri) - metrics = [val["name"] - for val in resp_body["values"]] - return metrics - - - def get_metric_data_points(self, entity, check, metric, start, end, - points=None, resolution=None, stats=None): - """ - Returns the data points for a given metric for the given period. The - 'start' and 'end' times must be specified; they can be be either Python - date/datetime values, a string representing a date/datetime in either - of 'YYYY-MM-DD HH:MM:SS' or 'YYYY-MM-DD' formats, or a Unix timestamp: - - The 'points' parameter represents the number of points to return. The - 'resolution' parameter represents the granularity of the data. You must - specify either 'points' or 'resolution', but not both. The allowed - values for resolution are: 'FULL', 'MIN5', 'MIN20', 'MIN60', 'MIN240', - and 'MIN1440'. - - Finally, the 'stats' parameter specifies the stats you want returned. - By default only the 'average' is returned. You omit this parameter, - pass in a single value, or pass in a list of values. The allowed values - are: 'average', 'variance', 'min', and 'max' - """ - allowed_resolutions = ("FULL", "MIN5", "MIN20", "MIN60", "MIN240", - "MIN1440") - if not (points or resolution): - raise exc.MissingMonitoringCheckGranularity("You must specify " - "either the 'points' or 'resolution' parameter when " - "fetching metrics.") - if resolution: - if resolution.upper() not in allowed_resolutions: - raise exc.InvalidMonitoringMetricsResolution("The specified " - "resolution '%s' is not valid. The valid values are: " - "%s." % (resolution, str(allowed_resolutions))) - start_tm = utils.to_timestamp(start) - end_tm = utils.to_timestamp(end) - # NOTE: For some odd reason, the timestamps required for this must be - # in milliseconds, instead of the UNIX standard for timestamps, which - # is in seconds. So the values here are multiplied by 1000 to make it - # work. If the API is ever corrected, the next two lines should be - # removed. GitHub #176. - start_tm *= 1000 - end_tm *= 1000 - qparms = [] - # Timestamps with fractional seconds currently cause a 408 (timeout) - qparms.append("from=%s" % int(start_tm)) - qparms.append("to=%s" % int(end_tm)) - if points: - qparms.append("points=%s" % points) - if resolution: - qparms.append("resolution=%s" % resolution.upper()) - if stats: - stats = utils.coerce_string_to_list(stats) - for stat in stats: - qparms.append("select=%s" % stat) - qparm = "&".join(qparms) - uri = "/%s/%s/checks/%s/metrics/%s/plot?%s" % (self.uri_base, - utils.get_id(entity), utils.get_id(check), metric, qparm) - try: - resp, resp_body = self.api.method_get(uri) - except exc.BadRequest as e: - msg = e.message - dtls = e.details - if msg.startswith("Validation error"): - raise exc.InvalidMonitoringMetricsRequest("Your request was " - "invalid: '%s'" % dtls) - else: - raise - return resp_body["values"] - - - def create_alarm(self, entity, check, notification_plan, criteria=None, - disabled=False, label=None, name=None, metadata=None): - """ - Creates an alarm that binds the check on the given entity with a - notification plan. - - Note that the 'criteria' parameter, if supplied, should be a string - representing the DSL for describing alerting conditions and their - output states. Pyrax does not do any validation of these criteria - statements; it is up to you as the developer to understand the language - and correctly form the statement. This alarm language is documented - online in the Cloud Monitoring section of http://docs.rackspace.com. - """ - uri = "/%s/%s/alarms" % (self.uri_base, utils.get_id(entity)) - body = {"check_id": utils.get_id(check), - "notification_plan_id": utils.get_id(notification_plan), - } - if criteria: - body["criteria"] = criteria - if disabled is not None: - body["disabled"] = disabled - label_name = label or name - if label_name: - body["label"] = label_name - if metadata: - body["metadata"] = metadata - resp, resp_body = self.api.method_post(uri, body=body) - if resp.status_code == 201: - alarm_id = resp.headers["x-object-id"] - return self.get_alarm(entity, alarm_id) - - - def update_alarm(self, entity, alarm, criteria=None, disabled=False, - label=None, name=None, metadata=None): - """ - Updates an existing alarm on the given entity. See the comments on the - 'create_alarm()' regarding the criteria parameter. - """ - uri = "/%s/%s/alarms/%s" % (self.uri_base, utils.get_id(entity), - utils.get_id(alarm)) - body = {} - if criteria: - body["criteria"] = criteria - if disabled is not None: - body["disabled"] = disabled - label_name = label or name - if label_name: - body["label"] = label_name - if metadata: - body["metadata"] = metadata - resp, resp_body = self.api.method_put(uri, body=body) - - - def list_alarms(self, entity): - """ - Returns a list of all the alarms created on the specified entity. - """ - uri = "/%s/%s/alarms" % (self.uri_base, utils.get_id(entity)) - resp, resp_body = self.api.method_get(uri) - return [CloudMonitorAlarm(self, dct, entity) - for dct in resp_body["values"]] - - - def get_alarm(self, entity, alarm): - """ - Returns the alarm with the specified ID for this entity. If a - CloudMonitorAlarm instance is passed, returns a new CloudMonitorAlarm - object with the current state from the API. - """ - uri = "/%s/%s/alarms/%s" % (self.uri_base, utils.get_id(entity), - utils.get_id(alarm)) - resp, resp_body = self.api.method_get(uri) - return CloudMonitorAlarm(self, resp_body, entity) - - - def delete_alarm(self, entity, alarm): - """ - Deletes the specified alarm. - """ - uri = "/%s/%s/alarms/%s" % (self.uri_base, utils.get_id(entity), - utils.get_id(alarm)) - resp, resp_body = self.api.method_delete(uri) - - - -class CloudMonitorChangelogManager(BaseManager): - """ - Handles calls to retrieve changelogs. - """ - def list(self, entity=None): - """ - Returns a dictionary of changelog data, optionally filtered for a given - entity. - """ - uri = "/%s" % self.uri_base - if entity: - uri = "%s?entityId=%s" % (uri, utils.get_id(entity)) - resp, resp_body = self._list(uri, return_raw=True) - return resp_body - - - -class CloudMonitorOverviewManager(BaseManager): - """ - Handles calls to retrieve overview information. - """ - def list(self, entity=None): - """ - Returns overview information, optionally filtered for a given entity. - """ - uri = "/%s" % self.uri_base - if entity: - uri = "%s?entityId=%s" % (uri, utils.get_id(entity)) - resp, resp_body = self._list(uri, return_raw=True) - return resp_body - - class CloudMonitorCheck(BaseResource): """ Represents a check defined for an entity. """ - def __init__(self, manager, info, entity, key=None, loaded=False): + def __init__(self, manager, info, entity=None, key=None, loaded=False): super(CloudMonitorCheck, self).__init__(manager, info, key=key, loaded=loaded) + self.set_entity(entity) + + + def set_entity(self, entity): + if entity is None: + # Not yet available + return if not isinstance(entity, CloudMonitorEntity): - entity = manager.get(entity) + entity = self.manager.get(entity) self.entity = entity + self._metrics_manager = CloudMonitorMetricsManager(self.manager.api, + uri_base="entities/%s/checks/%s/metrics" % (self.entity.id, + self.id), resource_class=CloudMonitorMetric, response_key=None, + plural_response_key=None) @property @@ -673,7 +744,7 @@ class CloudMonitorCheck(BaseResource): def get(self): """Reloads the check with its current values.""" - new = self.manager.get_check(self.entity, self) + new = self.manager.get(self) if new: self._add_details(new._info) @@ -686,7 +757,7 @@ class CloudMonitorCheck(BaseResource): """ Updates an existing check with any of the parameters. """ - self.manager.update_check(self, label=label, name=name, + self.manager.update(self, label=label, name=name, disabled=disabled, metadata=metadata, monitoring_zones_poll=monitoring_zones_poll, timeout=timeout, period=period, target_alias=target_alias, @@ -696,14 +767,15 @@ class CloudMonitorCheck(BaseResource): def delete(self): """Removes this check from its entity.""" - self.manager.delete_check(self.entity, self) + self.manager.delete(self) - def list_metrics(self): + def list_metrics(self, limit=None, marker=None, return_next=False): """ Returns a list of all the metrics associated with this check. """ - return self.manager.list_metrics(self.entity, self) + return self._metrics_manager.list(limit=limit, marker=marker, + return_next=return_next) def get_metric_data_points(self, metric, start, end, points=None, @@ -733,8 +805,8 @@ class CloudMonitorCheck(BaseResource): min max """ - return self.manager.get_metric_data_points(self.entity, self, metric, - start, end, points=points, resolution=resolution, stats=stats) + return self._metrics_manager.get_metric_data_points(metric, start, end, + points=points, resolution=resolution, stats=stats) def create_alarm(self, notification_plan, criteria=None, disabled=False, @@ -827,6 +899,14 @@ class CloudMonitorNotificationPlan(BaseResource): +class CloudMonitorMetric(BaseResource): + """ + Metrics represent statistics about the checks defined on an entity. + """ + pass + + + class CloudMonitorAlarm(BaseResource): """ Alarms bind alerting rules, entities, and notification plans into a logical @@ -884,7 +964,7 @@ class CloudMonitorClient(BaseClient): self._entity_manager = CloudMonitorEntityManager(self, uri_base="entities", resource_class=CloudMonitorEntity, response_key=None, plural_response_key=None) - self._check_type_manager = BaseManager(self, + self._check_type_manager = _PaginationManager(self, uri_base="check_types", resource_class=CloudMonitorCheckType, response_key=None, plural_response_key=None) self._monitoring_zone_manager = BaseManager(self, @@ -898,10 +978,10 @@ class CloudMonitorClient(BaseClient): self, uri_base="notification_plans", resource_class=CloudMonitorNotificationPlan, response_key=None, plural_response_key=None) - self._changelog_manager = CloudMonitorChangelogManager(self, + self._changelog_manager = _EntityFilteringManger(self, uri_base="changelogs/alarms", resource_class=None, response_key=None, plural_response_key=None) - self._overview_manager = CloudMonitorOverviewManager(self, + self._overview_manager = _EntityFilteringManger(self, uri_base="views/overview", resource_class=None, response_key="value", plural_response_key=None) @@ -929,8 +1009,9 @@ class CloudMonitorClient(BaseClient): return resp_body["values"] - def list_entities(self): - return self._entity_manager.list() + def list_entities(self, limit=None, marker=None, return_next=False): + return self._entity_manager.list(limit=limit, marker=marker, + return_next=return_next) def get_entity(self, entity): @@ -964,18 +1045,22 @@ class CloudMonitorClient(BaseClient): self._entity_manager.delete(entity) - def list_check_types(self): - return self._check_type_manager.list() + def list_check_types(self, limit=None, marker=None, return_next=False): + return self._check_type_manager.list(limit=limit, marker=marker, + return_next=return_next) def get_check_type(self, check_type): return self._check_type_manager.get(check_type) - def list_checks(self, entity): - return self._entity_manager.list_checks(entity) + @assure_entity + def list_checks(self, entity, limit=None, marker=None, return_next=False): + return entity.list_checks(limit=limit, marker=marker, + return_next=return_next) + @assure_entity def create_check(self, entity, label=None, name=None, check_type=None, disabled=False, metadata=None, details=None, monitoring_zones_poll=None, timeout=None, period=None, @@ -986,21 +1071,22 @@ class CloudMonitorClient(BaseClient): 'details' parameter should be a dict with the keys as the option name, and the value as the desired setting. """ - return self._entity_manager.create_check(entity, label=label, - name=name, check_type=check_type, disabled=disabled, - metadata=metadata, details=details, - monitoring_zones_poll=monitoring_zones_poll, timeout=timeout, - period=period, target_alias=target_alias, + return entity.create_check(label=label, name=name, + check_type=check_type, disabled=disabled, metadata=metadata, + details=details, monitoring_zones_poll=monitoring_zones_poll, + timeout=timeout, period=period, target_alias=target_alias, target_hostname=target_hostname, target_receiver=target_receiver, test_only=test_only, include_debug=include_debug) + @assure_entity def get_check(self, entity, check): """Returns the current check for the given entity.""" - return self._entity_manager.get_check(entity, check) + return entity.get_check(check) + @assure_entity def find_all_checks(self, entity, **kwargs): """ Finds all checks for a given entity with attributes matching @@ -1009,9 +1095,10 @@ class CloudMonitorClient(BaseClient): This isn't very efficient: it loads the entire list then filters on the Python side. """ - return self._entity_manager.find_all_checks(entity, **kwargs) + return entity.find_all_checks(**kwargs) + @assure_entity def update_check(self, entity, check, label=None, name=None, disabled=None, metadata=None, monitoring_zones_poll=None, timeout=None, period=None, target_alias=None, target_hostname=None, @@ -1019,28 +1106,32 @@ class CloudMonitorClient(BaseClient): """ Updates an existing check with any of the parameters. """ - self._entity_manager.update_check(entity, check, label=label, - name=name, disabled=disabled, metadata=metadata, - monitoring_zones_poll=monitoring_zones_poll, timeout=timeout, - period=period, target_alias=target_alias, + entity.update_check(check, label=label, name=name, disabled=disabled, + metadata=metadata, monitoring_zones_poll=monitoring_zones_poll, + timeout=timeout, period=period, target_alias=target_alias, target_hostname=target_hostname, target_receiver=target_receiver) + @assure_entity def delete_check(self, entity, check): """ Deletes the specified check from the entity. """ - return self._entity_manager.delete_check(entity, check) + return entity.delete_check(check) - def list_metrics(self, entity, check): + @assure_entity + def list_metrics(self, entity, check, limit=None, marker=None, + return_next=False): """ Returns a list of all the metrics associated with the specified check. """ - return self._entity_manager.list_metrics(entity, check) + return entity.list_metrics(check, limit=limit, marker=marker, + return_next=return_next) + @assure_entity def get_metric_data_points(self, entity, check, metric, start, end, points=None, resolution=None, stats=None): """ @@ -1068,9 +1159,8 @@ class CloudMonitorClient(BaseClient): min max """ - return self._entity_manager.get_metric_data_points(entity, check, - metric, start, end, points=points, resolution=resolution, - stats=stats) + return entity.get_metric_data_points(check, metric, start, end, + points=points, resolution=resolution, stats=stats) def list_notifications(self): @@ -1159,47 +1249,49 @@ class CloudMonitorClient(BaseClient): """ return self._notification_plan_manager.delete(notification_plan) - + @assure_entity def create_alarm(self, entity, check, notification_plan, criteria=None, disabled=False, label=None, name=None, metadata=None): """ Creates an alarm that binds the check on the given entity with a notification plan. """ - return self._entity_manager.create_alarm(entity, check, - notification_plan, criteria=criteria, disabled=disabled, - label=label, name=name, metadata=metadata) - + return entity.create_alarm(check, notification_plan, criteria=criteria, + disabled=disabled, label=label, name=name, metadata=metadata) + @assure_entity def update_alarm(self, entity, alarm, criteria=None, disabled=False, label=None, name=None, metadata=None): """ Updates an existing alarm on the given entity. """ - return self._entity_manager.update_alarm(entity, alarm, - criteria=criteria, disabled=disabled, label=label, name=name, - metadata=metadata) + return entity.update_alarm(alarm, criteria=criteria, disabled=disabled, + label=label, name=name, metadata=metadata) - def list_alarms(self, entity): + @assure_entity + def list_alarms(self, entity, limit=None, marker=None, return_next=False): """ Returns a list of all the alarms created on the specified entity. """ - return self._entity_manager.list_alarms(entity) + return entity.list_alarms(limit=limit, marker=marker, + return_next=return_next) + @assure_entity def get_alarm(self, entity, alarm_id): """ Returns the alarm with the specified ID for the entity. """ - return self._entity_manager.get_alarm(entity, alarm_id) + return entity.get_alarm(alarm_id) + @assure_entity def delete_alarm(self, entity, alarm): """ Deletes the specified alarm. """ - return self._entity_manager.delete_alarm(entity, alarm) + return entity.delete_alarm(alarm) def list_notification_types(self): diff --git a/awx/lib/site-packages/pyrax/cloudnetworks.py b/awx/lib/site-packages/pyrax/cloudnetworks.py index 43713d6255..6180036af7 100644 --- a/awx/lib/site-packages/pyrax/cloudnetworks.py +++ b/awx/lib/site-packages/pyrax/cloudnetworks.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c)2013 Rackspace US, Inc. @@ -29,13 +28,14 @@ SERVICE_NET_ID = "11111111-1111-1111-1111-111111111111" PSEUDO_NETWORKS = (PUBLIC_NET_ID, SERVICE_NET_ID) -def _get_server_networks(network, public=False, private=False): +def _get_server_networks(network, public=False, private=False, key=None): + key = key or "net-id" net_id = utils.get_id(network) - ret = [{"net-id": net_id}] + ret = [{key: net_id}] if public: - ret.append({"net-id": PUBLIC_NET_ID}) + ret.append({key: PUBLIC_NET_ID}) if private: - ret.append({"net-id": SERVICE_NET_ID}) + ret.append({key: SERVICE_NET_ID}) return ret @@ -88,16 +88,20 @@ class CloudNetwork(BaseResource): raise exc.NetworkInUse("Cannot delete a network in use by a server.") - def get_server_networks(self, public=False, private=False): + def get_server_networks(self, public=False, private=False, key=None): """ Creates the dict of network UUIDs required by Cloud Servers when - creating a new server with isolated networks. + creating a new server with isolated networks. By default, the UUID + values are returned with the key of "net-id", which is what novaclient + expects. Other tools may require different values, such as 'uuid'. If + that is the case, pass the desired key as the 'key' parameter. By default only this network is included. If you wish to create a server that has either the public (internet) or private (ServiceNet) networks, you have to pass those parameters in with values of True. """ - return _get_server_networks(self, public=public, private=private) + return _get_server_networks(self, public=public, private=private, + key=key) @@ -194,14 +198,18 @@ class CloudNetworkClient(BaseClient): find_network_by_name = find_network_by_label - def get_server_networks(self, network, public=False, private=False): + def get_server_networks(self, network, public=False, private=False, + key=None): """ Creates the dict of network UUIDs required by Cloud Servers when - creating a new server with isolated networks. + creating a new server with isolated networks. By default, the UUID + values are returned with the key of "net-id", which is what novaclient + expects. Other tools may require different values, such as 'uuid'. If + that is the case, pass the desired key as the 'key' parameter. - By default only the specified network is included. If you wish to - create a server that has either the public (internet) or private - (ServiceNet) networks, you have to pass those parameters in with - values of True. + By default only this network is included. If you wish to create a + server that has either the public (internet) or private (ServiceNet) + networks, you have to pass those parameters in with values of True. """ - return _get_server_networks(network, public=public, private=private) + return _get_server_networks(network, public=public, private=private, + key=key) diff --git a/awx/lib/site-packages/pyrax/exceptions.py b/awx/lib/site-packages/pyrax/exceptions.py index c73af611a8..a7a6506c95 100644 --- a/awx/lib/site-packages/pyrax/exceptions.py +++ b/awx/lib/site-packages/pyrax/exceptions.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c)2012 Rackspace US, Inc. @@ -349,7 +348,8 @@ class VolumeDetachmentFailed(PyraxException): class VolumeNotAvailable(PyraxException): pass - +class MissingCloudDatabaseParameter(PyraxException): + pass class AmbiguousEndpoints(PyraxException): """Found more than one matching endpoint in Service Catalog.""" @@ -376,6 +376,10 @@ class ClientException(PyraxException): formatted_string += " (Request-ID: %s)" % self.request_id return formatted_string + def __reduce__(self): + return (self.__class__, (self.code, self.message, + self.details, self.request_id)) + class BadRequest(ClientException): """ diff --git a/awx/lib/site-packages/pyrax/fakes.py b/awx/lib/site-packages/pyrax/fakes.py index 45909007b6..aa8956c1d2 100644 --- a/awx/lib/site-packages/pyrax/fakes.py +++ b/awx/lib/site-packages/pyrax/fakes.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- import json import os @@ -472,7 +471,7 @@ class FakeCloudNetwork(CloudNetwork): info["label"] = label super(FakeCloudNetwork, self).__init__(manager=None, info=info, *args, **kwargs) - self.id = uuid.uuid4() + self.id = uuid.uuid4().hex class FakeAutoScaleClient(AutoScaleClient): @@ -523,18 +522,20 @@ class FakeCloudMonitorClient(CloudMonitorClient): class FakeCloudMonitorEntity(CloudMonitorEntity): def __init__(self, *args, **kwargs): info = kwargs.pop("info", {"fake": "fake"}) + info["id"] = utils.random_ascii() super(FakeCloudMonitorEntity, self).__init__(FakeManager(), info=info, *args, **kwargs) self.manager.api = FakeCloudMonitorClient() - self.id = utils.random_unicode() class FakeCloudMonitorCheck(CloudMonitorCheck): def __init__(self, *args, **kwargs): info = kwargs.pop("info", {"fake": "fake"}) - entity = kwargs.pop("entity", FakeCloudMonitorEntity()) - super(FakeCloudMonitorCheck, self).__init__(None, info, entity, - *args, **kwargs) + entity = kwargs.pop("entity", None) + info["id"] = utils.random_ascii() + super(FakeCloudMonitorCheck, self).__init__(FakeManager(), info, *args, + **kwargs) + self.set_entity(entity) self.id = uuid.uuid4() @@ -892,13 +893,3 @@ class FakeIdentityResponse(FakeResponse): def read(self): return json.dumps(self.content) - - -def get_png_content(): - _module_pth = os.path.dirname(pyrax.__file__) - _img_path = os.path.join(_module_pth, "..", "tests", "unit", - "python-logo.png") - png_content = None - with open(_img_path, "rb") as pfile: - png_content = pfile.read() - return png_content diff --git a/awx/lib/site-packages/pyrax/http.py b/awx/lib/site-packages/pyrax/http.py index c5c75cc731..575bfe0035 100644 --- a/awx/lib/site-packages/pyrax/http.py +++ b/awx/lib/site-packages/pyrax/http.py @@ -48,6 +48,7 @@ def request(method, uri, *args, **kwargs): """ req_method = req_methods[method.upper()] raise_exception = kwargs.pop("raise_exception", True) + raw_content = kwargs.pop("raw_content", False) kwargs["headers"] = kwargs.get("headers", {}) http_log_req(method, uri, args, kwargs) data = None @@ -62,11 +63,14 @@ def request(method, uri, *args, **kwargs): resp = req_method(uri, data=data, **kwargs) else: resp = req_method(uri, **kwargs) - try: - body = resp.json() - except ValueError: - # No JSON in response + if raw_content: body = resp.content + else: + try: + body = resp.json() + except ValueError: + # No JSON in response + body = resp.content http_log_resp(resp, body) if resp.status_code >= 400 and raise_exception: raise exc.from_response(resp, body) diff --git a/awx/lib/site-packages/pyrax/identity/__init__.py b/awx/lib/site-packages/pyrax/identity/__init__.py index f5ae21629d..e53c0d9344 100644 --- a/awx/lib/site-packages/pyrax/identity/__init__.py +++ b/awx/lib/site-packages/pyrax/identity/__init__.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- import glob diff --git a/awx/lib/site-packages/pyrax/identity/keystone_identity.py b/awx/lib/site-packages/pyrax/identity/keystone_identity.py index 323dcc4048..fe9893f643 100644 --- a/awx/lib/site-packages/pyrax/identity/keystone_identity.py +++ b/awx/lib/site-packages/pyrax/identity/keystone_identity.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import absolute_import diff --git a/awx/lib/site-packages/pyrax/identity/rax_identity.py b/awx/lib/site-packages/pyrax/identity/rax_identity.py index 741bca38c1..648197d9f6 100644 --- a/awx/lib/site-packages/pyrax/identity/rax_identity.py +++ b/awx/lib/site-packages/pyrax/identity/rax_identity.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import absolute_import diff --git a/awx/lib/site-packages/pyrax/image.py b/awx/lib/site-packages/pyrax/image.py index c5c61bb737..3f1fca6807 100644 --- a/awx/lib/site-packages/pyrax/image.py +++ b/awx/lib/site-packages/pyrax/image.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c)2014 Rackspace US, Inc. diff --git a/awx/lib/site-packages/pyrax/manager.py b/awx/lib/site-packages/pyrax/manager.py index 4f1e968efc..8acc4eb7c4 100644 --- a/awx/lib/site-packages/pyrax/manager.py +++ b/awx/lib/site-packages/pyrax/manager.py @@ -58,7 +58,7 @@ class BaseManager(object): self.uri_base = uri_base - def list(self, limit=None, marker=None, return_raw=False): + def list(self, limit=None, marker=None, return_raw=False, other_keys=None): """ Returns a list of resource objects. Pagination is supported through the optional 'marker' and 'limit' parameters. @@ -67,6 +67,15 @@ class BaseManager(object): BaseManager subclasses will have to parse the raw response to get the desired information. For those cases, pass 'return_raw=True', and the response and response_body will be returned unprocessed. + + Another use case is when additional information is returned in the + response body. To have that returned, use the 'other_keys' parameter. + This can be either a single string or a list of strings that correspond + to keys in the response body. If specified, a 2-tuple is returned, with + the first element being the list of resources, and the second a dict + whose keys are the 'other_keys' items, and whose values are the + corresponding values in the response body, or None if no such key is + present. """ uri = "/%s" % self.uri_base pagination_items = [] @@ -77,7 +86,7 @@ class BaseManager(object): pagination = "&".join(pagination_items) if pagination: uri = "%s?%s" % (uri, pagination) - return self._list(uri, return_raw=return_raw) + return self._list(uri, return_raw=return_raw, other_keys=other_keys) def head(self, item): @@ -131,7 +140,8 @@ class BaseManager(object): return self._delete(uri) - def _list(self, uri, obj_class=None, body=None, return_raw=False): + def _list(self, uri, obj_class=None, body=None, return_raw=False, + other_keys=None): """ Handles the communication with the API when getting a full listing of the resources managed by this class. @@ -146,19 +156,28 @@ class BaseManager(object): obj_class = self.resource_class data = self._data_from_response(resp_body) - return [obj_class(self, res, loaded=False) - for res in data if res] + ret = [obj_class(self, res, loaded=False) for res in data if res] + if other_keys: + keys = utils.coerce_to_list(other_keys) + other = [self._data_from_response(resp_body, key) for key in keys] + return (ret, other) + else: + return ret - def _data_from_response(self, resp_body): + def _data_from_response(self, resp_body, key=None): """ This works for most API responses, but some don't structure their listing responses the same way, so overriding this method allows subclasses to handle extraction for those outliers. """ - data = resp_body.get(self.plural_response_key, resp_body) - # NOTE(ja): keystone returns values as list as {"values": [ ... ]} - # unlike other services which just return the list... + if key: + data = resp_body.get(key) + else: + data = resp_body.get(self.plural_response_key, resp_body) + # NOTE(ja): some services, such as keystone returns values as list as + # {"values": [ ... ]} unlike other services which just return the + # list. if isinstance(data, dict): try: data = data["values"] diff --git a/awx/lib/site-packages/pyrax/object_storage.py b/awx/lib/site-packages/pyrax/object_storage.py index 5e47d9bff6..29c9c4496d 100644 --- a/awx/lib/site-packages/pyrax/object_storage.py +++ b/awx/lib/site-packages/pyrax/object_storage.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright 2014 Rackspace @@ -55,6 +54,8 @@ DEFAULT_CHUNKSIZE = 65536 DEFAULT_CDN_TTL = 86400 # When comparing files dates, represents a date older than anything. EARLY_DATE_STR = "1900-01-01T00:00:00" +# Maximum number of objects that can be passed to bulk-delete +MAX_BULK_DELETE = 10000 # Used to indicate values that are lazy-loaded class Fault_cls(object): @@ -658,11 +659,11 @@ class Container(BaseResource): key=key, cached=cached) - def get_object_metadata(self, obj): + def get_object_metadata(self, obj, prefix=None): """ Returns the metadata for the specified object as a dict. """ - return self.object_manager.get_metadata(obj) + return self.object_manager.get_metadata(obj, prefix) def set_object_metadata(self, obj, metadata, clear=False, extra_info=None, @@ -784,11 +785,16 @@ class Container(BaseResource): class ContainerManager(BaseManager): - def _list(self, uri, obj_class=None, body=None, return_raw=False): + def list(self, limit=None, marker=None, end_marker=None, prefix=None): """ Swift doesn't return listings in the same format as the rest of OpenStack, so this method has to be overriden. """ + uri = "/%s" % self.uri_base + qs = utils.dict_to_qs({"marker": marker, "limit": limit, + "prefix": prefix, "end_marker": end_marker}) + if qs: + uri = "%s?%s" % (uri, qs) resp, resp_body = self.api.method_get(uri) return [Container(self, res, loaded=False) for res in resp_body if res] @@ -845,7 +851,7 @@ class ContainerManager(BaseManager): each object will be deleted first, and then the container. """ if del_objects: - nms = self.list_object_names(container) + nms = self.list_object_names(container, full_listing=True) self.api.bulk_delete(container, nms, async=False) uri = "/%s" % utils.get_name(container) resp, resp_body = self.api.method_delete(uri) @@ -1321,8 +1327,6 @@ class ContainerManager(BaseManager): objs = mthd(marker=marker, limit=limit, prefix=prefix, delimiter="/", return_raw=True) sdirs = [obj for obj in objs if "subdir" in obj] - for sdir in sdirs: - sdir["name"] = sdir["subdir"] mgr = container.object_manager return [StorageObject(mgr, sdir) for sdir in sdirs] @@ -1520,11 +1524,11 @@ class ContainerManager(BaseManager): @assure_container - def get_object_metadata(self, container, obj): + def get_object_metadata(self, container, obj, prefix=None): """ Returns the metadata for the specified object as a dict. """ - return container.get_object_metadata(obj) + return container.get_object_metadata(obj, prefix=prefix) @assure_container @@ -1557,6 +1561,10 @@ class StorageObject(BaseResource): """ def __init__(self, manager, info, *args, **kwargs): self._container = None + if ("name" not in info) and ("subdir" in info): + # Subdir dicts lack a 'name' and 'content_type' key + info["name"] = info["subdir"] + info["content_type"] = "pseudo/subdirectory" return super(StorageObject, self).__init__(manager, info, *args, **kwargs) @@ -1670,11 +1678,11 @@ class StorageObject(BaseResource): return self.manager.purge(self, email_addresses=email_addresses) - def get_metadata(self): + def get_metadata(self, prefix=None): """ Returns the metadata for this object as a dict. """ - return self.manager.get_metadata(self) + return self.manager.get_metadata(self, prefix) def set_metadata(self, metadata, clear=False, prefix=None): @@ -1790,6 +1798,7 @@ class StorageObjectManager(BaseManager): "content_type": hdrs.get("content-type"), "hash": hdrs.get("etag"), "last_modified": hdrs.get("last-modified"), + "timestamp": hdrs.get("x-timestamp"), } return StorageObject(self, data, loaded=True) @@ -1849,18 +1858,19 @@ class StorageObjectManager(BaseManager): if ttl is not None: headers["X-Delete-After"] = ttl if src is data: - self._upload(obj_name, data, content_type, content_encoding, - content_length, etag, chunked, chunk_size, headers) + self._upload(obj_name, data, content_type, + content_encoding, content_length, etag, chunked, + chunk_size, headers) + elif hasattr(file_or_path, "read"): + self._upload(obj_name, file_or_path, content_type, + content_encoding, content_length, etag, False, + chunk_size, headers) else: - if isinstance(file_or_path, file): - self._upload(obj_name, file_or_path, content_type, + # Need to wrap the call in a context manager + with open(file_or_path, "rb") as ff: + self._upload(obj_name, ff, content_type, content_encoding, content_length, etag, False, - headers) - else: - # Need to wrap the call in a context manager - with open(file_or_path, "rb") as ff: - self._upload(obj_name, ff, content_type, content_encoding, - content_length, etag, False, chunk_size, headers) + chunk_size, headers) if return_none: return return self.get(obj_name) @@ -1968,7 +1978,8 @@ class StorageObjectManager(BaseManager): headers = {} if size: headers = {"Range": "bytes=0-%s" % size} - resp, resp_body = self.api.method_get(uri, headers=headers) + resp, resp_body = self.api.method_get(uri, headers=headers, + raw_content=True) if include_meta: meta_resp, meta_body = self.api.method_head(uri) return (meta_resp.headers, resp_body) @@ -1984,10 +1995,11 @@ class StorageObjectManager(BaseManager): size = size or obj_size max_size = min(size, obj_size) while True: - endpos = min(obj_size, pos + chunk_size) + endpos = min(obj_size, pos + chunk_size - 1) headers = {"Range": "bytes=%s-%s" % (pos, endpos)} - resp, resp_body = self.api.method_get(uri, headers=headers) - pos = endpos + resp, resp_body = self.api.method_get(uri, headers=headers, + raw_content=True) + pos = endpos + 1 if not resp_body: # End of file raise StopIteration @@ -2033,7 +2045,7 @@ class StorageObjectManager(BaseManager): errors - a list of any errors returned by the bulk delete call """ if nms is None: - nms = self.api.list_object_names(self.name) + nms = self.api.list_object_names(self.name, full_listing=True) return self.api.bulk_delete(self.name, nms, async=async) @@ -2083,7 +2095,7 @@ class StorageObjectManager(BaseManager): oname = utils.get_name(obj) headers = {} if email_addresses: - email_addresses = utils.coerce_string_to_list(email_addresses) + email_addresses = utils.coerce_to_list(email_addresses) headers["X-Purge-Email"] = ", ".join(email_addresses) uri = "/%s/%s" % (cname, oname) resp, resp_body = self.api.cdn_request(uri, method="DELETE", @@ -2177,6 +2189,15 @@ class StorageClient(BaseClient): def __init__(self, *args, **kwargs): # Constants used in metadata headers super(StorageClient, self).__init__(*args, **kwargs) + self._sync_summary = {"total": 0, + "uploaded": 0, + "ignored": 0, + "older": 0, + "duplicate": 0, + "failed": 0, + "failure_reasons": [], + "deleted": 0, + } self._cached_temp_url_key = None self.cdn_management_url = "" self.method_dict = { @@ -2363,6 +2384,16 @@ class StorageClient(BaseClient): method=method, key=key, cached=cached) + def list(self, limit=None, marker=None, end_marker=None, prefix=None): + """ + List the containers in this account, using the parameters to control + the pagination of containers, since by default only the first 10,000 + containers are returned. + """ + return self._manager.list(limit=limit, marker=marker, + end_marker=end_marker, prefix=prefix) + + def list_public_containers(self): """ Returns a list of the names of all CDN-enabled containers. @@ -2558,11 +2589,11 @@ class StorageClient(BaseClient): return self._manager.set_cdn_metadata(container, metadata) - def get_object_metadata(self, container, obj): + def get_object_metadata(self, container, obj, prefix=None): """ Returns the metadata for the specified object as a dict. """ - return self._manager.get_object_metadata(container, obj) + return self._manager.get_object_metadata(container, obj, prefix=prefix) def set_object_metadata(self, container, obj, metadata, clear=False, @@ -2942,7 +2973,7 @@ class StorageClient(BaseClient): if not os.path.isdir(folder_path): raise exc.FolderNotFound("No such folder: '%s'" % folder_path) - ignore = utils.coerce_string_to_list(ignore) + ignore = utils.coerce_to_list(ignore) total_bytes = utils.folder_size(folder_path, ignore) upload_key = str(uuid.uuid4()) self.folder_upload_status[upload_key] = {"continue": True, @@ -3007,12 +3038,35 @@ class StorageClient(BaseClient): log.info("Loading remote object list (prefix=%s)", object_prefix) data = cont.get_objects(prefix=object_prefix, full_listing=True) self._remote_files = dict((d.name, d) for d in data) + self._sync_summary = {"total": 0, + "uploaded": 0, + "ignored": 0, + "older": 0, + "duplicate": 0, + "failed": 0, + "failure_reasons": [], + "deleted": 0, + } self._sync_folder_to_container(folder_path, cont, prefix="", delete=delete, include_hidden=include_hidden, ignore=ignore, ignore_timestamps=ignore_timestamps, object_prefix=object_prefix, verbose=verbose) # Unset the _remote_files self._remote_files = None + if verbose: + # Log the summary + summary = self._sync_summary + log.info("Folder sync completed at %s" % time.ctime()) + log.info(" Total files processed: %s" % summary["total"]) + log.info(" Number Uploaded: %s" % summary["uploaded"]) + log.info(" Number Ignored: %s" % summary["ignored"]) + log.info(" Number Skipped (older): %s" % summary["older"]) + log.info(" Number Skipped (dupe): %s" % summary["duplicate"]) + log.info(" Number Deleted: %s" % summary["deleted"]) + log.info(" Number Failed: %s" % summary["failed"]) + if summary["failed"]: + for reason in summary["failure_reasons"]: + log.info(" Reason: %s" % reason) def _sync_folder_to_container(self, folder_path, container, prefix, delete, @@ -3022,18 +3076,19 @@ class StorageClient(BaseClient): nested folder structures. """ fnames = os.listdir(folder_path) - ignore = utils.coerce_string_to_list(ignore) + ignore = utils.coerce_to_list(ignore) log = logging.getLogger("pyrax") if not include_hidden: ignore.append(".*") for fname in fnames: if utils.match_pattern(fname, ignore): + self._sync_summary["ignored"] += 1 continue pth = os.path.join(folder_path, fname) if os.path.isdir(pth): subprefix = fname if prefix: - subprefix = "%s/%s" % (prefix, subprefix) + subprefix = os.path.join(prefix, subprefix) self._sync_folder_to_container(pth, container, prefix=subprefix, delete=delete, include_hidden=include_hidden, ignore=ignore, ignore_timestamps=ignore_timestamps, @@ -3043,7 +3098,8 @@ class StorageClient(BaseClient): fname)) local_etag = utils.get_checksum(pth) if object_prefix: - prefix = os.path.join(prefix, object_prefix) + prefix = os.path.join(object_prefix, prefix) + object_prefix = "" fullname_with_prefix = os.path.join(prefix, fname) try: obj = self._remote_files[fullname_with_prefix] @@ -3062,17 +3118,28 @@ class StorageClient(BaseClient): local_mod_str = local_mod.isoformat() if obj_time_str >= local_mod_str: # Remote object is newer + self._sync_summary["older"] += 1 if verbose: log.info("%s NOT UPLOADED because remote object is " "newer", fullname_with_prefix) log.info(" Local: %s Remote: %s" % ( local_mod_str, obj_time_str)) continue - container.upload_file(pth, obj_name=fullname_with_prefix, - etag=local_etag, return_none=True) - if verbose: - log.info("%s UPLOADED", fullname_with_prefix) + try: + container.upload_file(pth, obj_name=fullname_with_prefix, + etag=local_etag, return_none=True) + self._sync_summary["uploaded"] += 1 + if verbose: + log.info("%s UPLOADED", fullname_with_prefix) + except Exception as e: + # Record the failure, and move on + self._sync_summary["failed"] += 1 + self._sync_summary["failure_reasons"].append("%s" % e) + if verbose: + log.error("%s UPLOAD FAILED. Exception: %s" % + (fullname_with_prefix, e)) else: + self._sync_summary["duplicate"] += 1 if verbose: log.info("%s NOT UPLOADED because it already exists", fullname_with_prefix) @@ -3089,6 +3156,7 @@ class StorageClient(BaseClient): full_listing=True)) localnames = set(self._local_files) to_delete = list(objnames.difference(localnames)) + self._sync_summary["deleted"] += len(to_delete) # We don't need to wait around for this to complete. Store the thread # reference in case it is needed at some point. self._thread = self.bulk_delete(cont, to_delete, async=True) @@ -3188,7 +3256,7 @@ class FolderUploader(threading.Thread): def __init__(self, root_folder, container, ignore, upload_key, client, ttl=None): self.root_folder = root_folder.rstrip("/") - self.ignore = utils.coerce_string_to_list(ignore) + self.ignore = utils.coerce_to_list(ignore) self.upload_key = upload_key self.ttl = ttl self.client = client @@ -3241,13 +3309,17 @@ class BulkDeleter(threading.Thread): """ Threading class to allow for bulk deletion of objects from a container. """ - completed = False - results = None - def __init__(self, client, container, object_names): self.client = client self.container = container self.object_names = object_names + self.completed = False + self.results = { + "deleted": 0, + "not_found": 0, + "status": "", + "errors": [] + } threading.Thread.__init__(self) @@ -3257,14 +3329,40 @@ class BulkDeleter(threading.Thread): object_names = self.object_names cname = utils.get_name(container) ident = self.client.identity - headers = {"X-Auth-Token": ident.token, - "Content-Type": "text/plain", - } - obj_paths = ("%s/%s" % (cname, nm) for nm in object_names) - body = "\n".join(obj_paths) + headers = { + "X-Auth-Token": ident.token, + "Content-Type": "text/plain", + } uri = "/?bulk-delete=1" - resp, resp_body = self.client.method_delete(uri, data=body, - headers=headers) - status = resp_body.get("Response Status", "").split(" ")[0] - self.results = resp_body + key_map = { + "Number Not Found": "not_found", + "Response Status": "status", + "Errors": "errors", + "Number Deleted": "deleted", + "Response Body": None, + } + batch = [] + while object_names: + batch[:] = object_names[:MAX_BULK_DELETE] + del object_names[:MAX_BULK_DELETE] + + obj_paths = ("%s/%s" % (cname, nm) for nm in batch) + body = "\n".join(obj_paths) + resp, resp_body = self.client.method_delete(uri, data=body, + headers=headers) + for k, v in six.iteritems(resp_body): + if key_map[k] == "errors": + status_code = int(resp_body.get("Response Status", "200")[:3]) + if status_code != 200 and not v: + self.results["errors"].extend([[ + resp_body.get("Response Body"), + resp_body.get("Response Status") + ]]) + else: + self.results["errors"].extend(v) + elif key_map[k] in ("deleted", "not_found"): + self.results[key_map[k]] += int(v) + elif key_map[k]: + self.results[key_map[k]] = v + self.completed = True diff --git a/awx/lib/site-packages/pyrax/queueing.py b/awx/lib/site-packages/pyrax/queueing.py index bd913595bc..100ac457e8 100644 --- a/awx/lib/site-packages/pyrax/queueing.py +++ b/awx/lib/site-packages/pyrax/queueing.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- # Copyright (c)2012 Rackspace US, Inc. @@ -21,7 +20,8 @@ from functools import wraps import json import os import re -from six.moves import urllib_parse as urlparse + +from six.moves import urllib import pyrax from pyrax.client import BaseClient @@ -70,10 +70,11 @@ class BaseQueueManager(BaseManager): This class attempts to add in all the common deviations from the API standards that the regular base classes are based on. """ - def _list(self, uri, obj_class=None, body=None, return_raw=False): + def _list(self, uri, obj_class=None, body=None, return_raw=False, + other_keys=None): try: return super(BaseQueueManager, self)._list(uri, obj_class=None, - body=None, return_raw=return_raw) + body=None, return_raw=return_raw, other_keys=other_keys) except (exc.NotFound, AttributeError): return [] @@ -247,7 +248,7 @@ class QueueMessage(BaseResource): super(QueueMessage, self)._add_details(info) if self.href is None: return - parsed = urlparse.urlparse(self.href) + parsed = urllib.parse.urlparse(self.href) self.id = parsed.path.rsplit("/", 1)[-1] query = parsed.query if query: @@ -279,7 +280,7 @@ class QueueClaim(BaseResource): """ msg_dicts = info.pop("messages", []) super(QueueClaim, self)._add_details(info) - parsed = urlparse.urlparse(self.href) + parsed = urllib.parse.urlparse(self.href) self.id = parsed.path.rsplit("/", 1)[-1] self.messages = [QueueMessage(self.manager._message_manager, item) for item in msg_dicts] @@ -363,7 +364,7 @@ class QueueMessageManager(BaseQueueManager): the matching messages will be returned. This avoids pulling down all the messages in a queue and filtering on the client side. """ - ids = utils.coerce_string_to_list(ids) + ids = utils.coerce_to_list(ids) uri = "/%s?ids=%s" % (self.uri_base, ",".join(ids)) # The API is not consistent in how it returns message lists, so this # workaround is needed. @@ -379,7 +380,7 @@ class QueueMessageManager(BaseQueueManager): """ Deletes the messages whose IDs are passed in from this queue. """ - ids = utils.coerce_string_to_list(ids) + ids = utils.coerce_to_list(ids) uri = "/%s?ids=%s" % (self.uri_base, ",".join(ids)) return self.api.method_delete(uri) diff --git a/awx/lib/site-packages/pyrax/resource.py b/awx/lib/site-packages/pyrax/resource.py index 9220ab3545..a8cdce3b96 100644 --- a/awx/lib/site-packages/pyrax/resource.py +++ b/awx/lib/site-packages/pyrax/resource.py @@ -58,7 +58,7 @@ class BaseResource(object): for bash completion. """ if self.NAME_ATTR in self.__dict__ and self.HUMAN_ID: - return utils.slugify(getattr(self, self.NAME_ATTR)) + return utils.to_slug(getattr(self, self.NAME_ATTR)) return None diff --git a/awx/lib/site-packages/pyrax/utils.py b/awx/lib/site-packages/pyrax/utils.py index 88e19c7bb9..4d74b12255 100644 --- a/awx/lib/site-packages/pyrax/utils.py +++ b/awx/lib/site-packages/pyrax/utils.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import print_function @@ -19,6 +18,7 @@ import tempfile import threading import time import types +import unicodedata try: import pudb @@ -32,6 +32,10 @@ import pyrax import pyrax.exceptions as exc +SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]") +SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+") + + def runproc(cmd): """ Convenience method for executing operating system commands. @@ -293,7 +297,7 @@ def random_ascii(length=20, ascii_only=False): return _join_chars(string.ascii_letters, length) -def coerce_string_to_list(val): +def coerce_to_list(val): """ For parameters that can take either a single string or a list of strings, this function will ensure that the result is a list containing the passed @@ -316,7 +320,7 @@ def folder_size(pth, ignore=None): if not os.path.isdir(pth): raise exc.FolderNotFound - ignore = coerce_string_to_list(ignore) + ignore = coerce_to_list(ignore) def get_size(total, root, names): paths = [os.path.realpath(os.path.join(root, nm)) for nm in names] @@ -589,14 +593,12 @@ def get_name(name_or_obj): raise exc.MissingName(name_or_obj) -def params_to_dict(params, dct, local_dict): +def params_to_dict(params, dct): """ - Given a set of optional parameter names, constructs a dictionary with the - parameter name as the key, and the value for that key in the local_dict as - the value, for all non-None values. + Updates the 'dct' dictionary with the 'params' dictionary, filtering out + all those whose param value is None. """ - for param in params: - val = local_dict.get(param) + for param, val in params.items(): if val is None: continue dct[param] = val @@ -621,7 +623,7 @@ def match_pattern(nm, patterns): `fnmatch` module. For example, the pattern "*.py" will match the names of all Python scripts. """ - patterns = coerce_string_to_list(patterns) + patterns = coerce_to_list(patterns) for pat in patterns: if fnmatch.fnmatch(nm, pat): return True @@ -713,20 +715,72 @@ def import_class(import_str): return getattr(sys.modules[mod_str], class_str) -# http://code.activestate.com/recipes/ -# 577257-slugify-make-a-string-usable-in-a-url-or-filename/ -def slugify(value): - """ - Normalizes string, converts to lowercase, removes non-alpha characters, - and converts spaces to hyphens. +def safe_decode(text, incoming=None, errors='strict'): + """Decodes incoming text/bytes string using `incoming` if they're not + already unicode. - From Django's "django/template/defaultfilters.py". + This function was copied from novaclient.openstack.strutils + + :param incoming: Text's current encoding + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: text or a unicode `incoming` encoded + representation of it. + :raises TypeError: If text is not an instance of str """ - import unicodedata - _slugify_strip_re = re.compile(r"[^\w\s-]") - _slugify_hyphenate_re = re.compile(r"[-\s]+") - if not isinstance(value, six.text_type): - value = six.text_type(value) - value = unicodedata.normalize("NFKD", value).encode("ascii", "ignore") - value = six.text_type(_slugify_strip_re.sub("", value).strip().lower()) - return _slugify_hyphenate_re.sub("-", value) + if not isinstance(text, (six.string_types, six.binary_type)): + raise TypeError("%s can't be decoded" % type(text)) + + if isinstance(text, six.text_type): + return text + + if not incoming: + incoming = (sys.stdin.encoding or + sys.getdefaultencoding()) + + try: + return text.decode(incoming, errors) + except UnicodeDecodeError: + # Note(flaper87) If we get here, it means that + # sys.stdin.encoding / sys.getdefaultencoding + # didn't return a suitable encoding to decode + # text. This happens mostly when global LANG + # var is not set correctly and there's no + # default encoding. In this case, most likely + # python will use ASCII or ANSI encoders as + # default encodings but they won't be capable + # of decoding non-ASCII characters. + # + # Also, UTF-8 is being used since it's an ASCII + # extension. + return text.decode('utf-8', errors) + + +def to_slug(value, incoming=None, errors="strict"): + """Normalize string. + + Convert to lowercase, remove non-word characters, and convert spaces + to hyphens. + + This function was copied from novaclient.openstack.strutils + + Inspired by Django's `slugify` filter. + + :param value: Text to slugify + :param incoming: Text's current encoding + :param errors: Errors handling policy. See here for valid + values http://docs.python.org/2/library/codecs.html + :returns: slugified unicode representation of `value` + :raises TypeError: If text is not an instance of str + """ + value = safe_decode(value, incoming, errors) + # NOTE(aababilov): no need to use safe_(encode|decode) here: + # encodings are always "ascii", error handling is always "ignore" + # and types are always known (first: unicode; second: str) + value = unicodedata.normalize("NFKD", value).encode( + "ascii", "ignore").decode("ascii") + value = SLUGIFY_STRIP_RE.sub("", value).strip().lower() + return SLUGIFY_HYPHENATE_RE.sub("-", value) + +# For backwards compatibility, alias slugify to point to_slug +slugify = to_slug diff --git a/awx/lib/site-packages/pyrax/version.py b/awx/lib/site-packages/pyrax/version.py index ab7d7141e1..4f628f1fa7 100644 --- a/awx/lib/site-packages/pyrax/version.py +++ b/awx/lib/site-packages/pyrax/version.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python # -*- coding: utf-8 -*- -version = "1.9.0" +version = "1.9.3"