diff options
Diffstat (limited to 'openstackclient')
59 files changed, 2766 insertions, 1096 deletions
diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index b310f3ac..4206ad00 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -19,6 +19,9 @@ import logging import pkg_resources import sys +from keystoneclient.auth.identity import v2 as v2_auth +from keystoneclient.auth.identity import v3 as v3_auth +from keystoneclient import session from openstackclient.identity import client as identity_client @@ -49,7 +52,7 @@ class ClientManager(object): user_domain_id=None, user_domain_name=None, project_domain_id=None, project_domain_name=None, region_name=None, api_version=None, verify=True, - trust_id=None): + trust_id=None, timing=None): self._token = token self._url = url self._auth_url = auth_url @@ -67,6 +70,7 @@ class ClientManager(object): self._api_version = api_version self._trust_id = trust_id self._service_catalog = None + self.timing = timing # verify is the Requests-compatible form self._verify = verify @@ -76,15 +80,70 @@ class ClientManager(object): self._insecure = not verify else: self._cacert = verify - self._insecure = True + self._insecure = False + + ver_prefix = identity_client.AUTH_VERSIONS[ + self._api_version[identity_client.API_NAME] + ] + + # Get logging from root logger + root_logger = logging.getLogger('') + LOG.setLevel(root_logger.getEffectiveLevel()) + + # NOTE(dtroyer): These plugins are hard-coded for the first step + # in using the new Keystone auth plugins. + + if self._url: + LOG.debug('Using token auth %s', ver_prefix) + if ver_prefix == 'v2': + self.auth = v2_auth.Token( + auth_url=url, + token=token, + ) + else: + self.auth = v3_auth.Token( + auth_url=url, + token=token, + ) + else: + LOG.debug('Using password auth %s', ver_prefix) + if ver_prefix == 'v2': + self.auth = v2_auth.Password( + auth_url=auth_url, + username=username, + password=password, + trust_id=trust_id, + tenant_id=project_id, + tenant_name=project_name, + ) + else: + self.auth = v3_auth.Password( + auth_url=auth_url, + username=username, + password=password, + trust_id=trust_id, + user_domain_id=user_domain_id, + user_domain_name=user_domain_name, + domain_id=domain_id, + domain_name=domain_name, + project_id=project_id, + project_name=project_name, + project_domain_id=project_domain_id, + project_domain_name=project_domain_name, + ) + + self.session = session.Session( + auth=self.auth, + verify=verify, + ) self.auth_ref = None - if not self._url: + # Trigger the auth call + self.auth_ref = self.session.auth.get_auth_ref(self.session) # Populate other password flow attributes - self.auth_ref = self.identity.auth_ref - self._token = self.identity.auth_token - self._service_catalog = self.identity.service_catalog + self._token = self.session.auth.get_token(self.session) + self._service_catalog = self.auth_ref.service_catalog return @@ -116,7 +175,7 @@ def get_extension_modules(group): setattr( ClientManager, - ep.name, + module.API_NAME, ClientCache( getattr(sys.modules[ep.module_name], 'make_client', None) ), diff --git a/openstackclient/common/restapi.py b/openstackclient/common/restapi.py deleted file mode 100644 index f20ad23d..00000000 --- a/openstackclient/common/restapi.py +++ /dev/null @@ -1,320 +0,0 @@ -# Copyright 2013 Nebula Inc. -# -# 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. -# - -"""REST API bits""" - -import json -import logging -import requests - -try: - from urllib.parse import urlencode -except ImportError: - from urllib import urlencode - - -USER_AGENT = 'RAPI' - -_logger = logging.getLogger(__name__) - - -class RESTApi(object): - """A REST API client that handles the interface from us to the server - - RESTApi is requests.Session wrapper that knows how to do: - * JSON serialization/deserialization - * log requests in 'curl' format - * basic API boilerplate for create/delete/list/set/show verbs - - * authentication is handled elsewhere and a token is passed in - - The expectation that there will be a RESTApi object per authentication - token in use, i.e. project/username/auth_endpoint - - On the other hand, a Client knows details about the specific REST Api that - it communicates with, such as the available endpoints, API versions, etc. - """ - - def __init__( - self, - session=None, - auth_header=None, - user_agent=USER_AGENT, - verify=True, - logger=None, - debug=None, - ): - """Construct a new REST client - - :param object session: A Session object to be used for - communicating with the identity service. - :param string auth_header: A token from an initialized auth_reference - to be used in the X-Auth-Token header - :param string user_agent: Set the User-Agent header in the requests - :param boolean/string verify: If ``True``, the SSL cert will be - verified. A CA_BUNDLE path can also be - provided. - :param logging.Logger logger: A logger to output to. (optional) - :param boolean debug: Enables debug logging of all request and - responses to identity service. - default False (optional) - """ - - self.set_auth(auth_header) - self.debug = debug - - if not session: - # We create a default session object - session = requests.Session() - self.session = session - self.session.verify = verify - self.session.user_agent = user_agent - - if logger: - self.logger = logger - else: - self.logger = _logger - - def set_auth(self, auth_header): - """Sets the current auth blob""" - self.auth_header = auth_header - - def set_header(self, header, content): - """Sets passed in headers into the session headers - - Replaces existing headers!! - """ - if content is None: - del self.session.headers[header] - else: - self.session.headers[header] = content - - def request(self, method, url, **kwargs): - """Make an authenticated (if token available) request - - :param method: Request HTTP method - :param url: Request URL - :param data: Request body - :param json: Request body to be encoded as JSON - Overwrites ``data`` argument if present - """ - - kwargs.setdefault('headers', {}) - if self.auth_header: - kwargs['headers']['X-Auth-Token'] = self.auth_header - - if 'json' in kwargs and isinstance(kwargs['json'], type({})): - kwargs['data'] = json.dumps(kwargs.pop('json')) - kwargs['headers']['Content-Type'] = 'application/json' - - kwargs.setdefault('allow_redirects', True) - - if self.debug: - self._log_request(method, url, **kwargs) - - response = self.session.request(method, url, **kwargs) - - if self.debug: - self._log_response(response) - - return self._error_handler(response) - - def _error_handler(self, response): - if response.status_code < 200 or response.status_code >= 300: - self.logger.debug( - "ERROR: %s", - response.text, - ) - response.raise_for_status() - return response - - # Convenience methods to mimic the ones provided by requests.Session - - def delete(self, url, **kwargs): - """Send a DELETE request. Returns :class:`requests.Response` object. - - :param url: Request URL - :param \*\*kwargs: Optional arguments passed to ``request`` - """ - - return self.request('DELETE', url, **kwargs) - - def get(self, url, **kwargs): - """Send a GET request. Returns :class:`requests.Response` object. - - :param url: Request URL - :param \*\*kwargs: Optional arguments passed to ``request`` - """ - - return self.request('GET', url, **kwargs) - - def head(self, url, **kwargs): - """Send a HEAD request. Returns :class:`requests.Response` object. - - :param url: Request URL - :param \*\*kwargs: Optional arguments passed to ``request`` - """ - - kwargs.setdefault('allow_redirects', False) - return self.request('HEAD', url, **kwargs) - - def options(self, url, **kwargs): - """Send an OPTIONS request. Returns :class:`requests.Response` object. - - :param url: Request URL - :param \*\*kwargs: Optional arguments passed to ``request`` - """ - - return self.request('OPTIONS', url, **kwargs) - - def patch(self, url, data=None, json=None, **kwargs): - """Send a PUT request. Returns :class:`requests.Response` object. - - :param url: Request URL - :param data: Request body - :param json: Request body to be encoded as JSON - Overwrites ``data`` argument if present - :param \*\*kwargs: Optional arguments passed to ``request`` - """ - - return self.request('PATCH', url, data=data, json=json, **kwargs) - - def post(self, url, data=None, json=None, **kwargs): - """Send a POST request. Returns :class:`requests.Response` object. - - :param url: Request URL - :param data: Request body - :param json: Request body to be encoded as JSON - Overwrites ``data`` argument if present - :param \*\*kwargs: Optional arguments passed to ``request`` - """ - - return self.request('POST', url, data=data, json=json, **kwargs) - - def put(self, url, data=None, json=None, **kwargs): - """Send a PUT request. Returns :class:`requests.Response` object. - - :param url: Request URL - :param data: Request body - :param json: Request body to be encoded as JSON - Overwrites ``data`` argument if present - :param \*\*kwargs: Optional arguments passed to ``request`` - """ - - return self.request('PUT', url, data=data, json=json, **kwargs) - - # Command verb methods - - def create(self, url, data=None, response_key=None, **kwargs): - """Create a new object via a POST request - - :param url: Request URL - :param data: Request body, wil be JSON encoded - :param response_key: Dict key in response body to extract - :param \*\*kwargs: Optional arguments passed to ``request`` - """ - - response = self.request('POST', url, json=data, **kwargs) - if response_key: - return response.json()[response_key] - else: - return response.json() - - def list(self, url, data=None, response_key=None, **kwargs): - """Retrieve a list of objects via a GET or POST request - - :param url: Request URL - :param data: Request body, will be JSON encoded - :param response_key: Dict key in response body to extract - :param \*\*kwargs: Optional arguments passed to ``request`` - """ - - if data: - response = self.request('POST', url, json=data, **kwargs) - else: - response = self.request('GET', url, **kwargs) - - if response_key: - return response.json()[response_key] - else: - return response.json() - - def set(self, url, data=None, response_key=None, **kwargs): - """Update an object via a PUT request - - :param url: Request URL - :param data: Request body - :param json: Request body to be encoded as JSON - Overwrites ``data`` argument if present - :param \*\*kwargs: Optional arguments passed to ``request`` - """ - - response = self.request('PUT', url, json=data) - if data: - if response_key: - return response.json()[response_key] - else: - return response.json() - else: - # Nothing to do here - return None - - def show(self, url, response_key=None, **kwargs): - """Retrieve a single object via a GET request - - :param url: Request URL - :param response_key: Dict key in response body to extract - :param \*\*kwargs: Optional arguments passed to ``request`` - """ - - response = self.request('GET', url, **kwargs) - if response_key: - return response.json()[response_key] - else: - return response.json() - - def _log_request(self, method, url, **kwargs): - if 'params' in kwargs and kwargs['params'] != {}: - url += '?' + urlencode(kwargs['params']) - - string_parts = [ - "curl -i", - "-X '%s'" % method, - "'%s'" % url, - ] - - for element in kwargs['headers']: - header = " -H '%s: %s'" % (element, kwargs['headers'][element]) - string_parts.append(header) - - self.logger.debug("REQ: %s" % " ".join(string_parts)) - if 'data' in kwargs: - self.logger.debug(" REQ BODY: %r\n" % (kwargs['data'])) - - def _log_response(self, response): - self.logger.debug( - "RESP: [%s] %r\n", - response.status_code, - response.headers, - ) - if response._content_consumed: - self.logger.debug( - " RESP BODY: %s\n", - response.text, - ) - self.logger.debug( - " encoding: %s", - response.encoding, - ) diff --git a/openstackclient/common/timing.py b/openstackclient/common/timing.py new file mode 100644 index 00000000..1c94682c --- /dev/null +++ b/openstackclient/common/timing.py @@ -0,0 +1,44 @@ +# 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. +# + +"""Timing Implementation""" + +import logging + +from cliff import lister + + +class Timing(lister.Lister): + """Show timing data""" + + log = logging.getLogger(__name__ + '.Timing') + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)' % parsed_args) + + column_headers = ( + 'URL', + 'Seconds', + ) + + results = [] + total = 0.0 + for url, start, end in self.app.timing_data: + seconds = end - start + total += seconds + results.append((url, seconds)) + results.append(('Total', total)) + return ( + column_headers, + results, + ) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 51c3ed4b..c013deee 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -19,10 +19,10 @@ import getpass import logging import os import six -import sys import time from openstackclient.common import exceptions +from openstackclient.openstack.common import importutils def find_resource(manager, name_or_id): @@ -77,7 +77,7 @@ def format_dict(data): """ output = "" - for s in data: + for s in sorted(data): output = output + s + "='" + six.text_type(data[s]) + "', " return output[:-2] @@ -89,7 +89,7 @@ def format_list(data): :rtype: a string formatted to a,b,c """ - return ', '.join(data) + return ', '.join(sorted(data)) def get_item_properties(item, fields, mixed_case_fields=[], formatters={}): @@ -157,17 +157,6 @@ def env(*vars, **kwargs): return kwargs.get('default', '') -def import_class(import_str): - """Returns a class from a string including module and class - - :param import_str: a string representation of the class name - :rtype: the requested class - """ - mod_str, _sep, class_str = import_str.rpartition('.') - __import__(mod_str) - return getattr(sys.modules[mod_str], class_str) - - def get_client_class(api_name, version, version_map): """Returns the client class for the requested API version @@ -183,7 +172,7 @@ def get_client_class(api_name, version, version_map): (api_name, version, ', '.join(version_map.keys()))) raise exceptions.UnsupportedVersion(msg) - return import_class(client_path) + return importutils.import_class(client_path) def wait_for_status(status_f, @@ -231,12 +220,15 @@ def get_effective_log_level(): return min_log_lvl -def get_password(stdin): +def get_password(stdin, prompt=None, confirm=True): + message = prompt or "User Password:" if hasattr(stdin, 'isatty') and stdin.isatty(): try: while True: - first_pass = getpass.getpass("User password: ") - second_pass = getpass.getpass("Repeat user password: ") + first_pass = getpass.getpass(message) + if not confirm: + return first_pass + second_pass = getpass.getpass("Repeat " + message) if first_pass == second_pass: return first_pass print("The passwords entered were not the same") diff --git a/openstackclient/compute/client.py b/openstackclient/compute/client.py index 36391c6d..dc50507e 100644 --- a/openstackclient/compute/client.py +++ b/openstackclient/compute/client.py @@ -57,7 +57,9 @@ def make_client(instance): service_type=API_NAME, # FIXME(dhellmann): what is service_name? service_name='', - http_log_debug=http_log_debug) + http_log_debug=http_log_debug, + timings=instance.timing, + ) # Populate the Nova client to skip another auth query to Identity if instance._url: diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index e1f84e23..8206f302 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -26,7 +26,7 @@ from openstackclient.common import utils class ShowConsoleLog(command.Command): - """Show console-log command""" + """Show server's console output""" log = logging.getLogger(__name__ + '.ShowConsoleLog') @@ -35,7 +35,7 @@ class ShowConsoleLog(command.Command): parser.add_argument( 'server', metavar='<server>', - help='Name or ID of server to display console log', + help='Server (name or ID)', ) parser.add_argument( '--lines', @@ -67,7 +67,7 @@ class ShowConsoleLog(command.Command): class ShowConsoleURL(show.ShowOne): - """Show console-url command""" + """Show server's remote console URL""" log = logging.getLogger(__name__ + '.ShowConsoleURL') @@ -76,7 +76,7 @@ class ShowConsoleURL(show.ShowOne): parser.add_argument( 'server', metavar='<server>', - help='Name or ID of server to display console log', + help='Server (name or ID)', ) type_group = parser.add_mutually_exclusive_group() type_group.add_argument( diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 73429796..6dd00b1b 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -26,7 +26,7 @@ from openstackclient.common import utils class CreateFlavor(show.ShowOne): - """Create flavor command""" + """Create new flavor""" log = logging.getLogger(__name__ + ".CreateFlavor") @@ -35,7 +35,8 @@ class CreateFlavor(show.ShowOne): parser.add_argument( "name", metavar="<name>", - help="Name of the new flavor") + help="New flavor name", + ) parser.add_argument( "--id", metavar="<id>", @@ -84,12 +85,14 @@ class CreateFlavor(show.ShowOne): dest="public", action="store_true", default=True, - help="Flavor is accessible to the public (default)") + help="Flavor is accessible to other projects (default)", + ) public_group.add_argument( "--private", dest="public", action="store_false", - help="Flavor is inaccessible to the public") + help="Flavor is inaccessible to other projects", + ) return parser def take_action(self, parsed_args): @@ -115,7 +118,7 @@ class CreateFlavor(show.ShowOne): class DeleteFlavor(command.Command): - """Delete flavor command""" + """Delete a flavor""" log = logging.getLogger(__name__ + ".DeleteFlavor") @@ -124,7 +127,8 @@ class DeleteFlavor(command.Command): parser.add_argument( "flavor", metavar="<flavor>", - help="Name or ID of flavor to delete") + help="Flavor to delete (name or ID)", + ) return parser def take_action(self, parsed_args): @@ -137,7 +141,7 @@ class DeleteFlavor(command.Command): class ListFlavor(lister.Lister): - """List flavor command""" + """List flavors""" log = logging.getLogger(__name__ + ".ListFlavor") @@ -164,7 +168,7 @@ class ListFlavor(lister.Lister): class ShowFlavor(show.ShowOne): - """Show flavor command""" + """Show flavor details""" log = logging.getLogger(__name__ + ".ShowFlavor") @@ -173,7 +177,8 @@ class ShowFlavor(show.ShowOne): parser.add_argument( "flavor", metavar="<flavor>", - help="Name or ID of flavor to display") + help="Flavor to display (name or ID)", + ) return parser def take_action(self, parsed_args): diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index 72b19c6c..658f0d5a 100644 --- a/openstackclient/compute/v2/floatingip.py +++ b/openstackclient/compute/v2/floatingip.py @@ -26,7 +26,7 @@ from openstackclient.common import utils class AddFloatingIP(command.Command): - """Add floating-ip command""" + """Add floating-ip to server""" log = logging.getLogger(__name__ + ".AddFloatingIP") @@ -40,7 +40,7 @@ class AddFloatingIP(command.Command): parser.add_argument( "server", metavar="<server>", - help="Name of the server to receive the IP address", + help="Server to receive the IP address (name or ID)", ) return parser @@ -56,7 +56,7 @@ class AddFloatingIP(command.Command): class CreateFloatingIP(show.ShowOne): - """Create floating-ip command""" + """Create new floating-ip""" log = logging.getLogger(__name__ + '.CreateFloatingIP') @@ -80,7 +80,7 @@ class CreateFloatingIP(show.ShowOne): class DeleteFloatingIP(command.Command): - """Delete floating-ip command""" + """Delete a floating-ip""" log = logging.getLogger(__name__ + '.DeleteFloatingIP') @@ -107,7 +107,7 @@ class DeleteFloatingIP(command.Command): class ListFloatingIP(lister.Lister): - """List floating-ip command""" + """List floating-ips""" log = logging.getLogger(__name__ + '.ListFloatingIP') @@ -127,7 +127,7 @@ class ListFloatingIP(lister.Lister): class RemoveFloatingIP(command.Command): - """Remove floating-ip command""" + """Remove floating-ip from server""" log = logging.getLogger(__name__ + ".RemoveFloatingIP") @@ -141,7 +141,7 @@ class RemoveFloatingIP(command.Command): parser.add_argument( "server", metavar="<server>", - help="Name of the server to remove the IP address from", + help="Server to remove the IP address from (name or ID)", ) return parser diff --git a/openstackclient/compute/v2/floatingippool.py b/openstackclient/compute/v2/floatingippool.py index d5e8d0dd..db1c9f0f 100644 --- a/openstackclient/compute/v2/floatingippool.py +++ b/openstackclient/compute/v2/floatingippool.py @@ -23,7 +23,7 @@ from openstackclient.common import utils class ListFloatingIPPool(lister.Lister): - """List floating-ip-pool command""" + """List floating-ip-pools""" log = logging.getLogger(__name__ + '.ListFloatingIPPool') diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 334987e2..e01258d1 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -25,7 +25,7 @@ from openstackclient.common import utils class ListHypervisor(lister.Lister): - """List hypervisor command""" + """List hypervisors""" log = logging.getLogger(__name__ + ".ListHypervisor") @@ -33,9 +33,9 @@ class ListHypervisor(lister.Lister): parser = super(ListHypervisor, self).get_parser(prog_name) parser.add_argument( "--matching", - metavar="<hostname>", - help="List hypervisors with hostnames matching the given" - " substring") + metavar="<hostname-str>", + help="Filter hypervisors using <hostname-str> substring", + ) return parser def take_action(self, parsed_args): @@ -58,7 +58,7 @@ class ListHypervisor(lister.Lister): class ShowHypervisor(show.ShowOne): - """Show hypervisor command""" + """Show hypervisor details""" log = logging.getLogger(__name__ + ".ShowHypervisor") diff --git a/openstackclient/compute/v2/keypair.py b/openstackclient/compute/v2/keypair.py index 972443a4..22c07ef7 100644 --- a/openstackclient/compute/v2/keypair.py +++ b/openstackclient/compute/v2/keypair.py @@ -29,7 +29,7 @@ from openstackclient.common import utils class CreateKeypair(show.ShowOne): - """Create keypair command""" + """Create new keypair""" log = logging.getLogger(__name__ + '.CreateKeypair') @@ -57,8 +57,9 @@ class CreateKeypair(show.ShowOne): with open(os.path.expanduser(parsed_args.public_key)) as p: public_key = p.read() except IOError as e: - raise exceptions.CommandError( - "Key file %s not found: %s" % (parsed_args.public_key, e)) + msg = "Key file %s not found: %s" + raise exceptions.CommandError(msg + % (parsed_args.public_key, e)) keypair = compute_client.keypairs.create( parsed_args.name, @@ -79,7 +80,7 @@ class CreateKeypair(show.ShowOne): class DeleteKeypair(command.Command): - """Delete keypair command""" + """Delete a keypair""" log = logging.getLogger(__name__ + '.DeleteKeypair') @@ -100,7 +101,7 @@ class DeleteKeypair(command.Command): class ListKeypair(lister.Lister): - """List keypair command""" + """List keypairs""" log = logging.getLogger(__name__ + ".ListKeypair") @@ -120,7 +121,7 @@ class ListKeypair(lister.Lister): class ShowKeypair(show.ShowOne): - """Show keypair command""" + """Show keypair details""" log = logging.getLogger(__name__ + '.ShowKeypair') diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 0ba55c98..cd330857 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -23,6 +23,7 @@ from cliff import command from cliff import lister from cliff import show +from keystoneclient.openstack.common.apiclient import exceptions as ksc_exc from novaclient.v1_1 import security_group_rules from openstackclient.common import parseractions from openstackclient.common import utils @@ -150,10 +151,15 @@ class ListSecurityGroup(lister.Lister): search = {'all_tenants': parsed_args.all_projects} data = compute_client.security_groups.list(search_opts=search) - projects = self.app.client_manager.identity.projects.list() project_hash = {} - for project in projects: - project_hash[project.id] = project + try: + projects = self.app.client_manager.identity.projects.list() + except ksc_exc.Forbidden: + # This fails when the user is not an admin, just move along + pass + else: + for project in projects: + project_hash[project.id] = project return (column_headers, (utils.get_item_properties( diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 2dcc7ae9..ec7f212d 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -175,7 +175,7 @@ class AddServerSecurityGroup(command.Command): parsed_args.group, ) - server.add_security_group(security_group) + server.add_security_group(security_group.name) return @@ -300,19 +300,22 @@ class CreateServer(show.ShowOne): raise exceptions.CommandError("Can't open '%s': %s" % (src, e)) if parsed_args.min > parsed_args.max: - raise exceptions.CommandError("min instances should be <= " - "max instances") + msg = "min instances should be <= max instances" + raise exceptions.CommandError(msg) if parsed_args.min < 1: - raise exceptions.CommandError("min instances should be > 0") + msg = "min instances should be > 0" + raise exceptions.CommandError(msg) if parsed_args.max < 1: - raise exceptions.CommandError("max instances should be > 0") + msg = "max instances should be > 0" + raise exceptions.CommandError(msg) userdata = None if parsed_args.user_data: try: userdata = open(parsed_args.user_data) except IOError as e: - raise exceptions.CommandError("Can't open '%s': %s" % + msg = "Can't open '%s': %s" + raise exceptions.CommandError(msg % (parsed_args.user_data, e)) block_device_mapping = dict(v.split('=', 1) @@ -938,27 +941,32 @@ class RescueServer(show.ShowOne): class ResizeServer(command.Command): - """Convert server to a new flavor""" + """Scale server to a new flavor""" log = logging.getLogger(__name__ + '.ResizeServer') def get_parser(self, prog_name): parser = super(ResizeServer, self).get_parser(prog_name) phase_group = parser.add_mutually_exclusive_group() + parser.add_argument( + 'server', + metavar='<server>', + help='Server (name or ID)', + ) phase_group.add_argument( '--flavor', metavar='<flavor>', - help='Resize server to this flavor', + help='Resize server to specified flavor', ) phase_group.add_argument( '--verify', action="store_true", - help='Verify previous server resize', + help='Verify server resize is complete', ) phase_group.add_argument( '--revert', action="store_true", - help='Restore server before resize', + help='Restore server state before resize', ) parser.add_argument( '--wait', @@ -980,7 +988,7 @@ class ResizeServer(command.Command): compute_client.flavors, parsed_args.flavor, ) - server.resize(flavor) + compute_client.servers.resize(server, flavor) if parsed_args.wait: if utils.wait_for_status( compute_client.servers.get, @@ -993,9 +1001,9 @@ class ResizeServer(command.Command): sys.stdout.write('\nError resizing server') raise SystemExit elif parsed_args.verify: - server.confirm_resize() + compute_client.servers.confirm_resize(server) elif parsed_args.revert: - server.revert_resize() + compute_client.servers.revert_resize(server) class ResumeServer(command.Command): @@ -1077,8 +1085,8 @@ class SetServer(command.Command): if p1 == p2: server.change_password(p1) else: - raise exceptions.CommandError( - "Passwords do not match, password unchanged") + msg = "Passwords do not match, password unchanged" + raise exceptions.CommandError(msg) class ShowServer(show.ShowOne): diff --git a/openstackclient/i18n.py b/openstackclient/i18n.py new file mode 100644 index 00000000..bd52d648 --- /dev/null +++ b/openstackclient/i18n.py @@ -0,0 +1,31 @@ +# Copyright 2012-2013 OpenStack Foundation +# +# 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 oslo import i18n + +_translators = i18n.TranslatorFactory(domain='openstackclient') + +# 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 diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index 72e8bfae..a43b50e3 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -29,6 +29,12 @@ API_VERSIONS = { '3': 'keystoneclient.v3.client.Client', } +# Translate our API version to auth plugin version prefix +AUTH_VERSIONS = { + '2.0': 'v2', + '3': 'v3', +} + def make_client(instance): """Returns an identity service client.""" @@ -36,8 +42,10 @@ def make_client(instance): API_NAME, instance._api_version[API_NAME], API_VERSIONS) - LOG.debug('Instantiating identity client: %s' % identity_client) + LOG.debug('Instantiating identity client: %s', identity_client) + # TODO(dtroyer): Something doesn't like the session.auth when using + # token auth, chase that down. if instance._url: LOG.debug('Using token auth') client = identity_client( @@ -50,26 +58,39 @@ def make_client(instance): else: LOG.debug('Using password auth') client = identity_client( - username=instance._username, - password=instance._password, - user_domain_id=instance._user_domain_id, - user_domain_name=instance._user_domain_name, - project_domain_id=instance._project_domain_id, - project_domain_name=instance._project_domain_name, - domain_id=instance._domain_id, - domain_name=instance._domain_name, - tenant_name=instance._project_name, - tenant_id=instance._project_id, - auth_url=instance._auth_url, - region_name=instance._region_name, + session=instance.session, cacert=instance._cacert, - insecure=instance._insecure, - trust_id=instance._trust_id, ) - instance.auth_ref = client.auth_ref + + # TODO(dtroyer): the identity v2 role commands use this yet, fix that + # so we can remove it + if not instance._url: + instance.auth_ref = instance.auth.get_auth_ref(instance.session) + return client +def build_option_parser(parser): + """Hook to add global options""" + parser.add_argument( + '--os-identity-api-version', + metavar='<identity-api-version>', + default=utils.env( + 'OS_IDENTITY_API_VERSION', + default=DEFAULT_IDENTITY_API_VERSION), + help='Identity API version, default=' + + DEFAULT_IDENTITY_API_VERSION + + ' (Env: OS_IDENTITY_API_VERSION)') + parser.add_argument( + '--os-trust-id', + metavar='<trust-id>', + default=utils.env('OS_TRUST_ID'), + help='Trust ID to use when authenticating. ' + 'This can only be used with Keystone v3 API ' + '(Env: OS_TRUST_ID)') + return parser + + class IdentityClientv2_0(identity_client_v2_0.Client): """Tweak the earlier client class to deal with some changes""" def __getattr__(self, name): diff --git a/openstackclient/identity/v2_0/token.py b/openstackclient/identity/v2_0/token.py index 01e1b3b2..f3fedc01 100644 --- a/openstackclient/identity/v2_0/token.py +++ b/openstackclient/identity/v2_0/token.py @@ -33,15 +33,14 @@ class IssueToken(show.ShowOne): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) - identity_client = self.app.client_manager.identity - token = identity_client.service_catalog.get_token() + token = self.app.client_manager.auth_ref.service_catalog.get_token() token['project_id'] = token.pop('tenant_id') return zip(*sorted(six.iteritems(token))) class RevokeToken(command.Command): - """Revoke token command""" + """Revoke existing token""" log = logging.getLogger(__name__ + '.RevokeToken') diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 60af6ddb..b291c882 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -99,9 +99,10 @@ class CreateUser(show.ShowOne): # NOTE(dtroyer): The users.create() method wants 'tenant_id' but # the returned resource has 'tenantId'. Sigh. # We're using project_id now inside OSC so there. - user._info.update( - {'project_id': user._info.pop('tenantId')} - ) + if 'tenantId' in user._info: + user._info.update( + {'project_id': user._info.pop('tenantId')} + ) info = {} info.update(user._info) diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index f9763847..49397afc 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -135,13 +135,12 @@ class SetDomain(command.Command): '--enable', dest='enabled', action='store_true', - default=True, help='Enable domain (default)', ) enable_group.add_argument( '--disable', - dest='enabled', - action='store_false', + dest='disabled', + action='store_true', help='Disable domain', ) return parser @@ -156,8 +155,10 @@ class SetDomain(command.Command): kwargs['name'] = parsed_args.name if parsed_args.description: kwargs['description'] = parsed_args.description - if 'enabled' in parsed_args: - kwargs['enabled'] = parsed_args.enabled + if parsed_args.enabled: + kwargs['enabled'] = True + if parsed_args.disabled: + kwargs['enabled'] = False if not kwargs: sys.stdout.write("Domain not updated, no arguments present") diff --git a/openstackclient/identity/v3/endpoint.py b/openstackclient/identity/v3/endpoint.py index 93d77be3..39798b2d 100644 --- a/openstackclient/identity/v3/endpoint.py +++ b/openstackclient/identity/v3/endpoint.py @@ -57,13 +57,13 @@ class CreateEndpoint(show.ShowOne): dest='enabled', action='store_true', default=True, - help='Enable user', + help='Enable endpoint', ) enable_group.add_argument( '--disable', dest='enabled', action='store_false', - help='Disable user', + help='Disable endpoint', ) return parser @@ -114,12 +114,38 @@ class ListEndpoint(lister.Lister): log = logging.getLogger(__name__ + '.ListEndpoint') + def get_parser(self, prog_name): + parser = super(ListEndpoint, self).get_parser(prog_name) + parser.add_argument( + '--service', + metavar='<service>', + help='Filter by a specific service') + parser.add_argument( + '--interface', + metavar='<interface>', + choices=['admin', 'public', 'internal'], + help='Filter by a specific interface, must be admin, public or' + ' internal') + parser.add_argument( + '--region', + metavar='<region>', + help='Filter by a specific region') + return parser + def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity columns = ('ID', 'Region', 'Service Name', 'Service Type', 'Enabled', 'Interface', 'URL') - data = identity_client.endpoints.list() + kwargs = {} + if parsed_args.service: + service = common.find_service(identity_client, parsed_args.service) + kwargs['service'] = service.id + if parsed_args.interface: + kwargs['interface'] = parsed_args.interface + if parsed_args.region: + kwargs['region'] = parsed_args.region + data = identity_client.endpoints.list(**kwargs) for ep in data: service = common.find_service(identity_client, ep.service_id) @@ -165,14 +191,13 @@ class SetEndpoint(command.Command): '--enable', dest='enabled', action='store_true', - default=True, - help='Enable user', + help='Enable endpoint', ) enable_group.add_argument( '--disable', - dest='enabled', - action='store_false', - help='Disable user', + dest='disabled', + action='store_true', + help='Disable endpoint', ) return parser @@ -181,20 +206,31 @@ class SetEndpoint(command.Command): identity_client = self.app.client_manager.identity endpoint = utils.find_resource(identity_client.endpoints, parsed_args.endpoint) - service = common.find_service(identity_client, parsed_args.service) if (not parsed_args.interface and not parsed_args.url - and not parsed_args.service and not parsed_args.region): + and not parsed_args.service and not parsed_args.region + and not parsed_args.enabled and not parsed_args.disabled): sys.stdout.write("Endpoint not updated, no arguments present") return + service_id = None + if parsed_args.service: + service = common.find_service(identity_client, parsed_args.service) + service_id = service.id + + enabled = None + if parsed_args.enabled: + enabled = True + if parsed_args.disabled: + enabled = False + identity_client.endpoints.update( endpoint.id, - service=service.id, + service=service_id, url=parsed_args.url, interface=parsed_args.interface, region=parsed_args.region, - enabled=parsed_args.enabled + enabled=enabled ) return diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index b60678b5..5e8ee566 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -25,7 +25,7 @@ from openstackclient.common import utils class CreateIdentityProvider(show.ShowOne): - """Create identity_provider command""" + """Create new identity provider""" log = logging.getLogger(__name__ + '.CreateIdentityProvider') @@ -33,7 +33,7 @@ class CreateIdentityProvider(show.ShowOne): parser = super(CreateIdentityProvider, self).get_parser(prog_name) parser.add_argument( 'identity_provider_id', - metavar='<identity_provider_id>', + metavar='<identity-provider-id>', help='New identity provider ID (must be unique)' ) parser.add_argument( @@ -61,8 +61,8 @@ class CreateIdentityProvider(show.ShowOne): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity - idp = identity_client.identity_providers.create( - parsed_args.identity_provider_id, + idp = identity_client.federation.identity_providers.create( + id=parsed_args.identity_provider_id, description=parsed_args.description, enabled=parsed_args.enabled) info = {} @@ -71,7 +71,7 @@ class CreateIdentityProvider(show.ShowOne): class DeleteIdentityProvider(command.Command): - """Delete identity provider""" + """Delete an identity provider""" log = logging.getLogger(__name__ + '.DeleteIdentityProvider') @@ -79,15 +79,15 @@ class DeleteIdentityProvider(command.Command): parser = super(DeleteIdentityProvider, self).get_parser(prog_name) parser.add_argument( 'identity_provider', - metavar='<identity_provider>', - help='ID of the identity provider to be deleted', + metavar='<identity-provider-id>', + help='Identity provider ID to delete', ) return parser def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity - identity_client.identity_providers.delete( + identity_client.federation.identity_providers.delete( parsed_args.identity_provider) return @@ -100,7 +100,8 @@ class ListIdentityProvider(lister.Lister): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) columns = ('ID', 'Enabled', 'Description') - data = self.app.client_manager.identity.identity_providers.list() + identity_client = self.app.client_manager.identity + data = identity_client.federation.identity_providers.list() return (columns, (utils.get_item_properties( s, columns, @@ -109,7 +110,7 @@ class ListIdentityProvider(lister.Lister): class SetIdentityProvider(command.Command): - """Set identity provider""" + """Set identity provider properties""" log = logging.getLogger(__name__ + '.SetIdentityProvider') @@ -117,8 +118,8 @@ class SetIdentityProvider(command.Command): parser = super(SetIdentityProvider, self).get_parser(prog_name) parser.add_argument( 'identity_provider', - metavar='<identity_provider>', - help='ID of the identity provider to be changed', + metavar='<identity-provider-id>', + help='Identity provider ID to change', ) enable_identity_provider = parser.add_mutually_exclusive_group() @@ -136,7 +137,7 @@ class SetIdentityProvider(command.Command): def take_action(self, parsed_args): self.log.debug('take_action(%s)', parsed_args) - identity_client = self.app.client_manager.identity + federation_client = self.app.client_manager.identity.federation if parsed_args.enable is True: enabled = True @@ -147,7 +148,7 @@ class SetIdentityProvider(command.Command): "no arguments present") return (None, None) - identity_provider = identity_client.identity_providers.update( + identity_provider = federation_client.identity_providers.update( parsed_args.identity_provider, enabled=enabled) info = {} info.update(identity_provider._info) @@ -155,7 +156,7 @@ class SetIdentityProvider(command.Command): class ShowIdentityProvider(show.ShowOne): - """Show identity provider""" + """Show identity provider details""" log = logging.getLogger(__name__ + '.ShowIdentityProvider') @@ -163,8 +164,8 @@ class ShowIdentityProvider(show.ShowOne): parser = super(ShowIdentityProvider, self).get_parser(prog_name) parser.add_argument( 'identity_provider', - metavar='<identity_provider>', - help='ID of the identity provider to be displayed', + metavar='<identity-provider-id>', + help='Identity provider ID to show', ) return parser @@ -172,7 +173,7 @@ class ShowIdentityProvider(show.ShowOne): self.log.debug('take_action(%s)', parsed_args) identity_client = self.app.client_manager.identity identity_provider = utils.find_resource( - identity_client.identity_providers, + identity_client.federation.identity_providers, parsed_args.identity_provider) info = {} diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 38c34973..6ba54368 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -323,6 +323,35 @@ class SetUser(command.Command): return +class SetPasswordUser(command.Command): + """Change current user password""" + + log = logging.getLogger(__name__ + '.SetPasswordUser') + + def get_parser(self, prog_name): + parser = super(SetPasswordUser, self).get_parser(prog_name) + parser.add_argument( + '--password', + metavar='<new-password>', + help='New user password' + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + identity_client = self.app.client_manager.identity + + current_password = utils.get_password( + self.app.stdin, prompt="Current Password:", confirm=False) + + password = parsed_args.password + if password is None: + password = utils.get_password( + self.app.stdin, prompt="New Password:") + + identity_client.users.update_password(current_password, password) + + class ShowUser(show.ShowOne): """Show user details""" diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 92d09953..cd746cf5 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -291,6 +291,12 @@ class ListImage(lister.Lister): metavar="<size>", help="Number of images to request in each paginated request", ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) return parser def take_action(self, parsed_args): @@ -303,7 +309,11 @@ class ListImage(lister.Lister): kwargs["page_size"] = parsed_args.page_size data = image_client.images.list(**kwargs) - columns = ["ID", "Name"] + if parsed_args.long: + columns = ('ID', 'Name', 'Disk Format', 'Container Format', + 'Size', 'Status') + else: + columns = ("ID", "Name") return (columns, (utils.get_item_properties(s, columns) for s in data)) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 08897b2b..ec023b64 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -48,7 +48,7 @@ class DeleteImage(command.Command): image_client.images, parsed_args.image, ) - image_client.images.delete(image) + image_client.images.delete(image.id) class ListImage(lister.Lister): @@ -63,6 +63,12 @@ class ListImage(lister.Lister): metavar="<size>", help="Number of images to request in each paginated request", ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) return parser def take_action(self, parsed_args): @@ -75,7 +81,11 @@ class ListImage(lister.Lister): kwargs["page_size"] = parsed_args.page_size data = image_client.images.list(**kwargs) - columns = ["ID", "Name"] + if parsed_args.long: + columns = ('ID', 'Name', 'Disk Format', 'Container Format', + 'Size', 'Status') + else: + columns = ("ID", "Name") return (columns, (utils.get_item_properties(s, columns) for s in data)) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index c0c25e71..f34666ba 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -43,13 +43,16 @@ class CreateNetwork(show.ShowOne): help='Name of network to create') admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( - '--admin-state-up', - dest='admin_state', action='store_true', - default=True, help='Set Admin State Up') + '--enable', + dest='admin_state', + default=True, + action='store_true', + help='Set administrative state up') admin_group.add_argument( - '--admin-state-down', - dest='admin_state', action='store_false', - help='Set Admin State Down') + '--disable', + dest='admin_state', + action='store_false', + help='Set administrative state down') share_group = parser.add_mutually_exclusive_group() share_group.add_argument( '--share', @@ -83,6 +86,7 @@ class CreateNetwork(show.ShowOne): class DeleteNetwork(command.Command): + """Delete a network""" log = logging.getLogger(__name__ + '.DeleteNetwork') @@ -157,6 +161,7 @@ class ListNetwork(lister.Lister): class SetNetwork(command.Command): + """Set network properties""" log = logging.getLogger(__name__ + '.SetNetwork') @@ -169,14 +174,16 @@ class SetNetwork(command.Command): ) admin_group = parser.add_mutually_exclusive_group() admin_group.add_argument( - '--admin-state-up', - dest='admin_state', action='store_true', + '--enable', + dest='admin_state', default=None, - help='Set Admin State Up') + action='store_true', + help='Set administrative state up') admin_group.add_argument( - '--admin-state-down', - dest='admin_state', action='store_false', - help='Set Admin State Down') + '--disable', + dest='admin_state', + action='store_false', + help='Set administrative state down') parser.add_argument( '--name', metavar='<network_name>', @@ -206,13 +213,15 @@ class SetNetwork(command.Command): if parsed_args.shared is not None: body['shared'] = parsed_args.shared if body == {}: - raise exceptions.CommandError("Nothing specified to be set") + msg = "Nothing specified to be set" + raise exceptions.CommandError(msg) update_method = getattr(client, "update_network") update_method(_id, {'network': body}) return class ShowNetwork(show.ShowOne): + """Show network details""" log = logging.getLogger(__name__ + '.ShowNetwork') diff --git a/openstackclient/object/client.py b/openstackclient/object/client.py index 4fe59794..b81ffaaf 100644 --- a/openstackclient/object/client.py +++ b/openstackclient/object/client.py @@ -23,7 +23,7 @@ LOG = logging.getLogger(__name__) DEFAULT_OBJECT_API_VERSION = '1' API_VERSION_OPTION = 'os_object_api_version' -API_NAME = 'object-store' +API_NAME = 'object_store' API_VERSIONS = { '1': 'openstackclient.object.client.ObjectClientv1', } @@ -31,16 +31,17 @@ API_VERSIONS = { def make_client(instance): """Returns an object service client.""" + object_client = utils.get_client_class( API_NAME, instance._api_version[API_NAME], API_VERSIONS) - LOG.debug('Instantiating object client: %s' % object_client) + LOG.debug('Instantiating object client: %s', object_client) if instance._url: endpoint = instance._url else: - endpoint = instance.get_endpoint_for_service_type(API_NAME) + endpoint = instance.get_endpoint_for_service_type("object-store") client = object_client( endpoint=endpoint, token=instance._token, diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index 1e252aaf..9d55381c 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -19,6 +19,7 @@ import logging import six +from cliff import command from cliff import lister from cliff import show @@ -26,6 +27,67 @@ from openstackclient.common import utils from openstackclient.object.v1.lib import container as lib_container +class CreateContainer(lister.Lister): + """Create a container""" + + log = logging.getLogger(__name__ + '.CreateContainer') + + def get_parser(self, prog_name): + parser = super(CreateContainer, self).get_parser(prog_name) + parser.add_argument( + 'containers', + metavar='<container>', + nargs="+", + help='Container name(s) to create', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + + results = [] + for container in parsed_args.containers: + data = lib_container.create_container( + self.app.client_manager.session, + self.app.client_manager.object_store.endpoint, + container, + ) + results.append(data) + + columns = ("account", "container", "x-trans-id") + return (columns, + (utils.get_dict_properties( + s, columns, + formatters={}, + ) for s in results)) + + +class DeleteContainer(command.Command): + """Delete a container""" + + log = logging.getLogger(__name__ + '.DeleteContainer') + + def get_parser(self, prog_name): + parser = super(DeleteContainer, self).get_parser(prog_name) + parser.add_argument( + 'containers', + metavar='<container>', + nargs="+", + help='Container name(s) to delete', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + + for container in parsed_args.containers: + lib_container.delete_container( + self.app.client_manager.session, + self.app.client_manager.object_store.endpoint, + container, + ) + + class ListContainer(lister.Lister): """List containers""" @@ -89,7 +151,7 @@ class ListContainer(lister.Lister): kwargs['full_listing'] = True data = lib_container.list_containers( - self.app.restapi, + self.app.client_manager.session, self.app.client_manager.object_store.endpoint, **kwargs ) @@ -101,6 +163,30 @@ class ListContainer(lister.Lister): ) for s in data)) +class SaveContainer(command.Command): + """Save the contents of a container locally""" + + log = logging.getLogger(__name__ + ".SaveContainer") + + def get_parser(self, prog_name): + parser = super(SaveContainer, self).get_parser(prog_name) + parser.add_argument( + 'container', + metavar='<container>', + help='Container name to save', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + lib_container.save_container( + self.app.client_manager.session, + self.app.client_manager.object_store.endpoint, + parsed_args.container + ) + + class ShowContainer(show.ShowOne): """Show container information""" @@ -119,7 +205,7 @@ class ShowContainer(show.ShowOne): self.log.debug('take_action(%s)', parsed_args) data = lib_container.show_container( - self.app.restapi, + self.app.client_manager.session, self.app.client_manager.object_store.endpoint, parsed_args.container, ) diff --git a/openstackclient/object/v1/lib/container.py b/openstackclient/object/v1/lib/container.py index 0bae2349..4293ff4a 100644 --- a/openstackclient/object/v1/lib/container.py +++ b/openstackclient/object/v1/lib/container.py @@ -17,13 +17,54 @@ """Object v1 API library""" try: - from urllib.parse import urlparse + from urllib.parse import urlparse # noqa except ImportError: - from urlparse import urlparse + from urlparse import urlparse # noqa + +from openstackclient.object.v1.lib import object as object_lib + + +def create_container( + session, + url, + container, +): + """Create a container + + :param session: an authenticated keystoneclient.session.Session object + :param url: endpoint + :param container: name of container to create + :returns: dict of returned headers + """ + + response = session.put("%s/%s" % (url, container)) + url_parts = urlparse(url) + data = { + 'account': url_parts.path.split('/')[-1], + 'container': container, + 'x-trans-id': response.headers.get('x-trans-id', None), + } + + return data + + +def delete_container( + session, + url, + container, +): + """Delete a container + + :param session: an authenticated keystoneclient.session.Session object + :param url: endpoint + :param container: name of container to delete + """ + + session.delete("%s/%s" % (url, container)) def list_containers( - api, + session, url, marker=None, limit=None, @@ -33,7 +74,7 @@ def list_containers( ): """Get containers in an account - :param api: a restapi object + :param session: an authenticated keystoneclient.session.Session object :param url: endpoint :param marker: marker query :param limit: limit query @@ -46,7 +87,7 @@ def list_containers( if full_listing: data = listing = list_containers( - api, + session, url, marker, limit, @@ -56,7 +97,7 @@ def list_containers( while listing: marker = listing[-1]['name'] listing = list_containers( - api, + session, url, marker, limit, @@ -78,34 +119,52 @@ def list_containers( params['end_marker'] = end_marker if prefix: params['prefix'] = prefix - return api.list(url, params=params) + return session.get(url, params=params).json() + + +def save_container( + session, + url, + container +): + """Save all the content from a container + + :param session: an authenticated keystoneclient.session.Session object + :param url: endpoint + :param container: name of container to save + """ + + objects = object_lib.list_objects(session, url, container) + for object in objects: + object_lib.save_object(session, url, container, object['name']) def show_container( - api, + session, url, container, ): """Get container details - :param api: a restapi object + :param session: an authenticated keystoneclient.session.Session object :param url: endpoint :param container: name of container to show :returns: dict of returned headers """ - response = api.head("%s/%s" % (url, container)) - url_parts = urlparse(url) + response = session.head("%s/%s" % (url, container)) data = { - 'account': url_parts.path.split('/')[-1], + 'account': response.headers.get('x-container-meta-owner', None), 'container': container, + 'object_count': response.headers.get( + 'x-container-object-count', + None, + ), + 'bytes_used': response.headers.get('x-container-bytes-used', None), + 'read_acl': response.headers.get('x-container-read', None), + 'write_acl': response.headers.get('x-container-write', None), + 'sync_to': response.headers.get('x-container-sync-to', None), + 'sync_key': response.headers.get('x-container-sync-key', None), } - data['object_count'] = response.headers.get( - 'x-container-object-count', None) - data['bytes_used'] = response.headers.get('x-container-bytes-used', None) - data['read_acl'] = response.headers.get('x-container-read', None) - data['write_acl'] = response.headers.get('x-container-write', None) - data['sync_to'] = response.headers.get('x-container-sync-to', None) - data['sync_key'] = response.headers.get('x-container-sync-key', None) return data diff --git a/openstackclient/object/v1/lib/object.py b/openstackclient/object/v1/lib/object.py index 646737bd..7a23fc76 100644 --- a/openstackclient/object/v1/lib/object.py +++ b/openstackclient/object/v1/lib/object.py @@ -16,16 +16,64 @@ """Object v1 API library""" +import os + import six try: - from urllib.parse import urlparse + from urllib.parse import urlparse # noqa except ImportError: - from urlparse import urlparse + from urlparse import urlparse # noqa + + +def create_object( + session, + url, + container, + object, +): + """Create an object, upload it to a container + + :param session: an authenticated keystoneclient.session.Session object + :param url: endpoint + :param container: name of container to store object + :param object: local path to object + :returns: dict of returned headers + """ + + full_url = "%s/%s/%s" % (url, container, object) + response = session.put(full_url, data=open(object)) + url_parts = urlparse(url) + data = { + 'account': url_parts.path.split('/')[-1], + 'container': container, + 'object': object, + 'x-trans-id': response.headers.get('X-Trans-Id', None), + 'etag': response.headers.get('Etag', None), + } + + return data + + +def delete_object( + session, + url, + container, + object, +): + """Delete an object stored in a container + + :param session: an authenticated keystoneclient.session.Session object + :param url: endpoint + :param container: name of container that stores object + :param container: name of object to delete + """ + + session.delete("%s/%s/%s" % (url, container, object)) def list_objects( - api, + session, url, container, marker=None, @@ -38,7 +86,7 @@ def list_objects( ): """Get objects in a container - :param api: a restapi object + :param session: an authenticated keystoneclient.session.Session object :param url: endpoint :param container: container name to get a listing for :param marker: marker query @@ -55,7 +103,7 @@ def list_objects( if full_listing: data = listing = list_objects( - api, + session, url, container, marker, @@ -71,7 +119,7 @@ def list_objects( else: marker = listing[-1]['name'] listing = list_objects( - api, + session, url, container, marker, @@ -85,7 +133,6 @@ def list_objects( data.extend(listing) return data - object_url = url params = { 'format': 'json', } @@ -101,32 +148,59 @@ def list_objects( params['prefix'] = prefix if path: params['path'] = path - url = "%s/%s" % (object_url, container) - return api.list(url, params=params) + requrl = "%s/%s" % (url, container) + return session.get(requrl, params=params).json() + + +def save_object( + session, + url, + container, + obj, + file=None +): + """Save an object stored in a container + + :param session: an authenticated keystoneclient.session.Session object + :param url: endpoint + :param container: name of container that stores object + :param object: name of object to save + :param file: local name of object + """ + + if not file: + file = obj + + response = session.get("%s/%s/%s" % (url, container, obj), stream=True) + if response.status_code == 200: + if not os.path.exists(os.path.dirname(file)): + os.makedirs(os.path.dirname(file)) + with open(file, 'wb') as f: + for chunk in response.iter_content(): + f.write(chunk) def show_object( - api, + session, url, container, obj, ): """Get object details - :param api: a restapi object + :param session: an authenticated keystoneclient.session.Session object :param url: endpoint :param container: container name to get a listing for :returns: dict of object properties """ - response = api.head("%s/%s/%s" % (url, container, obj)) - url_parts = urlparse(url) + response = session.head("%s/%s/%s" % (url, container, obj)) data = { - 'account': url_parts.path.split('/')[-1], + 'account': response.headers.get('x-container-meta-owner', None), 'container': container, 'object': obj, + 'content-type': response.headers.get('content-type', None), } - data['content-type'] = response.headers.get('content-type', None) if 'content-length' in response.headers: data['content-length'] = response.headers.get('content-length', None) if 'last-modified' in response.headers: @@ -138,10 +212,10 @@ def show_object( 'x-object-manifest', None) for key, value in six.iteritems(response.headers): if key.startswith('x-object-meta-'): - data[key[len('x-object-meta-'):].title()] = value + data[key[len('x-object-meta-'):].lower()] = value elif key not in ( 'content-type', 'content-length', 'last-modified', - 'etag', 'date', 'x-object-manifest'): - data[key.title()] = value + 'etag', 'date', 'x-object-manifest', 'x-container-meta-owner'): + data[key.lower()] = value return data diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index ee30c842..f0ea7633 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -19,6 +19,7 @@ import logging import six +from cliff import command from cliff import lister from cliff import show @@ -26,6 +27,80 @@ from openstackclient.common import utils from openstackclient.object.v1.lib import object as lib_object +class CreateObject(lister.Lister): + """Upload an object to a container""" + + log = logging.getLogger(__name__ + '.CreateObject') + + def get_parser(self, prog_name): + parser = super(CreateObject, self).get_parser(prog_name) + parser.add_argument( + 'container', + metavar='<container>', + help='Container to store new object', + ) + parser.add_argument( + 'objects', + metavar='<object-name>', + nargs="+", + help='Local path of object(s) to upload', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + + results = [] + for obj in parsed_args.objects: + data = lib_object.create_object( + self.app.client_manager.session, + self.app.client_manager.object_store.endpoint, + parsed_args.container, + obj, + ) + results.append(data) + + columns = ("object", "container", "etag") + return (columns, + (utils.get_dict_properties( + s, columns, + formatters={}, + ) for s in results)) + + +class DeleteObject(command.Command): + """Delete an object within a container""" + + log = logging.getLogger(__name__ + '.DeleteObject') + + def get_parser(self, prog_name): + parser = super(DeleteObject, self).get_parser(prog_name) + parser.add_argument( + 'container', + metavar='<container>', + help='Container that stores the object to delete', + ) + parser.add_argument( + 'objects', + metavar='<object-name>', + nargs="+", + help='Object(s) to delete', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + + for obj in parsed_args.objects: + lib_object.delete_object( + self.app.restapi, + self.app.client_manager.session, + self.app.client_manager.object_store.endpoint, + parsed_args.container, + obj, + ) + + class ListObject(lister.Lister): """List objects""" @@ -107,7 +182,7 @@ class ListObject(lister.Lister): kwargs['full_listing'] = True data = lib_object.list_objects( - self.app.restapi, + self.app.client_manager.session, self.app.client_manager.object_store.endpoint, parsed_args.container, **kwargs @@ -120,6 +195,42 @@ class ListObject(lister.Lister): ) for s in data)) +class SaveObject(command.Command): + """Save an object locally""" + + log = logging.getLogger(__name__ + ".SaveObject") + + def get_parser(self, prog_name): + parser = super(SaveObject, self).get_parser(prog_name) + parser.add_argument( + "--file", + metavar="<filename>", + help="Downloaded object filename [defaults to object name]", + ) + parser.add_argument( + 'container', + metavar='<container>', + help='Container name that has the object', + ) + parser.add_argument( + "object", + metavar="<object>", + help="Name of the object to save", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + lib_object.save_object( + self.app.client_manager.session, + self.app.client_manager.object_store.endpoint, + parsed_args.container, + parsed_args.object, + parsed_args.file, + ) + + class ShowObject(show.ShowOne): """Show object information""" @@ -143,7 +254,7 @@ class ShowObject(show.ShowOne): self.log.debug('take_action(%s)', parsed_args) data = lib_object.show_object( - self.app.restapi, + self.app.client_manager.session, self.app.client_manager.object_store.endpoint, parsed_args.container, parsed_args.object, diff --git a/openstackclient/openstack/common/gettextutils.py b/openstackclient/openstack/common/gettextutils.py index 6f573a7f..0c82634b 100644 --- a/openstackclient/openstack/common/gettextutils.py +++ b/openstackclient/openstack/common/gettextutils.py @@ -23,7 +23,6 @@ Usual usage in an openstack.common module: """ import copy -import functools import gettext import locale from logging import handlers @@ -42,7 +41,7 @@ class TranslatorFactory(object): """Create translator functions """ - def __init__(self, domain, lazy=False, localedir=None): + def __init__(self, domain, localedir=None): """Establish a set of translation functions for the domain. :param domain: Name of translation domain, @@ -55,7 +54,6 @@ class TranslatorFactory(object): :type localedir: str """ self.domain = domain - self.lazy = lazy if localedir is None: localedir = os.environ.get(domain.upper() + '_LOCALEDIR') self.localedir = localedir @@ -75,16 +73,19 @@ class TranslatorFactory(object): """ 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 + t = gettext.translation(domain, + localedir=self.localedir, + fallback=True) + # Use the appropriate method of the translation object based + # on the python version. + m = t.gettext if six.PY3 else t.ugettext + + def f(msg): + """oslo.i18n.gettextutils translation function.""" + if USE_LAZY: + return Message(msg, domain=domain) + return m(msg) + return f @property def primary(self): @@ -147,19 +148,11 @@ def enable_lazy(): your project is importing _ directly instead of using the gettextutils.install() way of importing the _ function. """ - # 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('openstackclient', lazy=True) - _ = tf.primary - _LI = tf.log_info - _LW = tf.log_warning - _LE = tf.log_error - _LC = tf.log_critical + global USE_LAZY USE_LAZY = True -def install(domain, lazy=False): +def install(domain): """Install a _() function using the given translation domain. Given a translation domain, install a _() function using gettext's @@ -170,26 +163,14 @@ def install(domain, lazy=False): a translation-domain-specific environment variable (e.g. NOVA_LOCALEDIR). + Note that to enable lazy translation, enable_lazy must be + called. + :param domain: the translation domain - :param lazy: indicates whether or not to install the lazy _() function. - The lazy _() introduces a way to do deferred translation - of messages by installing a _ that builds Message objects, - instead of strings, which can then be lazily translated into - any available locale. """ - if lazy: - from six import moves - tf = TranslatorFactory(domain, lazy=True) - moves.builtins.__dict__['_'] = tf.primary - else: - localedir = '%s_LOCALEDIR' % domain.upper() - if six.PY3: - gettext.install(domain, - localedir=os.environ.get(localedir)) - else: - gettext.install(domain, - localedir=os.environ.get(localedir), - unicode=True) + from six import moves + tf = TranslatorFactory(domain) + moves.builtins.__dict__['_'] = tf.primary class Message(six.text_type): @@ -373,8 +354,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/openstackclient/openstack/common/importutils.py b/openstackclient/openstack/common/importutils.py new file mode 100644 index 00000000..69e8d8f1 --- /dev/null +++ b/openstackclient/openstack/common/importutils.py @@ -0,0 +1,73 @@ +# Copyright 2011 OpenStack Foundation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +""" +Import related utilities and helper functions. +""" + +import sys +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: + return getattr(sys.modules[mod_str], class_str) + except AttributeError: + raise ImportError('Class %s cannot be found (%s)' % + (class_str, + traceback.format_exception(*sys.exc_info()))) + + +def import_object(import_str, *args, **kwargs): + """Import a class and return an instance of it.""" + return import_class(import_str)(*args, **kwargs) + + +def import_object_ns(name_space, import_str, *args, **kwargs): + """Tries to import object from default namespace. + + Imports a class and return an instance of it, first by trying + to find the class in a default namespace, then failing back to + a full path if not found in the default namespace. + """ + import_value = "%s.%s" % (name_space, import_str) + try: + return import_class(import_value)(*args, **kwargs) + except ImportError: + return import_class(import_str)(*args, **kwargs) + + +def import_module(import_str): + """Import a module.""" + __import__(import_str) + return sys.modules[import_str] + + +def import_versioned_module(version, submodule=None): + module = 'openstackclient.v%s' % version + if submodule: + module = '.'.join((module, submodule)) + return import_module(module) + + +def try_import(import_str, default=None): + """Try to import a module and if it fails return default.""" + try: + return import_module(import_str) + except ImportError: + return default diff --git a/openstackclient/openstack/common/strutils.py b/openstackclient/openstack/common/strutils.py index 9d70264f..ad3cb44c 100644 --- a/openstackclient/openstack/common/strutils.py +++ b/openstackclient/openstack/common/strutils.py @@ -50,6 +50,39 @@ SLUGIFY_STRIP_RE = re.compile(r"[^\w\s-]") SLUGIFY_HYPHENATE_RE = re.compile(r"[-\s]+") +# NOTE(flaper87): The following globals are used by `mask_password` +_SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password'] + +# NOTE(ldbragst): Let's build a list of regex objects using the list of +# _SANITIZE_KEYS we already have. This way, we only have to add the new key +# to the list of _SANITIZE_KEYS and we can generate regular expressions +# for XML and JSON automatically. +_SANITIZE_PATTERNS_2 = [] +_SANITIZE_PATTERNS_1 = [] + +# NOTE(amrith): Some regular expressions have only one parameter, some +# have two parameters. Use different lists of patterns here. +_FORMAT_PATTERNS_1 = [r'(%(key)s\s*[=]\s*)[^\s^\'^\"]+'] +_FORMAT_PATTERNS_2 = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])', + r'(%(key)s\s+[\"\']).*?([\"\'])', + r'([-]{2}%(key)s\s+)[^\'^\"^=^\s]+([\s]*)', + r'(<%(key)s>).*?(</%(key)s>)', + r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])', + r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])', + r'([\'"].*?%(key)s[\'"]\s*,\s*\'--?[A-z]+\'\s*,\s*u?' + '[\'"]).*?([\'"])', + r'(%(key)s\s*--?[A-z]+\s*)\S+(\s*)'] + +for key in _SANITIZE_KEYS: + for pattern in _FORMAT_PATTERNS_2: + reg_ex = re.compile(pattern % {'key': key}, re.DOTALL) + _SANITIZE_PATTERNS_2.append(reg_ex) + + for pattern in _FORMAT_PATTERNS_1: + reg_ex = re.compile(pattern % {'key': key}, re.DOTALL) + _SANITIZE_PATTERNS_1.append(reg_ex) + + def int_from_bool_as_string(subject): """Interpret a string as a boolean and return either 1 or 0. @@ -237,3 +270,42 @@ def to_slug(value, incoming=None, errors="strict"): "ascii", "ignore").decode("ascii") value = SLUGIFY_STRIP_RE.sub("", value).strip().lower() return SLUGIFY_HYPHENATE_RE.sub("-", value) + + +def mask_password(message, secret="***"): + """Replace password with 'secret' in message. + + :param message: The string which includes security information. + :param secret: value with which to replace passwords. + :returns: The unicode value of message with the password fields masked. + + For example: + + >>> mask_password("'adminPass' : 'aaaaa'") + "'adminPass' : '***'" + >>> mask_password("'admin_pass' : 'aaaaa'") + "'admin_pass' : '***'" + >>> mask_password('"password" : "aaaaa"') + '"password" : "***"' + >>> mask_password("'original_password' : 'aaaaa'") + "'original_password' : '***'" + >>> mask_password("u'original_password' : u'aaaaa'") + "u'original_password' : u'***'" + """ + message = six.text_type(message) + + # NOTE(ldbragst): Check to see if anything in message contains any key + # specified in _SANITIZE_KEYS, if not then just return the message since + # we don't have to mask any passwords. + if not any(key in message for key in _SANITIZE_KEYS): + return message + + substitute = r'\g<1>' + secret + r'\g<2>' + for pattern in _SANITIZE_PATTERNS_2: + message = re.sub(pattern, substitute, message) + + substitute = r'\g<1>' + secret + for pattern in _SANITIZE_PATTERNS_1: + message = re.sub(pattern, substitute, message) + + return message diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 1d0c5771..24804343 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -31,9 +31,8 @@ import openstackclient from openstackclient.common import clientmanager from openstackclient.common import commandmanager from openstackclient.common import exceptions as exc -from openstackclient.common import restapi +from openstackclient.common import timing from openstackclient.common import utils -from openstackclient.identity import client as identity_client KEYRING_SERVICE = 'openstack' @@ -60,6 +59,7 @@ class OpenStackShell(app.App): CONSOLE_MESSAGE_FORMAT = '%(levelname)s: %(name)s %(message)s' log = logging.getLogger(__name__) + timing_data = [] def __init__(self): # Patch command.Command to add a default auth_required = True @@ -74,6 +74,8 @@ class OpenStackShell(app.App): version=openstackclient.__version__, command_manager=commandmanager.CommandManager('openstack.cli')) + self.api_version = {} + # Until we have command line arguments parsed, dump any stack traces self.dump_stack_trace = True @@ -84,10 +86,14 @@ class OpenStackShell(app.App): # Assume TLS host certificate verification is enabled self.verify = True - # Get list of extension modules + # Get list of base modules self.ext_modules = clientmanager.get_extension_modules( - 'openstack.cli.extension', + 'openstack.cli.base', ) + # Append list of extension modules + self.ext_modules.extend(clientmanager.get_extension_modules( + 'openstack.cli.extension', + )) # Loop through extensions to get parser additions for mod in self.ext_modules: @@ -303,23 +309,12 @@ class OpenStackShell(app.App): metavar='<url>', default=env('OS_URL'), help='Defaults to env[OS_URL]') - parser.add_argument( - '--os-identity-api-version', - metavar='<identity-api-version>', - default=env( - 'OS_IDENTITY_API_VERSION', - default=identity_client.DEFAULT_IDENTITY_API_VERSION), - help='Identity API version, default=' + - identity_client.DEFAULT_IDENTITY_API_VERSION + - ' (Env: OS_IDENTITY_API_VERSION)') - parser.add_argument( - '--os-trust-id', - metavar='<trust-id>', - default=utils.env('OS_TRUST_ID'), - help='Trust ID to use when authenticating. ' - 'This can only be used with Keystone v3 API ' - '(Env: OS_TRUST_ID)') + '--timing', + default=False, + action='store_true', + help="Print API call timing info", + ) return parser @@ -410,6 +405,7 @@ class OpenStackShell(app.App): password=self.options.os_password, region_name=self.options.os_region_name, verify=self.verify, + timing=self.options.timing, api_version=self.api_version, trust_id=self.options.os_trust_id, ) @@ -428,24 +424,19 @@ class OpenStackShell(app.App): # Save default domain self.default_domain = self.options.os_default_domain - # Stash selected API versions for later - self.api_version = { - 'identity': self.options.os_identity_api_version, - } # Loop through extensions to get API versions for mod in self.ext_modules: - ver = getattr(self.options, mod.API_VERSION_OPTION, None) - if ver: - self.api_version[mod.API_NAME] = ver - self.log.debug('%(name)s API version %(version)s', - {'name': mod.API_NAME, 'version': ver}) - - # Add the API version-specific commands - for api in self.api_version.keys(): - version = '.v' + self.api_version[api].replace('.', '_') - cmd_group = 'openstack.' + api.replace('-', '_') + version - self.log.debug('command group %s', cmd_group) - self.command_manager.add_command_group(cmd_group) + version_opt = getattr(self.options, mod.API_VERSION_OPTION, None) + if version_opt: + api = mod.API_NAME + self.api_version[api] = version_opt + version = '.v' + version_opt.replace('.', '_') + cmd_group = 'openstack.' + api.replace('-', '_') + version + self.command_manager.add_command_group(cmd_group) + self.log.debug( + '%(name)s API version %(version)s, cmd group %(group)s', + {'name': api, 'version': version_opt, 'group': cmd_group} + ) # Commands that span multiple APIs self.command_manager.add_command_group( @@ -475,10 +466,6 @@ class OpenStackShell(app.App): self.verify = self.options.os_cacert else: self.verify = not self.options.insecure - self.restapi = restapi.RESTApi( - verify=self.verify, - debug=self.options.debug, - ) def prepare_to_run_command(self, cmd): """Set up auth and API versions""" @@ -489,24 +476,45 @@ class OpenStackShell(app.App): if cmd.best_effort: try: self.authenticate_user() - self.restapi.set_auth(self.client_manager.identity.auth_token) except Exception: pass else: self.authenticate_user() - self.restapi.set_auth(self.client_manager.identity.auth_token) return def clean_up(self, cmd, result, err): self.log.debug('clean_up %s', cmd.__class__.__name__) + if err: self.log.debug('got an error: %s', err) + # Process collected timing data + if self.options.timing: + # Loop through extensions + for mod in self.ext_modules: + client = getattr(self.client_manager, mod.API_NAME) + if hasattr(client, 'get_timings'): + self.timing_data.extend(client.get_timings()) + + # Use the Timing pseudo-command to generate the output + tcmd = timing.Timing(self, self.options) + tparser = tcmd.get_parser('Timing') + + # If anything other than prettytable is specified, force csv + format = 'table' + # Check the formatter used in the actual command + if hasattr(cmd, 'formatter') \ + and cmd.formatter != cmd._formatter_plugins['table'].obj: + format = 'csv' + + sys.stdout.write('\n') + targs = tparser.parse_args(['-f', format]) + tcmd.run(targs) + def interact(self): # NOTE(dtroyer): Maintain the old behaviour for interactive use as # this path does not call prepare_to_run_command() self.authenticate_user() - self.restapi.set_auth(self.client_manager.identity.auth_token) super(OpenStackShell, self).interact() diff --git a/openstackclient/tests/common/test_clientmanager.py b/openstackclient/tests/common/test_clientmanager.py index 6aee711d..0bb657ad 100644 --- a/openstackclient/tests/common/test_clientmanager.py +++ b/openstackclient/tests/common/test_clientmanager.py @@ -13,12 +13,33 @@ # under the License. # +import mock + +from keystoneclient.auth.identity import v2 as auth_v2 from openstackclient.common import clientmanager from openstackclient.tests import utils +AUTH_REF = {'a': 1} AUTH_TOKEN = "foobar" AUTH_URL = "http://0.0.0.0" +USERNAME = "itchy" +PASSWORD = "scratchy" +SERVICE_CATALOG = {'sc': '123'} + +API_VERSION = { + 'identity': '2.0', +} + + +def FakeMakeClient(instance): + return FakeClient() + + +class FakeClient(object): + auth_ref = AUTH_REF + auth_token = AUTH_TOKEN + service_catalog = SERVICE_CATALOG class Container(object): @@ -28,31 +49,86 @@ class Container(object): pass +class TestClientCache(utils.TestCase): + + def test_singleton(self): + # NOTE(dtroyer): Verify that the ClientCache descriptor only invokes + # the factory one time and always returns the same value after that. + c = Container() + self.assertEqual(c.attr, c.attr) + + +@mock.patch('keystoneclient.session.Session') class TestClientManager(utils.TestCase): def setUp(self): super(TestClientManager, self).setUp() - api_version = {"identity": "2.0"} + clientmanager.ClientManager.identity = \ + clientmanager.ClientCache(FakeMakeClient) - self.client_manager = clientmanager.ClientManager( + def test_client_manager_token(self, mock): + + client_manager = clientmanager.ClientManager( token=AUTH_TOKEN, url=AUTH_URL, - auth_url=AUTH_URL, - api_version=api_version, + verify=True, + api_version=API_VERSION, ) - def test_singleton(self): - # NOTE(dtroyer): Verify that the ClientCache descriptor only invokes - # the factory one time and always returns the same value after that. - c = Container() - self.assertEqual(c.attr, c.attr) - - def test_make_client_identity_default(self): self.assertEqual( - self.client_manager.identity.auth_token, AUTH_TOKEN, + client_manager._token, + ) + self.assertEqual( + AUTH_URL, + client_manager._url, + ) + self.assertIsInstance( + client_manager.auth, + auth_v2.Token, ) + self.assertFalse(client_manager._insecure) + self.assertTrue(client_manager._verify) + + def test_client_manager_password(self, mock): + + client_manager = clientmanager.ClientManager( + auth_url=AUTH_URL, + username=USERNAME, + password=PASSWORD, + verify=False, + api_version=API_VERSION, + ) + self.assertEqual( - self.client_manager.identity.management_url, AUTH_URL, + client_manager._auth_url, ) + self.assertEqual( + USERNAME, + client_manager._username, + ) + self.assertEqual( + PASSWORD, + client_manager._password, + ) + self.assertIsInstance( + client_manager.auth, + auth_v2.Password, + ) + self.assertTrue(client_manager._insecure) + self.assertFalse(client_manager._verify) + + def test_client_manager_password_verify_ca(self, mock): + + client_manager = clientmanager.ClientManager( + auth_url=AUTH_URL, + username=USERNAME, + password=PASSWORD, + verify='cafile', + api_version=API_VERSION, + ) + + self.assertFalse(client_manager._insecure) + self.assertTrue(client_manager._verify) + self.assertEqual('cafile', client_manager._cacert) diff --git a/openstackclient/tests/common/test_restapi.py b/openstackclient/tests/common/test_restapi.py deleted file mode 100644 index d4fe2d3d..00000000 --- a/openstackclient/tests/common/test_restapi.py +++ /dev/null @@ -1,341 +0,0 @@ -# Copyright 2013 Nebula Inc. -# -# 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. -# - -"""Test rest module""" - -import json -import mock - -import requests -import six - -from openstackclient.common import restapi -from openstackclient.tests import utils - -fake_user_agent = 'test_rapi' - -fake_auth = '11223344556677889900' -fake_url = 'http://gopher.com' -fake_key = 'gopher' -fake_keys = 'gophers' -fake_gopher_mac = { - 'id': 'g1', - 'name': 'mac', - 'actor': 'Mel Blanc', -} -fake_gopher_tosh = { - 'id': 'g2', - 'name': 'tosh', - 'actor': 'Stan Freeberg', -} -fake_gopher_single = { - fake_key: fake_gopher_mac, -} -fake_gopher_list = { - fake_keys: - [ - fake_gopher_mac, - fake_gopher_tosh, - ] -} -fake_headers = { - 'User-Agent': fake_user_agent, -} - - -class FakeResponse(requests.Response): - def __init__(self, headers={}, status_code=200, data=None, encoding=None): - super(FakeResponse, self).__init__() - - self.status_code = status_code - - self.headers.update(headers) - self._content = json.dumps(data) - if not isinstance(self._content, six.binary_type): - self._content = self._content.encode() - - -@mock.patch('openstackclient.common.restapi.requests.Session') -class TestRESTApi(utils.TestCase): - - def test_request_get(self, session_mock): - resp = FakeResponse(status_code=200, data=fake_gopher_single) - session_mock.return_value = mock.MagicMock( - request=mock.MagicMock(return_value=resp), - ) - - api = restapi.RESTApi( - user_agent=fake_user_agent, - ) - gopher = api.request('GET', fake_url) - session_mock.return_value.request.assert_called_with( - 'GET', - fake_url, - headers={}, - allow_redirects=True, - ) - self.assertEqual(gopher.status_code, 200) - self.assertEqual(gopher.json(), fake_gopher_single) - - def test_request_get_return_300(self, session_mock): - resp = FakeResponse(status_code=300, data=fake_gopher_single) - session_mock.return_value = mock.MagicMock( - request=mock.MagicMock(return_value=resp), - ) - - api = restapi.RESTApi( - user_agent=fake_user_agent, - ) - gopher = api.request('GET', fake_url) - session_mock.return_value.request.assert_called_with( - 'GET', - fake_url, - headers={}, - allow_redirects=True, - ) - self.assertEqual(gopher.status_code, 300) - self.assertEqual(gopher.json(), fake_gopher_single) - - def test_request_get_fail_404(self, session_mock): - resp = FakeResponse(status_code=404, data=fake_gopher_single) - session_mock.return_value = mock.MagicMock( - request=mock.MagicMock(return_value=resp), - ) - - api = restapi.RESTApi( - user_agent=fake_user_agent, - ) - self.assertRaises(requests.HTTPError, api.request, 'GET', fake_url) - session_mock.return_value.request.assert_called_with( - 'GET', - fake_url, - headers={}, - allow_redirects=True, - ) - - def test_request_get_auth(self, session_mock): - resp = FakeResponse(data=fake_gopher_single) - session_mock.return_value = mock.MagicMock( - request=mock.MagicMock(return_value=resp), - headers=mock.MagicMock(return_value={}), - ) - - api = restapi.RESTApi( - auth_header=fake_auth, - user_agent=fake_user_agent, - ) - gopher = api.request('GET', fake_url) - session_mock.return_value.request.assert_called_with( - 'GET', - fake_url, - headers={ - 'X-Auth-Token': fake_auth, - }, - allow_redirects=True, - ) - self.assertEqual(gopher.json(), fake_gopher_single) - - def test_request_post(self, session_mock): - resp = FakeResponse(data=fake_gopher_single) - session_mock.return_value = mock.MagicMock( - request=mock.MagicMock(return_value=resp), - ) - - api = restapi.RESTApi( - user_agent=fake_user_agent, - ) - data = fake_gopher_tosh - gopher = api.request('POST', fake_url, json=data) - session_mock.return_value.request.assert_called_with( - 'POST', - fake_url, - headers={ - 'Content-Type': 'application/json', - }, - allow_redirects=True, - data=json.dumps(data), - ) - self.assertEqual(gopher.json(), fake_gopher_single) - - # Methods - # TODO(dtroyer): add the other method methods - - def test_delete(self, session_mock): - resp = FakeResponse(status_code=200, data=None) - session_mock.return_value = mock.MagicMock( - request=mock.MagicMock(return_value=resp), - ) - - api = restapi.RESTApi() - gopher = api.delete(fake_url) - session_mock.return_value.request.assert_called_with( - 'DELETE', - fake_url, - headers=mock.ANY, - allow_redirects=True, - ) - self.assertEqual(gopher.status_code, 200) - - # Commands - - def test_create(self, session_mock): - resp = FakeResponse(data=fake_gopher_single) - session_mock.return_value = mock.MagicMock( - request=mock.MagicMock(return_value=resp), - ) - - api = restapi.RESTApi() - data = fake_gopher_mac - - # Test no key - gopher = api.create(fake_url, data=data) - session_mock.return_value.request.assert_called_with( - 'POST', - fake_url, - headers=mock.ANY, - allow_redirects=True, - data=json.dumps(data), - ) - self.assertEqual(gopher, fake_gopher_single) - - # Test with key - gopher = api.create(fake_url, data=data, response_key=fake_key) - session_mock.return_value.request.assert_called_with( - 'POST', - fake_url, - headers=mock.ANY, - allow_redirects=True, - data=json.dumps(data), - ) - self.assertEqual(gopher, fake_gopher_mac) - - def test_list(self, session_mock): - resp = FakeResponse(data=fake_gopher_list) - session_mock.return_value = mock.MagicMock( - request=mock.MagicMock(return_value=resp), - ) - - # test base - api = restapi.RESTApi() - gopher = api.list(fake_url, response_key=fake_keys) - session_mock.return_value.request.assert_called_with( - 'GET', - fake_url, - headers=mock.ANY, - allow_redirects=True, - ) - self.assertEqual(gopher, [fake_gopher_mac, fake_gopher_tosh]) - - # test body - api = restapi.RESTApi() - data = {'qwerty': 1} - gopher = api.list(fake_url, response_key=fake_keys, data=data) - session_mock.return_value.request.assert_called_with( - 'POST', - fake_url, - headers=mock.ANY, - allow_redirects=True, - data=json.dumps(data), - ) - self.assertEqual(gopher, [fake_gopher_mac, fake_gopher_tosh]) - - # test query params - api = restapi.RESTApi() - params = {'qaz': '123'} - gophers = api.list(fake_url, response_key=fake_keys, params=params) - session_mock.return_value.request.assert_called_with( - 'GET', - fake_url, - headers=mock.ANY, - allow_redirects=True, - params=params, - ) - self.assertEqual(gophers, [fake_gopher_mac, fake_gopher_tosh]) - - def test_set(self, session_mock): - new_gopher = fake_gopher_single - new_gopher[fake_key]['name'] = 'Chip' - resp = FakeResponse(data=fake_gopher_single) - session_mock.return_value = mock.MagicMock( - request=mock.MagicMock(return_value=resp), - ) - - api = restapi.RESTApi() - data = fake_gopher_mac - data['name'] = 'Chip' - - # Test no data, no key - gopher = api.set(fake_url) - session_mock.return_value.request.assert_called_with( - 'PUT', - fake_url, - headers=mock.ANY, - allow_redirects=True, - json=None, - ) - self.assertEqual(gopher, None) - - # Test data, no key - gopher = api.set(fake_url, data=data) - session_mock.return_value.request.assert_called_with( - 'PUT', - fake_url, - headers=mock.ANY, - allow_redirects=True, - data=json.dumps(data), - ) - self.assertEqual(gopher, fake_gopher_single) - - # NOTE:(dtroyer): Key and no data is not tested as without data - # the response_key is moot - - # Test data and key - gopher = api.set(fake_url, data=data, response_key=fake_key) - session_mock.return_value.request.assert_called_with( - 'PUT', - fake_url, - headers=mock.ANY, - allow_redirects=True, - data=json.dumps(data), - ) - self.assertEqual(gopher, fake_gopher_mac) - - def test_show(self, session_mock): - resp = FakeResponse(data=fake_gopher_single) - session_mock.return_value = mock.MagicMock( - request=mock.MagicMock(return_value=resp), - ) - - api = restapi.RESTApi() - - # Test no key - gopher = api.show(fake_url) - session_mock.return_value.request.assert_called_with( - 'GET', - fake_url, - headers=mock.ANY, - allow_redirects=True, - ) - self.assertEqual(gopher, fake_gopher_single) - - # Test with key - gopher = api.show(fake_url, response_key=fake_key) - session_mock.return_value.request.assert_called_with( - 'GET', - fake_url, - headers=mock.ANY, - allow_redirects=True, - ) - self.assertEqual(gopher, fake_gopher_mac) diff --git a/openstackclient/tests/common/test_timing.py b/openstackclient/tests/common/test_timing.py new file mode 100644 index 00000000..aa910b91 --- /dev/null +++ b/openstackclient/tests/common/test_timing.py @@ -0,0 +1,87 @@ +# 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. +# + +"""Test Timing pseudo-command""" + +from openstackclient.common import timing +from openstackclient.tests import fakes +from openstackclient.tests import utils + + +timing_url = 'GET http://localhost:5000' +timing_start = 1404802774.872809 +timing_end = 1404802775.724802 + + +class FakeGenericClient(object): + + def __init__(self, **kwargs): + self.auth_token = kwargs['token'] + self.management_url = kwargs['endpoint'] + + +class TestTiming(utils.TestCommand): + + def setUp(self): + super(TestTiming, self).setUp() + + self.app.timing_data = [] + + self.app.client_manager.compute = FakeGenericClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + + self.app.client_manager.volume = FakeGenericClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + + # Get the command object to test + self.cmd = timing.Timing(self.app, None) + + def test_timing_list_no_data(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + collist = ('URL', 'Seconds') + self.assertEqual(collist, columns) + datalist = [ + ('Total', 0.0,) + ] + self.assertEqual(datalist, data) + + def test_timing_list(self): + self.app.timing_data = [ + (timing_url, timing_start, timing_end), + ] + + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + collist = ('URL', 'Seconds') + self.assertEqual(collist, columns) + timing_sec = timing_end - timing_start + datalist = [ + (timing_url, timing_sec), + ('Total', timing_sec) + ] + self.assertEqual(datalist, data) diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py index 6d75a9b5..e782d410 100644 --- a/openstackclient/tests/common/test_utils.py +++ b/openstackclient/tests/common/test_utils.py @@ -130,3 +130,15 @@ class TestFindResource(test_utils.TestCase): str(result)) self.manager.get.assert_called_with(self.name) self.manager.find.assert_called_with(name=self.name) + + def test_format_dict(self): + expected = "a='b', c='d', e='f'" + self.assertEqual(expected, + utils.format_dict({'a': 'b', 'c': 'd', 'e': 'f'})) + self.assertEqual(expected, + utils.format_dict({'e': 'f', 'c': 'd', 'a': 'b'})) + + def test_format_list(self): + expected = 'a, b, c' + self.assertEqual(expected, utils.format_list(['a', 'b', 'c'])) + self.assertEqual(expected, utils.format_list(['c', 'b', 'a'])) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index cef5ee90..9a7964db 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -47,6 +47,18 @@ EXTENSION = { 'links': extension_links, } +flavor_id = 'm1.large' +flavor_name = 'Large' +flavor_ram = 8192 +flavor_vcpus = 4 + +FLAVOR = { + 'id': flavor_id, + 'name': flavor_name, + 'ram': flavor_ram, + 'vcpus': flavor_vcpus, +} + class FakeComputev2Client(object): def __init__(self, **kwargs): @@ -56,6 +68,8 @@ class FakeComputev2Client(object): self.servers.resource_class = fakes.FakeResource(None, {}) self.extensions = mock.Mock() self.extensions.resource_class = fakes.FakeResource(None, {}) + self.flavors = mock.Mock() + self.flavors.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index efe4c58b..a98cd156 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -30,6 +30,10 @@ class TestServer(compute_fakes.TestComputev2): self.servers_mock = self.app.client_manager.compute.servers self.servers_mock.reset_mock() + # Get a shortcut to the FlavorManager Mock + self.flavors_mock = self.app.client_manager.compute.flavors + self.flavors_mock.reset_mock() + # Get a shortcut to the ImageManager Mock self.images_mock = self.app.client_manager.image.images self.images_mock.reset_mock() @@ -148,3 +152,134 @@ class TestServerImageCreate(TestServer): image_fakes.image_owner, ) self.assertEqual(data, datalist) + + +class TestServerResize(TestServer): + + def setUp(self): + super(TestServerResize, self).setUp() + + # This is the return value for utils.find_resource() + self.servers_get_return_value = fakes.FakeResource( + None, + copy.deepcopy(compute_fakes.SERVER), + loaded=True, + ) + self.servers_mock.get.return_value = self.servers_get_return_value + + self.servers_mock.resize.return_value = None + self.servers_mock.confirm_resize.return_value = None + self.servers_mock.revert_resize.return_value = None + + # This is the return value for utils.find_resource() + self.flavors_get_return_value = fakes.FakeResource( + None, + copy.deepcopy(compute_fakes.FLAVOR), + loaded=True, + ) + self.flavors_mock.get.return_value = self.flavors_get_return_value + + # Get the command object to test + self.cmd = server.ResizeServer(self.app, None) + + def test_server_resize_no_options(self): + arglist = [ + compute_fakes.server_id, + ] + verifylist = [ + ('verify', False), + ('revert', False), + ('server', compute_fakes.server_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with( + compute_fakes.server_id, + ) + + self.assertNotCalled(self.servers_mock.resize) + self.assertNotCalled(self.servers_mock.confirm_resize) + self.assertNotCalled(self.servers_mock.revert_resize) + + def test_server_resize(self): + arglist = [ + '--flavor', compute_fakes.flavor_id, + compute_fakes.server_id, + ] + verifylist = [ + ('flavor', compute_fakes.flavor_id), + ('verify', False), + ('revert', False), + ('server', compute_fakes.server_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with( + compute_fakes.server_id, + ) + self.flavors_mock.get.assert_called_with( + compute_fakes.flavor_id, + ) + + self.servers_mock.resize.assert_called_with( + self.servers_get_return_value, + self.flavors_get_return_value, + ) + self.assertNotCalled(self.servers_mock.confirm_resize) + self.assertNotCalled(self.servers_mock.revert_resize) + + def test_server_resize_confirm(self): + arglist = [ + '--verify', + compute_fakes.server_id, + ] + verifylist = [ + ('verify', True), + ('revert', False), + ('server', compute_fakes.server_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with( + compute_fakes.server_id, + ) + + self.assertNotCalled(self.servers_mock.resize) + self.servers_mock.confirm_resize.assert_called_with( + self.servers_get_return_value, + ) + self.assertNotCalled(self.servers_mock.revert_resize) + + def test_server_resize_revert(self): + arglist = [ + '--revert', + compute_fakes.server_id, + ] + verifylist = [ + ('verify', False), + ('revert', True), + ('server', compute_fakes.server_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + self.servers_mock.get.assert_called_with( + compute_fakes.server_id, + ) + + self.assertNotCalled(self.servers_mock.resize) + self.assertNotCalled(self.servers_mock.confirm_resize) + self.servers_mock.revert_resize.assert_called_with( + self.servers_get_return_value, + ) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index fb27ef94..263640ee 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -13,9 +13,12 @@ # under the License. # +import json import six import sys +import requests + AUTH_TOKEN = "foobar" AUTH_URL = "http://0.0.0.0" @@ -42,7 +45,6 @@ class FakeApp(object): self.stdin = sys.stdin self.stdout = _stdout or sys.stdout self.stderr = sys.stderr - self.restapi = None class FakeClientManager(object): @@ -53,6 +55,7 @@ class FakeClientManager(object): self.object = None self.volume = None self.network = None + self.session = None self.auth_ref = None @@ -78,3 +81,15 @@ class FakeResource(object): k != 'manager') info = ", ".join("%s=%s" % (k, getattr(self, k)) for k in reprkeys) return "<%s %s>" % (self.__class__.__name__, info) + + +class FakeResponse(requests.Response): + def __init__(self, headers={}, status_code=200, data=None, encoding=None): + super(FakeResponse, self).__init__() + + self.status_code = status_code + + self.headers.update(headers) + self._content = json.dumps(data) + if not isinstance(self._content, six.binary_type): + self._content = self._content.encode() diff --git a/openstackclient/tests/identity/v2_0/fakes.py b/openstackclient/tests/identity/v2_0/fakes.py index a8438e96..b136f841 100644 --- a/openstackclient/tests/identity/v2_0/fakes.py +++ b/openstackclient/tests/identity/v2_0/fakes.py @@ -125,7 +125,6 @@ class FakeIdentityv2Client(object): def __init__(self, **kwargs): self.roles = mock.Mock() self.roles.resource_class = fakes.FakeResource(None, {}) - self.service_catalog = mock.Mock() self.services = mock.Mock() self.services.resource_class = fakes.FakeResource(None, {}) self.tenants = mock.Mock() diff --git a/openstackclient/tests/identity/v2_0/test_token.py b/openstackclient/tests/identity/v2_0/test_token.py index e094ad4a..4184326c 100644 --- a/openstackclient/tests/identity/v2_0/test_token.py +++ b/openstackclient/tests/identity/v2_0/test_token.py @@ -13,6 +13,8 @@ # under the License. # +import mock + from openstackclient.identity.v2_0 import token from openstackclient.tests.identity.v2_0 import fakes as identity_fakes @@ -23,8 +25,9 @@ class TestToken(identity_fakes.TestIdentityv2): super(TestToken, self).setUp() # Get a shortcut to the Service Catalog Mock - self.sc_mock = self.app.client_manager.identity.service_catalog - self.sc_mock.reset_mock() + self.sc_mock = mock.Mock() + self.app.client_manager.auth_ref = mock.Mock() + self.app.client_manager.auth_ref.service_catalog = self.sc_mock class TestTokenIssue(TestToken): diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index 8143409d..e9cda9ff 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -21,10 +21,13 @@ from openstackclient.tests import utils domain_id = 'd1' domain_name = 'oftheking' +domain_description = 'domain description' DOMAIN = { 'id': domain_id, 'name': domain_name, + 'description': domain_description, + 'enabled': True, } group_id = 'gr-010' @@ -74,6 +77,20 @@ SERVICE = { 'enabled': True, } +endpoint_id = 'e-123' +endpoint_url = 'http://127.0.0.1:35357' +endpoint_region = 'RegionOne' +endpoint_interface = 'admin' + +ENDPOINT = { + 'id': endpoint_id, + 'url': endpoint_url, + 'region': endpoint_region, + 'interface': endpoint_interface, + 'service_id': service_id, + 'enabled': True, +} + user_id = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa' user_name = 'paul' user_description = 'Sir Paul' @@ -182,6 +199,8 @@ class FakeIdentityv3Client(object): def __init__(self, **kwargs): self.domains = mock.Mock() self.domains.resource_class = fakes.FakeResource(None, {}) + self.endpoints = mock.Mock() + self.endpoints.resource_class = fakes.FakeResource(None, {}) self.groups = mock.Mock() self.groups.resource_class = fakes.FakeResource(None, {}) self.oauth1 = mock.Mock() @@ -201,14 +220,18 @@ class FakeIdentityv3Client(object): self.management_url = kwargs['endpoint'] -class FakeFederatedClient(FakeIdentityv3Client): +class FakeFederationManager(object): def __init__(self, **kwargs): - super(FakeFederatedClient, self).__init__(**kwargs) - self.identity_providers = mock.Mock() self.identity_providers.resource_class = fakes.FakeResource(None, {}) +class FakeFederatedClient(FakeIdentityv3Client): + def __init__(self, **kwargs): + super(FakeFederatedClient, self).__init__(**kwargs) + self.federation = FakeFederationManager() + + class FakeOAuth1Client(FakeIdentityv3Client): def __init__(self, **kwargs): super(FakeOAuth1Client, self).__init__(**kwargs) diff --git a/openstackclient/tests/identity/v3/test_domain.py b/openstackclient/tests/identity/v3/test_domain.py new file mode 100644 index 00000000..8dad5bcc --- /dev/null +++ b/openstackclient/tests/identity/v3/test_domain.py @@ -0,0 +1,414 @@ +# 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 copy + +from openstackclient.identity.v3 import domain +from openstackclient.tests import fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes + + +class TestDomain(identity_fakes.TestIdentityv3): + + def setUp(self): + super(TestDomain, self).setUp() + + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains + self.domains_mock.reset_mock() + + +class TestDomainCreate(TestDomain): + + def setUp(self): + super(TestDomainCreate, self).setUp() + + self.domains_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.DOMAIN), + loaded=True, + ) + + # Get the command object to test + self.cmd = domain.CreateDomain(self.app, None) + + def test_domain_create_no_options(self): + arglist = [ + identity_fakes.domain_name, + ] + verifylist = [ + ('enabled', True), + ('name', identity_fakes.domain_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': identity_fakes.domain_name, + 'description': None, + 'enabled': True, + } + self.domains_mock.create.assert_called_with( + **kwargs + ) + + collist = ('description', 'enabled', 'id', 'name') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.domain_description, + True, + identity_fakes.domain_id, + identity_fakes.domain_name, + ) + self.assertEqual(datalist, data) + + def test_domain_create_description(self): + arglist = [ + '--description', 'new desc', + identity_fakes.domain_name, + ] + verifylist = [ + ('description', 'new desc'), + ('enabled', True), + ('name', identity_fakes.domain_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': identity_fakes.domain_name, + 'description': 'new desc', + 'enabled': True, + } + self.domains_mock.create.assert_called_with( + **kwargs + ) + + collist = ('description', 'enabled', 'id', 'name') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.domain_description, + True, + identity_fakes.domain_id, + identity_fakes.domain_name, + ) + self.assertEqual(datalist, data) + + def test_domain_create_enable(self): + arglist = [ + '--enable', + identity_fakes.domain_name, + ] + verifylist = [ + ('enabled', True), + ('name', identity_fakes.domain_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': identity_fakes.domain_name, + 'description': None, + 'enabled': True, + } + self.domains_mock.create.assert_called_with( + **kwargs + ) + + collist = ('description', 'enabled', 'id', 'name') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.domain_description, + True, + identity_fakes.domain_id, + identity_fakes.domain_name, + ) + self.assertEqual(datalist, data) + + def test_domain_create_disable(self): + arglist = [ + '--disable', + identity_fakes.domain_name, + ] + verifylist = [ + ('enabled', False), + ('name', identity_fakes.domain_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': identity_fakes.domain_name, + 'description': None, + 'enabled': False, + } + self.domains_mock.create.assert_called_with( + **kwargs + ) + + collist = ('description', 'enabled', 'id', 'name') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.domain_description, + True, + identity_fakes.domain_id, + identity_fakes.domain_name, + ) + self.assertEqual(datalist, data) + + +class TestDomainDelete(TestDomain): + + def setUp(self): + super(TestDomainDelete, self).setUp() + + # This is the return value for utils.find_resource() + self.domains_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.DOMAIN), + loaded=True, + ) + self.domains_mock.delete.return_value = None + + # Get the command object to test + self.cmd = domain.DeleteDomain(self.app, None) + + def test_domain_delete(self): + arglist = [ + identity_fakes.domain_id, + ] + verifylist = [ + ('domain', identity_fakes.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.run(parsed_args) + self.assertEqual(0, result) + + self.domains_mock.delete.assert_called_with( + identity_fakes.domain_id, + ) + + +class TestDomainList(TestDomain): + + def setUp(self): + super(TestDomainList, self).setUp() + + self.domains_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.DOMAIN), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = domain.ListDomain(self.app, None) + + def test_domain_list_no_options(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + self.domains_mock.list.assert_called_with() + + collist = ('ID', 'Name', 'Enabled', 'Description') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.domain_id, + identity_fakes.domain_name, + True, + identity_fakes.domain_description, + ), ) + self.assertEqual(datalist, tuple(data)) + + +class TestDomainSet(TestDomain): + + def setUp(self): + super(TestDomainSet, self).setUp() + + self.domains_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.DOMAIN), + loaded=True, + ) + + self.domains_mock.update.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.DOMAIN), + loaded=True, + ) + + # Get the command object to test + self.cmd = domain.SetDomain(self.app, None) + + def test_domain_set_no_options(self): + arglist = [ + identity_fakes.domain_name, + ] + verifylist = [ + ('domain', identity_fakes.domain_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.run(parsed_args) + self.assertEqual(0, result) + + self.assertNotCalled(self.domains_mock.update) + + def test_domain_set_name(self): + arglist = [ + '--name', 'qwerty', + identity_fakes.domain_id, + ] + verifylist = [ + ('name', 'qwerty'), + ('domain', identity_fakes.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.run(parsed_args) + self.assertEqual(0, result) + + # Set expected values + kwargs = { + 'name': 'qwerty', + } + self.domains_mock.update.assert_called_with( + identity_fakes.domain_id, + **kwargs + ) + + def test_domain_set_description(self): + arglist = [ + '--description', 'new desc', + identity_fakes.domain_id, + ] + verifylist = [ + ('description', 'new desc'), + ('domain', identity_fakes.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.run(parsed_args) + self.assertEqual(0, result) + + # Set expected values + kwargs = { + 'description': 'new desc', + } + self.domains_mock.update.assert_called_with( + identity_fakes.domain_id, + **kwargs + ) + + def test_domain_set_enable(self): + arglist = [ + '--enable', + identity_fakes.domain_id, + ] + verifylist = [ + ('enabled', True), + ('domain', identity_fakes.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.run(parsed_args) + self.assertEqual(0, result) + + # Set expected values + kwargs = { + 'enabled': True, + } + self.domains_mock.update.assert_called_with( + identity_fakes.domain_id, + **kwargs + ) + + def test_domain_set_disable(self): + arglist = [ + '--disable', + identity_fakes.domain_id, + ] + verifylist = [ + ('disabled', True), + ('domain', identity_fakes.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.run(parsed_args) + self.assertEqual(0, result) + + # Set expected values + kwargs = { + 'enabled': False, + } + self.domains_mock.update.assert_called_with( + identity_fakes.domain_id, + **kwargs + ) + + +class TestDomainShow(TestDomain): + + def setUp(self): + super(TestDomainShow, self).setUp() + + self.domains_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.DOMAIN), + loaded=True, + ) + + # Get the command object to test + self.cmd = domain.ShowDomain(self.app, None) + + def test_domain_show(self): + arglist = [ + identity_fakes.domain_id, + ] + verifylist = [ + ('domain', identity_fakes.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + self.domains_mock.get.assert_called_with( + identity_fakes.domain_id, + ) + + collist = ('description', 'enabled', 'id', 'name') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.domain_description, + True, + identity_fakes.domain_id, + identity_fakes.domain_name, + ) + self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v3/test_endpoint.py b/openstackclient/tests/identity/v3/test_endpoint.py new file mode 100644 index 00000000..ea05326e --- /dev/null +++ b/openstackclient/tests/identity/v3/test_endpoint.py @@ -0,0 +1,671 @@ +# 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 copy + +from openstackclient.identity.v3 import endpoint +from openstackclient.tests import fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes + + +class TestEndpoint(identity_fakes.TestIdentityv3): + + def setUp(self): + super(TestEndpoint, self).setUp() + + # Get a shortcut to the EndpointManager Mock + self.endpoints_mock = self.app.client_manager.identity.endpoints + self.endpoints_mock.reset_mock() + + # Get a shortcut to the ServiceManager Mock + self.services_mock = self.app.client_manager.identity.services + self.services_mock.reset_mock() + + +class TestEndpointCreate(TestEndpoint): + + def setUp(self): + super(TestEndpointCreate, self).setUp() + + self.endpoints_mock.create.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ENDPOINT), + loaded=True, + ) + + # This is the return value for common.find_resource(service) + self.services_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.SERVICE), + loaded=True, + ) + + # Get the command object to test + self.cmd = endpoint.CreateEndpoint(self.app, None) + + def test_endpoint_create_no_options(self): + arglist = [ + identity_fakes.service_id, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_url, + ] + verifylist = [ + ('enabled', True), + ('service', identity_fakes.service_id), + ('interface', identity_fakes.endpoint_interface), + ('url', identity_fakes.endpoint_url), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'service': identity_fakes.service_id, + 'url': identity_fakes.endpoint_url, + 'interface': identity_fakes.endpoint_interface, + 'enabled': True, + 'region': None, + } + + self.endpoints_mock.create.assert_called_with( + **kwargs + ) + + collist = ('enabled', 'id', 'interface', 'region', 'service_id', + 'service_name', 'service_type', 'url') + self.assertEqual(collist, columns) + datalist = ( + True, + identity_fakes.endpoint_id, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_region, + identity_fakes.service_id, + identity_fakes.service_name, + identity_fakes.service_type, + identity_fakes.endpoint_url, + ) + self.assertEqual(datalist, data) + + def test_endpoint_create_region(self): + arglist = [ + identity_fakes.service_id, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_url, + '--region', identity_fakes.endpoint_region, + ] + verifylist = [ + ('enabled', True), + ('service', identity_fakes.service_id), + ('interface', identity_fakes.endpoint_interface), + ('url', identity_fakes.endpoint_url), + ('region', identity_fakes.endpoint_region), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'service': identity_fakes.service_id, + 'url': identity_fakes.endpoint_url, + 'interface': identity_fakes.endpoint_interface, + 'enabled': True, + 'region': identity_fakes.endpoint_region, + } + + self.endpoints_mock.create.assert_called_with( + **kwargs + ) + + collist = ('enabled', 'id', 'interface', 'region', 'service_id', + 'service_name', 'service_type', 'url') + self.assertEqual(collist, columns) + datalist = ( + True, + identity_fakes.endpoint_id, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_region, + identity_fakes.service_id, + identity_fakes.service_name, + identity_fakes.service_type, + identity_fakes.endpoint_url, + ) + self.assertEqual(datalist, data) + + def test_endpoint_create_enable(self): + arglist = [ + identity_fakes.service_id, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_url, + '--enable' + ] + verifylist = [ + ('enabled', True), + ('service', identity_fakes.service_id), + ('interface', identity_fakes.endpoint_interface), + ('url', identity_fakes.endpoint_url), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'service': identity_fakes.service_id, + 'url': identity_fakes.endpoint_url, + 'interface': identity_fakes.endpoint_interface, + 'enabled': True, + 'region': None, + } + + self.endpoints_mock.create.assert_called_with( + **kwargs + ) + + collist = ('enabled', 'id', 'interface', 'region', 'service_id', + 'service_name', 'service_type', 'url') + self.assertEqual(collist, columns) + datalist = ( + True, + identity_fakes.endpoint_id, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_region, + identity_fakes.service_id, + identity_fakes.service_name, + identity_fakes.service_type, + identity_fakes.endpoint_url, + ) + self.assertEqual(datalist, data) + + def test_endpoint_create_disable(self): + arglist = [ + identity_fakes.service_id, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_url, + '--disable', + ] + verifylist = [ + ('enabled', False), + ('service', identity_fakes.service_id), + ('interface', identity_fakes.endpoint_interface), + ('url', identity_fakes.endpoint_url), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'service': identity_fakes.service_id, + 'url': identity_fakes.endpoint_url, + 'interface': identity_fakes.endpoint_interface, + 'enabled': False, + 'region': None, + } + + self.endpoints_mock.create.assert_called_with( + **kwargs + ) + + collist = ('enabled', 'id', 'interface', 'region', 'service_id', + 'service_name', 'service_type', 'url') + self.assertEqual(collist, columns) + datalist = ( + True, + identity_fakes.endpoint_id, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_region, + identity_fakes.service_id, + identity_fakes.service_name, + identity_fakes.service_type, + identity_fakes.endpoint_url, + ) + self.assertEqual(datalist, data) + + +class TestEndpointDelete(TestEndpoint): + + def setUp(self): + super(TestEndpointDelete, self).setUp() + + # This is the return value for utils.find_resource(endpoint) + self.endpoints_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ENDPOINT), + loaded=True, + ) + self.endpoints_mock.delete.return_value = None + + # Get the command object to test + self.cmd = endpoint.DeleteEndpoint(self.app, None) + + def test_endpoint_delete(self): + arglist = [ + identity_fakes.endpoint_id, + ] + verifylist = [ + ('endpoint', identity_fakes.endpoint_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.run(parsed_args) + self.assertEqual(0, result) + + self.endpoints_mock.delete.assert_called_with( + identity_fakes.endpoint_id, + ) + + +class TestEndpointList(TestEndpoint): + + def setUp(self): + super(TestEndpointList, self).setUp() + + self.endpoints_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ENDPOINT), + loaded=True, + ), + ] + + # This is the return value for common.find_resource(service) + self.services_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.SERVICE), + loaded=True, + ) + + # Get the command object to test + self.cmd = endpoint.ListEndpoint(self.app, None) + + def test_endpoint_list_no_options(self): + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + self.endpoints_mock.list.assert_called_with() + + collist = ('ID', 'Region', 'Service Name', 'Service Type', + 'Enabled', 'Interface', 'URL') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.endpoint_id, + identity_fakes.endpoint_region, + identity_fakes.service_name, + identity_fakes.service_type, + True, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_url, + ),) + self.assertEqual(datalist, tuple(data)) + + def test_endpoint_list_service(self): + arglist = [ + '--service', identity_fakes.service_name, + ] + verifylist = [ + ('service', identity_fakes.service_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'service': identity_fakes.service_id, + } + self.endpoints_mock.list.assert_called_with(**kwargs) + + collist = ('ID', 'Region', 'Service Name', 'Service Type', + 'Enabled', 'Interface', 'URL') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.endpoint_id, + identity_fakes.endpoint_region, + identity_fakes.service_name, + identity_fakes.service_type, + True, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_url, + ),) + self.assertEqual(datalist, tuple(data)) + + def test_endpoint_list_interface(self): + arglist = [ + '--interface', identity_fakes.endpoint_interface, + ] + verifylist = [ + ('interface', identity_fakes.endpoint_interface), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'interface': identity_fakes.endpoint_interface, + } + self.endpoints_mock.list.assert_called_with(**kwargs) + + collist = ('ID', 'Region', 'Service Name', 'Service Type', + 'Enabled', 'Interface', 'URL') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.endpoint_id, + identity_fakes.endpoint_region, + identity_fakes.service_name, + identity_fakes.service_type, + True, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_url, + ),) + self.assertEqual(datalist, tuple(data)) + + def test_endpoint_list_region(self): + arglist = [ + '--region', identity_fakes.endpoint_region, + ] + verifylist = [ + ('region', identity_fakes.endpoint_region), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'region': identity_fakes.endpoint_region, + } + self.endpoints_mock.list.assert_called_with(**kwargs) + + collist = ('ID', 'Region', 'Service Name', 'Service Type', + 'Enabled', 'Interface', 'URL') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.endpoint_id, + identity_fakes.endpoint_region, + identity_fakes.service_name, + identity_fakes.service_type, + True, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_url, + ),) + self.assertEqual(datalist, tuple(data)) + + +class TestEndpointSet(TestEndpoint): + + def setUp(self): + super(TestEndpointSet, self).setUp() + + # This is the return value for utils.find_resource(endpoint) + self.endpoints_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ENDPOINT), + loaded=True, + ) + + self.endpoints_mock.update.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ENDPOINT), + loaded=True, + ) + + # This is the return value for common.find_resource(service) + self.services_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.SERVICE), + loaded=True, + ) + + # Get the command object to test + self.cmd = endpoint.SetEndpoint(self.app, None) + + def test_endpoint_set_no_options(self): + arglist = [ + identity_fakes.endpoint_id, + ] + verifylist = [ + ('endpoint', identity_fakes.endpoint_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.run(parsed_args) + self.assertEqual(0, result) + + self.assertNotCalled(self.endpoints_mock.update) + + def test_endpoint_set_interface(self): + arglist = [ + '--interface', 'public', + identity_fakes.endpoint_id + ] + verifylist = [ + ('interface', 'public'), + ('endpoint', identity_fakes.endpoint_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.run(parsed_args) + self.assertEqual(0, result) + + # Set expected values + kwargs = { + 'enabled': None, + 'interface': 'public', + 'url': None, + 'region': None, + 'service': None, + } + self.endpoints_mock.update.assert_called_with( + identity_fakes.endpoint_id, + **kwargs + ) + + def test_endpoint_set_url(self): + arglist = [ + '--url', 'http://localhost:5000', + identity_fakes.endpoint_id + ] + verifylist = [ + ('url', 'http://localhost:5000'), + ('endpoint', identity_fakes.endpoint_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.run(parsed_args) + self.assertEqual(0, result) + + # Set expected values + kwargs = { + 'enabled': None, + 'interface': None, + 'url': 'http://localhost:5000', + 'region': None, + 'service': None, + } + self.endpoints_mock.update.assert_called_with( + identity_fakes.endpoint_id, + **kwargs + ) + + def test_endpoint_set_service(self): + arglist = [ + '--service', identity_fakes.service_id, + identity_fakes.endpoint_id + ] + verifylist = [ + ('service', identity_fakes.service_id), + ('endpoint', identity_fakes.endpoint_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.run(parsed_args) + self.assertEqual(0, result) + + # Set expected values + kwargs = { + 'enabled': None, + 'interface': None, + 'url': None, + 'region': None, + 'service': identity_fakes.service_id, + } + self.endpoints_mock.update.assert_called_with( + identity_fakes.endpoint_id, + **kwargs + ) + + def test_endpoint_set_region(self): + arglist = [ + '--region', 'e-rzzz', + identity_fakes.endpoint_id + ] + verifylist = [ + ('region', 'e-rzzz'), + ('endpoint', identity_fakes.endpoint_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.run(parsed_args) + self.assertEqual(0, result) + + # Set expected values + kwargs = { + 'enabled': None, + 'interface': None, + 'url': None, + 'region': 'e-rzzz', + 'service': None, + } + self.endpoints_mock.update.assert_called_with( + identity_fakes.endpoint_id, + **kwargs + ) + + def test_endpoint_set_enable(self): + arglist = [ + '--enable', + identity_fakes.endpoint_id + ] + verifylist = [ + ('enabled', True), + ('endpoint', identity_fakes.endpoint_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.run(parsed_args) + self.assertEqual(0, result) + + # Set expected values + kwargs = { + 'enabled': True, + 'interface': None, + 'url': None, + 'region': None, + 'service': None, + } + self.endpoints_mock.update.assert_called_with( + identity_fakes.endpoint_id, + **kwargs + ) + + def test_endpoint_set_disable(self): + arglist = [ + '--disable', + identity_fakes.endpoint_id + ] + verifylist = [ + ('disabled', True), + ('endpoint', identity_fakes.endpoint_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.run(parsed_args) + self.assertEqual(0, result) + + # Set expected values + kwargs = { + 'enabled': False, + 'interface': None, + 'url': None, + 'region': None, + 'service': None, + } + self.endpoints_mock.update.assert_called_with( + identity_fakes.endpoint_id, + **kwargs + ) + + +class TestEndpointShow(TestEndpoint): + + def setUp(self): + super(TestEndpointShow, self).setUp() + + self.endpoints_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ENDPOINT), + loaded=True, + ) + + # This is the return value for common.find_resource(service) + self.services_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.SERVICE), + loaded=True, + ) + + # Get the command object to test + self.cmd = endpoint.ShowEndpoint(self.app, None) + + def test_endpoint_show(self): + arglist = [ + identity_fakes.endpoint_id, + ] + verifylist = [ + ('endpoint', identity_fakes.endpoint_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + self.endpoints_mock.get.assert_called_with( + identity_fakes.endpoint_id, + ) + + collist = ('enabled', 'id', 'interface', 'region', 'service_id', + 'service_name', 'service_type', 'url') + self.assertEqual(collist, columns) + datalist = ( + True, + identity_fakes.endpoint_id, + identity_fakes.endpoint_interface, + identity_fakes.endpoint_region, + identity_fakes.service_id, + identity_fakes.service_name, + identity_fakes.service_type, + identity_fakes.endpoint_url, + ) + self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index 280d9227..c74bce8e 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -24,8 +24,8 @@ class TestIdentityProvider(identity_fakes.TestFederatedIdentity): def setUp(self): super(TestIdentityProvider, self).setUp() - identity_lib = self.app.client_manager.identity - self.identity_providers_mock = identity_lib.identity_providers + federation_lib = self.app.client_manager.identity.federation + self.identity_providers_mock = federation_lib.identity_providers self.identity_providers_mock.reset_mock() @@ -56,7 +56,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): } self.identity_providers_mock.create.assert_called_with( - identity_fakes.idp_id, + id=identity_fakes.idp_id, **kwargs ) @@ -88,7 +88,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): } self.identity_providers_mock.create.assert_called_with( - identity_fakes.idp_id, + id=identity_fakes.idp_id, **kwargs ) @@ -128,7 +128,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): } self.identity_providers_mock.create.assert_called_with( - identity_fakes.idp_id, + id=identity_fakes.idp_id, **kwargs ) @@ -217,12 +217,12 @@ class TestIdentityProviderShow(TestIdentityProvider): def setUp(self): super(TestIdentityProviderShow, self).setUp() - self.identity_providers_mock.get.return_value = fakes.FakeResource( + ret = fakes.FakeResource( None, copy.deepcopy(identity_fakes.IDENTITY_PROVIDER), loaded=True, ) - + self.identity_providers_mock.get.return_value = ret # Get the command object to test self.cmd = identity_provider.ShowIdentityProvider(self.app, None) diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py index fa02ecb9..3d2a402b 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/identity/v3/test_role.py @@ -715,7 +715,7 @@ class TestRoleShow(TestRole): # Get the command object to test self.cmd = role.ShowRole(self.app, None) - def test_service_show(self): + def test_role_show(self): arglist = [ identity_fakes.role_name, ] diff --git a/openstackclient/tests/identity/v3/test_user.py b/openstackclient/tests/identity/v3/test_user.py index 569d9140..42df5773 100644 --- a/openstackclient/tests/identity/v3/test_user.py +++ b/openstackclient/tests/identity/v3/test_user.py @@ -13,7 +13,9 @@ # under the License. # +import contextlib import copy + import mock from openstackclient.identity.v3 import user @@ -944,6 +946,52 @@ class TestUserSet(TestUser): ) +class TestUserSetPassword(TestUser): + + def setUp(self): + super(TestUserSetPassword, self).setUp() + self.cmd = user.SetPasswordUser(self.app, None) + + @staticmethod + @contextlib.contextmanager + def _mock_get_password(*passwords): + mocker = mock.Mock(side_effect=passwords) + with mock.patch("openstackclient.common.utils.get_password", mocker): + yield + + def test_user_password_change(self): + current_pass = 'old_pass' + new_pass = 'new_pass' + arglist = [ + '--password', new_pass, + ] + verifylist = [ + ('password', new_pass), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # Mock getting user current password. + with self._mock_get_password(current_pass): + self.cmd.take_action(parsed_args) + + self.users_mock.update_password.assert_called_with( + current_pass, new_pass + ) + + def test_user_create_password_prompt(self): + current_pass = 'old_pass' + new_pass = 'new_pass' + parsed_args = self.check_parser(self.cmd, [], []) + + # Mock getting user current and new password. + with self._mock_get_password(current_pass, new_pass): + self.cmd.take_action(parsed_args) + + self.users_mock.update_password.assert_called_with( + current_pass, new_pass + ) + + class TestUserShow(TestUser): def setUp(self): diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index b014482a..3f97b151 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -446,3 +446,48 @@ class TestImageSet(TestImage): image_fakes.image_id, **kwargs ) + + +class TestImageList(TestImage): + + def setUp(self): + super(TestImageList, self).setUp() + + # This is the return value for utils.find_resource() + self.images_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(image_fakes.IMAGE), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = image.ListImage(self.app, None) + + def test_image_list_long_option(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + self.images_mock.list.assert_called_with() + + collist = ('ID', 'Name', 'Disk Format', 'Container Format', + 'Size', 'Status') + + self.assertEqual(columns, collist) + datalist = (( + image_fakes.image_id, + image_fakes.image_name, + '', + '', + '', + '', + ), ) + self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index ef84e2c0..3e9eeebb 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -15,7 +15,7 @@ import copy -from openstackclient.image.v1 import image +from openstackclient.image.v2 import image from openstackclient.tests import fakes from openstackclient.tests.image.v2 import fakes as image_fakes @@ -61,3 +61,48 @@ class TestImageDelete(TestImage): self.images_mock.delete.assert_called_with( image_fakes.image_id, ) + + +class TestImageList(TestImage): + + def setUp(self): + super(TestImageList, self).setUp() + + # This is the return value for utils.find_resource() + self.images_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(image_fakes.IMAGE), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = image.ListImage(self.app, None) + + def test_image_list_long_option(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + self.images_mock.list.assert_called_with() + + collist = ('ID', 'Name', 'Disk Format', 'Container Format', + 'Size', 'Status') + + self.assertEqual(columns, collist) + datalist = (( + image_fakes.image_id, + image_fakes.image_name, + '', + '', + '', + '', + ), ) + self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 08b61a0a..468db5e0 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -61,7 +61,7 @@ class TestCreateNetwork(common.TestNetworkBase): def test_create_all_options(self): arglist = [ - "--admin-state-down", + "--disable", "--share", FAKE_NAME, ] + self.given_show_options @@ -88,7 +88,7 @@ class TestCreateNetwork(common.TestNetworkBase): def test_create_other_options(self): arglist = [ - "--admin-state-up", + "--enable", "--no-share", FAKE_NAME, ] @@ -220,7 +220,7 @@ class TestSetNetwork(common.TestNetworkBase): def test_set_this(self): arglist = [ FAKE_NAME, - '--admin-state-up', + '--enable', '--name', 'noob', '--share', ] @@ -247,7 +247,7 @@ class TestSetNetwork(common.TestNetworkBase): def test_set_that(self): arglist = [ FAKE_NAME, - '--admin-state-down', + '--disable', '--no-share', ] verifylist = [ diff --git a/openstackclient/tests/object/v1/lib/test_container.py b/openstackclient/tests/object/v1/lib/test_container.py index f7355592..ce70b835 100644 --- a/openstackclient/tests/object/v1/lib/test_container.py +++ b/openstackclient/tests/object/v1/lib/test_container.py @@ -18,7 +18,7 @@ import mock from openstackclient.object.v1.lib import container as lib_container -from openstackclient.tests.common import test_restapi as restapi +from openstackclient.tests import fakes from openstackclient.tests.object.v1 import fakes as object_fakes @@ -39,156 +39,158 @@ class TestContainer(object_fakes.TestObjectv1): def setUp(self): super(TestContainer, self).setUp() - self.app.restapi = mock.MagicMock() + self.app.client_manager.session = mock.MagicMock() class TestContainerList(TestContainer): def test_container_list_no_options(self): resp = [{'name': 'is-name'}] - self.app.restapi.list.return_value = resp + self.app.client_manager.session.get().json.return_value = resp data = lib_container.list_containers( - self.app.restapi, + self.app.client_manager.session, fake_url, ) # Check expected values - self.app.restapi.list.assert_called_with( + self.app.client_manager.session.get.assert_called_with( fake_url, params={ 'format': 'json', } ) - self.assertEqual(data, resp) + self.assertEqual(resp, data) def test_container_list_marker(self): resp = [{'name': 'is-name'}] - self.app.restapi.list.return_value = resp + self.app.client_manager.session.get().json.return_value = resp data = lib_container.list_containers( - self.app.restapi, + self.app.client_manager.session, fake_url, marker='next', ) # Check expected values - self.app.restapi.list.assert_called_with( + self.app.client_manager.session.get.assert_called_with( fake_url, params={ 'format': 'json', 'marker': 'next', } ) - self.assertEqual(data, resp) + self.assertEqual(resp, data) def test_container_list_limit(self): resp = [{'name': 'is-name'}] - self.app.restapi.list.return_value = resp + self.app.client_manager.session.get().json.return_value = resp data = lib_container.list_containers( - self.app.restapi, + self.app.client_manager.session, fake_url, limit=5, ) # Check expected values - self.app.restapi.list.assert_called_with( + self.app.client_manager.session.get.assert_called_with( fake_url, params={ 'format': 'json', 'limit': 5, } ) - self.assertEqual(data, resp) + self.assertEqual(resp, data) def test_container_list_end_marker(self): resp = [{'name': 'is-name'}] - self.app.restapi.list.return_value = resp + self.app.client_manager.session.get().json.return_value = resp data = lib_container.list_containers( - self.app.restapi, + self.app.client_manager.session, fake_url, end_marker='last', ) # Check expected values - self.app.restapi.list.assert_called_with( + self.app.client_manager.session.get.assert_called_with( fake_url, params={ 'format': 'json', 'end_marker': 'last', } ) - self.assertEqual(data, resp) + self.assertEqual(resp, data) def test_container_list_prefix(self): resp = [{'name': 'is-name'}] - self.app.restapi.list.return_value = resp + self.app.client_manager.session.get().json.return_value = resp data = lib_container.list_containers( - self.app.restapi, + self.app.client_manager.session, fake_url, prefix='foo/', ) # Check expected values - self.app.restapi.list.assert_called_with( + self.app.client_manager.session.get.assert_called_with( fake_url, params={ 'format': 'json', 'prefix': 'foo/', } ) - self.assertEqual(data, resp) + self.assertEqual(resp, data) def test_container_list_full_listing(self): + sess = self.app.client_manager.session def side_effect(*args, **kwargs): - rv = self.app.restapi.list.return_value - self.app.restapi.list.return_value = [] - self.app.restapi.list.side_effect = None + rv = sess.get().json.return_value + sess.get().json.return_value = [] + sess.get().json.side_effect = None return rv resp = [{'name': 'is-name'}] - self.app.restapi.list.return_value = resp - self.app.restapi.list.side_effect = side_effect + sess.get().json.return_value = resp + sess.get().json.side_effect = side_effect data = lib_container.list_containers( - self.app.restapi, + self.app.client_manager.session, fake_url, full_listing=True, ) # Check expected values - self.app.restapi.list.assert_called_with( + sess.get.assert_called_with( fake_url, params={ 'format': 'json', 'marker': 'is-name', } ) - self.assertEqual(data, resp) + self.assertEqual(resp, data) class TestContainerShow(TestContainer): def test_container_show_no_options(self): resp = { + 'X-Container-Meta-Owner': fake_account, 'x-container-object-count': 1, 'x-container-bytes-used': 577, } - self.app.restapi.head.return_value = \ - restapi.FakeResponse(headers=resp) + self.app.client_manager.session.head.return_value = \ + fakes.FakeResponse(headers=resp) data = lib_container.show_container( - self.app.restapi, + self.app.client_manager.session, fake_url, 'is-name', ) # Check expected values - self.app.restapi.head.assert_called_with( + self.app.client_manager.session.head.assert_called_with( fake_url + '/is-name', ) @@ -202,4 +204,4 @@ class TestContainerShow(TestContainer): 'sync_to': None, 'sync_key': None, } - self.assertEqual(data, data_expected) + self.assertEqual(data_expected, data) diff --git a/openstackclient/tests/object/v1/lib/test_object.py b/openstackclient/tests/object/v1/lib/test_object.py index 064efb53..f96732b4 100644 --- a/openstackclient/tests/object/v1/lib/test_object.py +++ b/openstackclient/tests/object/v1/lib/test_object.py @@ -18,7 +18,7 @@ import mock from openstackclient.object.v1.lib import object as lib_object -from openstackclient.tests.common import test_restapi as restapi +from openstackclient.tests import fakes from openstackclient.tests.object.v1 import fakes as object_fakes @@ -40,99 +40,99 @@ class TestObject(object_fakes.TestObjectv1): def setUp(self): super(TestObject, self).setUp() - self.app.restapi = mock.MagicMock() + self.app.client_manager.session = mock.MagicMock() class TestObjectListObjects(TestObject): def test_list_objects_no_options(self): resp = [{'name': 'is-name'}] - self.app.restapi.list.return_value = resp + self.app.client_manager.session.get().json.return_value = resp data = lib_object.list_objects( - self.app.restapi, + self.app.client_manager.session, fake_url, fake_container, ) # Check expected values - self.app.restapi.list.assert_called_with( + self.app.client_manager.session.get.assert_called_with( fake_url + '/' + fake_container, params={ 'format': 'json', } ) - self.assertEqual(data, resp) + self.assertEqual(resp, data) def test_list_objects_marker(self): resp = [{'name': 'is-name'}] - self.app.restapi.list.return_value = resp + self.app.client_manager.session.get().json.return_value = resp data = lib_object.list_objects( - self.app.restapi, + self.app.client_manager.session, fake_url, fake_container, marker='next', ) # Check expected values - self.app.restapi.list.assert_called_with( + self.app.client_manager.session.get.assert_called_with( fake_url + '/' + fake_container, params={ 'format': 'json', 'marker': 'next', } ) - self.assertEqual(data, resp) + self.assertEqual(resp, data) def test_list_objects_limit(self): resp = [{'name': 'is-name'}] - self.app.restapi.list.return_value = resp + self.app.client_manager.session.get().json.return_value = resp data = lib_object.list_objects( - self.app.restapi, + self.app.client_manager.session, fake_url, fake_container, limit=5, ) # Check expected values - self.app.restapi.list.assert_called_with( + self.app.client_manager.session.get.assert_called_with( fake_url + '/' + fake_container, params={ 'format': 'json', 'limit': 5, } ) - self.assertEqual(data, resp) + self.assertEqual(resp, data) def test_list_objects_end_marker(self): resp = [{'name': 'is-name'}] - self.app.restapi.list.return_value = resp + self.app.client_manager.session.get().json.return_value = resp data = lib_object.list_objects( - self.app.restapi, + self.app.client_manager.session, fake_url, fake_container, end_marker='last', ) # Check expected values - self.app.restapi.list.assert_called_with( + self.app.client_manager.session.get.assert_called_with( fake_url + '/' + fake_container, params={ 'format': 'json', 'end_marker': 'last', } ) - self.assertEqual(data, resp) + self.assertEqual(resp, data) def test_list_objects_delimiter(self): resp = [{'name': 'is-name'}] - self.app.restapi.list.return_value = resp + self.app.client_manager.session.get().json.return_value = resp data = lib_object.list_objects( - self.app.restapi, + self.app.client_manager.session, fake_url, fake_container, delimiter='|', @@ -142,85 +142,86 @@ class TestObjectListObjects(TestObject): # NOTE(dtroyer): requests handles the URL encoding and we're # mocking that so use the otherwise-not-legal # pipe '|' char in the response. - self.app.restapi.list.assert_called_with( + self.app.client_manager.session.get.assert_called_with( fake_url + '/' + fake_container, params={ 'format': 'json', 'delimiter': '|', } ) - self.assertEqual(data, resp) + self.assertEqual(resp, data) def test_list_objects_prefix(self): resp = [{'name': 'is-name'}] - self.app.restapi.list.return_value = resp + self.app.client_manager.session.get().json.return_value = resp data = lib_object.list_objects( - self.app.restapi, + self.app.client_manager.session, fake_url, fake_container, prefix='foo/', ) # Check expected values - self.app.restapi.list.assert_called_with( + self.app.client_manager.session.get.assert_called_with( fake_url + '/' + fake_container, params={ 'format': 'json', 'prefix': 'foo/', } ) - self.assertEqual(data, resp) + self.assertEqual(resp, data) def test_list_objects_path(self): resp = [{'name': 'is-name'}] - self.app.restapi.list.return_value = resp + self.app.client_manager.session.get().json.return_value = resp data = lib_object.list_objects( - self.app.restapi, + self.app.client_manager.session, fake_url, fake_container, path='next', ) # Check expected values - self.app.restapi.list.assert_called_with( + self.app.client_manager.session.get.assert_called_with( fake_url + '/' + fake_container, params={ 'format': 'json', 'path': 'next', } ) - self.assertEqual(data, resp) + self.assertEqual(resp, data) def test_list_objects_full_listing(self): + sess = self.app.client_manager.session def side_effect(*args, **kwargs): - rv = self.app.restapi.list.return_value - self.app.restapi.list.return_value = [] - self.app.restapi.list.side_effect = None + rv = sess.get().json.return_value + sess.get().json.return_value = [] + sess.get().json.side_effect = None return rv resp = [{'name': 'is-name'}] - self.app.restapi.list.return_value = resp - self.app.restapi.list.side_effect = side_effect + sess.get().json.return_value = resp + sess.get().json.side_effect = side_effect data = lib_object.list_objects( - self.app.restapi, + sess, fake_url, fake_container, full_listing=True, ) # Check expected values - self.app.restapi.list.assert_called_with( + sess.get.assert_called_with( fake_url + '/' + fake_container, params={ 'format': 'json', 'marker': 'is-name', } ) - self.assertEqual(data, resp) + self.assertEqual(resp, data) class TestObjectShowObjects(TestObject): @@ -228,19 +229,20 @@ class TestObjectShowObjects(TestObject): def test_object_show_no_options(self): resp = { 'content-type': 'text/alpha', + 'x-container-meta-owner': fake_account, } - self.app.restapi.head.return_value = \ - restapi.FakeResponse(headers=resp) + self.app.client_manager.session.head.return_value = \ + fakes.FakeResponse(headers=resp) data = lib_object.show_object( - self.app.restapi, + self.app.client_manager.session, fake_url, fake_container, fake_object, ) # Check expected values - self.app.restapi.head.assert_called_with( + self.app.client_manager.session.head.assert_called_with( fake_url + '/%s/%s' % (fake_container, fake_object), ) @@ -250,7 +252,7 @@ class TestObjectShowObjects(TestObject): 'object': fake_object, 'content-type': 'text/alpha', } - self.assertEqual(data, data_expected) + self.assertEqual(data_expected, data) def test_object_show_all_options(self): resp = { @@ -258,22 +260,23 @@ class TestObjectShowObjects(TestObject): 'content-length': 577, 'last-modified': '20130101', 'etag': 'qaz', + 'x-container-meta-owner': fake_account, 'x-object-manifest': None, 'x-object-meta-wife': 'Wilma', 'x-tra-header': 'yabba-dabba-do', } - self.app.restapi.head.return_value = \ - restapi.FakeResponse(headers=resp) + self.app.client_manager.session.head.return_value = \ + fakes.FakeResponse(headers=resp) data = lib_object.show_object( - self.app.restapi, + self.app.client_manager.session, fake_url, fake_container, fake_object, ) # Check expected values - self.app.restapi.head.assert_called_with( + self.app.client_manager.session.head.assert_called_with( fake_url + '/%s/%s' % (fake_container, fake_object), ) @@ -286,7 +289,7 @@ class TestObjectShowObjects(TestObject): 'last-modified': '20130101', 'etag': 'qaz', 'x-object-manifest': None, - 'Wife': 'Wilma', - 'X-Tra-Header': 'yabba-dabba-do', + 'wife': 'Wilma', + 'x-tra-header': 'yabba-dabba-do', } - self.assertEqual(data, data_expected) + self.assertEqual(data_expected, data) diff --git a/openstackclient/tests/object/v1/test_container.py b/openstackclient/tests/object/v1/test_container.py index 4afb1006..b72c79d6 100644 --- a/openstackclient/tests/object/v1/test_container.py +++ b/openstackclient/tests/object/v1/test_container.py @@ -77,7 +77,7 @@ class TestContainerList(TestObject): kwargs = { } c_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, **kwargs ) @@ -113,7 +113,7 @@ class TestContainerList(TestObject): 'prefix': 'bit', } c_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, **kwargs ) @@ -148,7 +148,7 @@ class TestContainerList(TestObject): 'marker': object_fakes.container_name, } c_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, **kwargs ) @@ -183,7 +183,7 @@ class TestContainerList(TestObject): 'end_marker': object_fakes.container_name_3, } c_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, **kwargs ) @@ -218,7 +218,7 @@ class TestContainerList(TestObject): 'limit': 2, } c_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, **kwargs ) @@ -252,7 +252,7 @@ class TestContainerList(TestObject): kwargs = { } c_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, **kwargs ) @@ -296,7 +296,7 @@ class TestContainerList(TestObject): 'full_listing': True, } c_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, **kwargs ) @@ -341,7 +341,7 @@ class TestContainerShow(TestObject): } # lib.container.show_container(api, url, container) c_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, object_fakes.container_name, **kwargs diff --git a/openstackclient/tests/object/v1/test_object.py b/openstackclient/tests/object/v1/test_object.py index bea0d270..26d07b2c 100644 --- a/openstackclient/tests/object/v1/test_object.py +++ b/openstackclient/tests/object/v1/test_object.py @@ -71,7 +71,7 @@ class TestObjectList(TestObject): columns, data = self.cmd.take_action(parsed_args) o_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, object_fakes.container_name, ) @@ -107,7 +107,7 @@ class TestObjectList(TestObject): 'prefix': 'floppy', } o_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, object_fakes.container_name_2, **kwargs @@ -143,7 +143,7 @@ class TestObjectList(TestObject): 'delimiter': '=', } o_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, object_fakes.container_name_2, **kwargs @@ -179,7 +179,7 @@ class TestObjectList(TestObject): 'marker': object_fakes.object_name_2, } o_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, object_fakes.container_name_2, **kwargs @@ -215,7 +215,7 @@ class TestObjectList(TestObject): 'end_marker': object_fakes.object_name_2, } o_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, object_fakes.container_name_2, **kwargs @@ -251,7 +251,7 @@ class TestObjectList(TestObject): 'limit': 2, } o_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, object_fakes.container_name_2, **kwargs @@ -287,7 +287,7 @@ class TestObjectList(TestObject): kwargs = { } o_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, object_fakes.container_name, **kwargs @@ -337,7 +337,7 @@ class TestObjectList(TestObject): 'full_listing': True, } o_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, object_fakes.container_name, **kwargs @@ -384,7 +384,7 @@ class TestObjectShow(TestObject): } # lib.container.show_container(api, url, container) c_mock.assert_called_with( - self.app.restapi, + self.app.client_manager.session, AUTH_URL, object_fakes.container_name, object_fakes.object_name_1, diff --git a/openstackclient/tests/utils.py b/openstackclient/tests/utils.py index 307abd7b..38d47250 100644 --- a/openstackclient/tests/utils.py +++ b/openstackclient/tests/utils.py @@ -37,6 +37,14 @@ class TestCase(testtools.TestCase): stderr = self.useFixture(fixtures.StringStream("stderr")).stream self.useFixture(fixtures.MonkeyPatch("sys.stderr", stderr)) + def assertNotCalled(self, m, msg=None): + """Assert a function was not called""" + + if m.called: + if not msg: + msg = 'method %s should not have been called' % m + self.fail(msg) + # 2.6 doesn't have the assert dict equals so make sure that it exists if tuple(sys.version_info)[0:2] < (2, 7): diff --git a/openstackclient/tests/volume/test_find_resource.py b/openstackclient/tests/volume/test_find_resource.py index 8539070f..56081966 100644 --- a/openstackclient/tests/volume/test_find_resource.py +++ b/openstackclient/tests/volume/test_find_resource.py @@ -21,6 +21,7 @@ from cinderclient.v1 import volumes from openstackclient.common import exceptions from openstackclient.common import utils from openstackclient.tests import utils as test_utils +from openstackclient.volume import client # noqa ID = '1after909' diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py index cb006b10..f020791a 100644 --- a/openstackclient/tests/volume/v1/test_volume.py +++ b/openstackclient/tests/volume/v1/test_volume.py @@ -101,7 +101,7 @@ class TestVolumeCreate(TestVolume): 'status', 'type', ) - self.assertEqual(columns, collist) + self.assertEqual(collist, columns) datalist = ( 'detached', volume_fakes.volume_zone, @@ -113,7 +113,7 @@ class TestVolumeCreate(TestVolume): '', volume_fakes.volume_type, ) - self.assertEqual(data, datalist) + self.assertEqual(datalist, data) def test_volume_create_options(self): arglist = [ @@ -165,7 +165,7 @@ class TestVolumeCreate(TestVolume): 'status', 'type', ) - self.assertEqual(columns, collist) + self.assertEqual(collist, columns) datalist = ( 'detached', volume_fakes.volume_zone, @@ -177,7 +177,7 @@ class TestVolumeCreate(TestVolume): '', volume_fakes.volume_type, ) - self.assertEqual(data, datalist) + self.assertEqual(datalist, data) def test_volume_create_user_project_id(self): # Return a project @@ -240,7 +240,7 @@ class TestVolumeCreate(TestVolume): 'status', 'type', ) - self.assertEqual(columns, collist) + self.assertEqual(collist, columns) datalist = ( 'detached', volume_fakes.volume_zone, @@ -252,7 +252,7 @@ class TestVolumeCreate(TestVolume): '', volume_fakes.volume_type, ) - self.assertEqual(data, datalist) + self.assertEqual(datalist, data) def test_volume_create_user_project_name(self): # Return a project @@ -315,7 +315,7 @@ class TestVolumeCreate(TestVolume): 'status', 'type', ) - self.assertEqual(columns, collist) + self.assertEqual(collist, columns) datalist = ( 'detached', volume_fakes.volume_zone, @@ -327,7 +327,7 @@ class TestVolumeCreate(TestVolume): '', volume_fakes.volume_type, ) - self.assertEqual(data, datalist) + self.assertEqual(datalist, data) def test_volume_create_properties(self): arglist = [ @@ -376,7 +376,7 @@ class TestVolumeCreate(TestVolume): 'status', 'type', ) - self.assertEqual(columns, collist) + self.assertEqual(collist, columns) datalist = ( 'detached', volume_fakes.volume_zone, @@ -388,4 +388,4 @@ class TestVolumeCreate(TestVolume): '', volume_fakes.volume_type, ) - self.assertEqual(data, datalist) + self.assertEqual(datalist, data) diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 3e4af56c..99abac52 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -36,7 +36,7 @@ class CreateVolume(show.ShowOne): parser.add_argument( 'name', metavar='<name>', - help='Name of the volume', + help='Name of the new volume', ) parser.add_argument( '--size', @@ -48,7 +48,7 @@ class CreateVolume(show.ShowOne): parser.add_argument( '--snapshot-id', metavar='<snapshot-id>', - help='ID of the snapshot', + help='Use <snapshot-id> as source of new volume', ) parser.add_argument( '--description', @@ -73,7 +73,7 @@ class CreateVolume(show.ShowOne): parser.add_argument( '--availability-zone', metavar='<availability-zone>', - help='Availability zone to use', + help='Create new volume in <availability-zone>', ) parser.add_argument( '--property', @@ -85,12 +85,12 @@ class CreateVolume(show.ShowOne): parser.add_argument( '--image', metavar='<image>', - help='Reference to a stored image', + help='Use <image> as source of new volume', ) parser.add_argument( '--source', metavar='<volume>', - help='Source for volume clone', + help='Volume to clone (name or ID)', ) return parser @@ -143,7 +143,7 @@ class CreateVolume(show.ShowOne): class DeleteVolume(command.Command): - """Delete volume""" + """Delete a volume""" log = logging.getLogger(__name__ + '.DeleteVolume') @@ -152,7 +152,7 @@ class DeleteVolume(command.Command): parser.add_argument( 'volume', metavar='<volume>', - help='Name or ID of volume to delete', + help='Volume to delete (name or ID)', ) parser.add_argument( '--force', @@ -271,7 +271,7 @@ class SetVolume(command.Command): parser.add_argument( 'volume', metavar='<volume>', - help='Name or ID of volume to change', + help='Volume to change (name or ID)', ) parser.add_argument( '--name', @@ -315,7 +315,7 @@ class SetVolume(command.Command): class ShowVolume(show.ShowOne): - """Show specific volume""" + """Show volume details""" log = logging.getLogger(__name__ + '.ShowVolume') @@ -324,7 +324,7 @@ class ShowVolume(show.ShowOne): parser.add_argument( 'volume', metavar='<volume>', - help='Name or ID of volume to display', + help='Volume to display (name or ID)', ) return parser @@ -357,7 +357,7 @@ class UnsetVolume(command.Command): parser.add_argument( 'volume', metavar='<volume>', - help='Name or ID of volume to change', + help='Volume to change (name or ID)', ) parser.add_argument( '--property', |
