diff options
Diffstat (limited to 'openstackclient')
75 files changed, 2929 insertions, 823 deletions
diff --git a/openstackclient/api/api.py b/openstackclient/api/api.py index ba83ce4d..97eb7e4a 100644 --- a/openstackclient/api/api.py +++ b/openstackclient/api/api.py @@ -15,8 +15,9 @@ import simplejson as json -from keystoneclient import exceptions as ksc_exceptions -from keystoneclient import session as ksc_session +from keystoneauth1 import exceptions as ks_exceptions +from keystoneauth1 import session as ks_session + from openstackclient.common import exceptions @@ -24,7 +25,7 @@ class KeystoneSession(object): """Wrapper for the Keystone Session Restore some requests.session.Session compatibility; - keystoneclient.session.Session.request() has the method and url + keystoneauth1.session.Session.request() has the method and url arguments swapped from the rest of the requests-using world. """ @@ -70,7 +71,7 @@ class KeystoneSession(object): if not session: session = self.session if not session: - session = ksc_session.Session() + session = ks_session.Session() if self.endpoint: if url: @@ -255,7 +256,7 @@ class BaseAPI(KeystoneSession): return data[0] if len(data) > 1: msg = "Multiple %s exist with %s='%s'" - raise ksc_exceptions.CommandError( + raise exceptions.CommandError( msg % (resource, attr, value), ) @@ -314,7 +315,7 @@ class BaseAPI(KeystoneSession): num_bulk = len(bulk_list) if num_bulk == 0: msg = "none found" - raise ksc_exceptions.NotFound(msg) + raise exceptions.NotFound(msg) elif num_bulk > 1: msg = "many found" raise RuntimeError(msg) @@ -338,12 +339,12 @@ class BaseAPI(KeystoneSession): try: ret = self._request('GET', "/%s/%s" % (path, value)).json() - except ksc_exceptions.NotFound: + except ks_exceptions.NotFound: kwargs = {attr: value} try: ret = self.find_one("/%s/detail" % (path), **kwargs) - except ksc_exceptions.NotFound: + except ks_exceptions.NotFound: msg = "%s not found" % value - raise ksc_exceptions.NotFound(msg) + raise exceptions.NotFound(msg) return ret diff --git a/openstackclient/api/object_store_v1.py b/openstackclient/api/object_store_v1.py index ae03ab7d..ab75a78c 100644 --- a/openstackclient/api/object_store_v1.py +++ b/openstackclient/api/object_store_v1.py @@ -14,7 +14,9 @@ """Object Store v1 API Library""" import io +import logging import os + import six from six.moves import urllib @@ -24,6 +26,7 @@ except ImportError: from urlparse import urlparse # noqa from openstackclient.api import api +from openstackclient.common import utils class APIv1(api.BaseAPI): @@ -176,13 +179,24 @@ class APIv1(api.BaseAPI): 'x-container-object-count', None, ), - 'meta-owner': response.headers.get('x-container-meta-owner', 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), + 'bytes_used': response.headers.get('x-container-bytes-used', None) } + + if 'x-container-read' in response.headers: + data['read_acl'] = response.headers.get('x-container-read', None) + if 'x-container-write' in response.headers: + data['write_acl'] = response.headers.get('x-container-write', None) + if 'x-container-sync-to' in response.headers: + data['sync_to'] = response.headers.get('x-container-sync-to', None) + if 'x-container-sync-key' in response.headers: + data['sync_key'] = response.headers.get('x-container-sync-key', + None) + + properties = self._get_properties(response.headers, + 'x-container-meta-') + if properties: + data['properties'] = properties + return data def container_unset( @@ -434,12 +448,12 @@ class APIv1(api.BaseAPI): response = self._request('HEAD', "%s/%s" % (urllib.parse.quote(container), urllib.parse.quote(object))) + data = { 'account': self._find_account_id(), 'container': container, 'object': object, 'content-type': response.headers.get('content-type', None), - 'meta-owner': response.headers.get('x-container-meta-owner', None), } if 'content-length' in response.headers: data['content-length'] = response.headers.get( @@ -455,19 +469,10 @@ class APIv1(api.BaseAPI): 'x-object-manifest', None, ) - for key, value in six.iteritems(response.headers): - if key.startswith('x-object-meta-'): - data[key[len('x-object-meta-'):].lower()] = value - elif key not in ( - 'content-type', - 'content-length', - 'last-modified', - 'etag', - 'date', - 'x-object-manifest', - 'x-container-meta-owner', - ): - data[key.lower()] = value + + properties = self._get_properties(response.headers, 'x-object-meta-') + if properties: + data['properties'] = properties return data @@ -495,12 +500,16 @@ class APIv1(api.BaseAPI): # catalog should be enough. response = self._request("HEAD", "") data = {} - for k, v in response.headers.iteritems(): - data[k] = v + + properties = self._get_properties(response.headers, 'x-account-meta-') + if properties: + data['properties'] = properties + # Map containers, bytes and objects a bit nicer - data['Containers'] = data.pop('x-account-container-count', None) - data['Objects'] = data.pop('x-account-object-count', None) - data['Bytes'] = data.pop('x-account-bytes-used', None) + data['Containers'] = response.headers.get('x-account-container-count', + None) + data['Objects'] = response.headers.get('x-account-object-count', None) + data['Bytes'] = response.headers.get('x-account-bytes-used', None) # Add in Account info too data['Account'] = self._find_account_id() return data @@ -544,8 +553,23 @@ class APIv1(api.BaseAPI): # property we use: "X-Add-Container-Meta-Book: MobyDick", and the same # logic applies for Object properties + log = logging.getLogger(__name__ + '._set_properties') + headers = {} for k, v in properties.iteritems(): + if not utils.is_ascii(k) or not utils.is_ascii(v): + log.error('Cannot set property %s to non-ascii value', k) + continue + header_name = header_tag % k headers[header_name] = v return headers + + def _get_properties(self, headers, header_tag): + # Add in properties as a top level key, this is consistent with other + # OSC commands + properties = {} + for k, v in six.iteritems(headers): + if k.startswith(header_tag): + properties[k[len(header_tag):]] = v + return properties diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 55c6fe53..edabf65e 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -20,6 +20,7 @@ import logging import pkg_resources import sys +from oslo_utils import strutils import requests from openstackclient.api import auth @@ -167,7 +168,8 @@ class ClientManager(object): self._project_name = self._auth_params['tenant_name'] LOG.info('Using auth plugin: %s' % self.auth_plugin_name) - LOG.debug('Using parameters %s' % self._auth_params) + LOG.debug('Using parameters %s' % + strutils.mask_password(self._auth_params)) self.auth = auth_plugin.load_from_options(**self._auth_params) # needed by SAML authentication request_session = requests.session() diff --git a/openstackclient/common/context.py b/openstackclient/common/logs.py index 6d1aec13..6d1aec13 100644 --- a/openstackclient/common/context.py +++ b/openstackclient/common/logs.py diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index 8f6008e2..fd90369a 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -65,3 +65,18 @@ class RangeAction(argparse.Action): # Too many values msg = "Invalid range, too many values" raise argparse.ArgumentError(self, msg) + + +class NonNegativeAction(argparse.Action): + """A custom action to check whether the value is non-negative or not + + Ensures the value is >= 0. + """ + + def __call__(self, parser, namespace, values, option_string=None): + try: + assert(int(values) >= 0) + setattr(namespace, self.dest, values) + except Exception: + msg = "%s expected a non-negative integer" % (str(option_string)) + raise argparse.ArgumentTypeError(self, msg) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index e092feff..8a9b910f 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -81,6 +81,7 @@ class SetQuota(command.Command): parser.add_argument( '--%s' % v, metavar='<%s>' % v, + dest=k, type=int, help='New value for the %s quota' % v, ) @@ -94,6 +95,7 @@ class SetQuota(command.Command): @utils.log_method(log) def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity compute_client = self.app.client_manager.compute volume_client = self.app.client_manager.volume @@ -115,23 +117,29 @@ class SetQuota(command.Command): sys.stderr.write("No quotas updated") return + if parsed_args.project: + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ) + if parsed_args.quota_class: if compute_kwargs: compute_client.quota_classes.update( - parsed_args.project, + project.id, **compute_kwargs) if volume_kwargs: volume_client.quota_classes.update( - parsed_args.project, + project.id, **volume_kwargs) else: if compute_kwargs: compute_client.quotas.update( - parsed_args.project, + project.id, **compute_kwargs) if volume_kwargs: volume_client.quotas.update( - parsed_args.project, + project.id, **volume_kwargs) @@ -165,13 +173,19 @@ class ShowQuota(show.ShowOne): return parser def get_compute_volume_quota(self, client, parsed_args): + identity_client = self.app.client_manager.identity + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ).id + try: if parsed_args.quota_class: - quota = client.quota_classes.get(parsed_args.project) + quota = client.quota_classes.get(project) elif parsed_args.default: - quota = client.quotas.defaults(parsed_args.project) + quota = client.quotas.defaults(project) else: - quota = client.quotas.get(parsed_args.project) + quota = client.quotas.get(project) except Exception as e: if type(e).__name__ == 'EndpointNotFound': return {} diff --git a/openstackclient/common/session.py b/openstackclient/common/session.py index dda1c417..9b19fd46 100644 --- a/openstackclient/common/session.py +++ b/openstackclient/common/session.py @@ -11,9 +11,9 @@ # under the License. # -"""Subclass of keystoneclient.session""" +"""Subclass of keystoneauth1.session""" -from keystoneclient import session +from keystoneauth1 import session class TimingSession(session.Session): diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 51e2a2f9..42630d91 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -145,7 +145,6 @@ def format_dict(data): """Return a formatted string of key value pairs :param data: a dict - :param format: optional formatting hints :rtype: a string formatted to key='value' """ @@ -298,14 +297,16 @@ def wait_for_status(status_f, res_id, status_field='status', success_status=['active'], + error_status=['error'], sleep_time=5, callback=None): """Wait for status change on a resource during a long-running operation :param status_f: a status function that takes a single id argument :param res_id: the resource id to watch - :param success_status: a list of status strings for successful completion :param status_field: the status attribute in the returned resource object + :param success_status: a list of status strings for successful completion + :param error_status: a list of status strings for error :param sleep_time: wait this long (seconds) :param callback: called per sleep cycle, useful to display progress :rtype: True on success @@ -316,7 +317,7 @@ def wait_for_status(status_f, if status in success_status: retval = True break - elif status == 'error': + elif status in error_status: retval = False break if callback: @@ -334,6 +335,7 @@ def wait_for_delete(manager, callback=None): """Wait for resource deletion + :param manager: the manager from which we can get the resource :param res_id: the resource id to watch :param status_field: the status attribute in the returned resource object, this is used to check for error states while the resource is being @@ -375,7 +377,7 @@ def wait_for_delete(manager, def get_effective_log_level(): """Returns the lowest logging level considered by logging handlers - Retrieve an return the smallest log level set among the root + Retrieve and return the smallest log level set among the root logger's handlers (in case of multiple handlers). """ root_log = logging.getLogger() @@ -419,3 +421,11 @@ def build_kwargs_dict(arg_name, value): if value: kwargs[arg_name] = value return kwargs + + +def is_ascii(string): + try: + string.decode('ascii') + return True + except UnicodeDecodeError: + return False diff --git a/openstackclient/compute/v2/console.py b/openstackclient/compute/v2/console.py index bb0747b1..aafa5d44 100644 --- a/openstackclient/compute/v2/console.py +++ b/openstackclient/compute/v2/console.py @@ -22,6 +22,7 @@ import sys from cliff import command from cliff import show +from openstackclient.common import parseractions from openstackclient.common import utils @@ -42,6 +43,7 @@ class ShowConsoleLog(command.Command): metavar='<num-lines>', type=int, default=None, + action=parseractions.NonNegativeAction, help='Number of lines to display from the end of the log ' '(default=all)', ) diff --git a/openstackclient/compute/v2/fixedip.py b/openstackclient/compute/v2/fixedip.py index d105e391..da9d85c3 100644 --- a/openstackclient/compute/v2/fixedip.py +++ b/openstackclient/compute/v2/fixedip.py @@ -23,7 +23,7 @@ from openstackclient.common import utils class AddFixedIP(command.Command): - """Add fixed-ip command""" + """Add fixed IP address to server""" log = logging.getLogger(__name__ + ".AddFixedIP") @@ -32,12 +32,12 @@ class AddFixedIP(command.Command): parser.add_argument( "network", metavar="<network>", - help="Name of the network to fetch an IP address from", + help="Network to fetch an IP address from (name or ID)", ) 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 AddFixedIP(command.Command): class RemoveFixedIP(command.Command): - """Remove fixed-ip command""" + """Remove fixed IP address from server""" log = logging.getLogger(__name__ + ".RemoveFixedIP") @@ -65,12 +65,12 @@ class RemoveFixedIP(command.Command): parser.add_argument( "ip_address", metavar="<ip-address>", - help="IP address to remove from server", + help="IP address to remove from server (name only)", ) 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/flavor.py b/openstackclient/compute/v2/flavor.py index 3458cf79..7474580b 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -181,6 +181,15 @@ class ListFlavor(lister.Lister): action='store_true', default=False, help='List additional fields in output') + parser.add_argument( + '--marker', + metavar="<marker>", + help='The last flavor ID of the previous page') + parser.add_argument( + '--limit', + type=int, + metavar="<limit>", + help='Maximum number of flavors to display') return parser def take_action(self, parsed_args): @@ -202,7 +211,9 @@ class ListFlavor(lister.Lister): # and flavors from their own projects only. is_public = None if parsed_args.all else parsed_args.public - data = compute_client.flavors.list(is_public=is_public) + data = compute_client.flavors.list(is_public=is_public, + marker=parsed_args.marker, + limit=parsed_args.limit) if parsed_args.long: columns = columns + ( diff --git a/openstackclient/compute/v2/floatingip.py b/openstackclient/compute/v2/floatingip.py index c557c24b..65fe5910 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 to server""" + """Add floating IP address to server""" log = logging.getLogger(__name__ + ".AddFloatingIP") @@ -35,7 +35,7 @@ class AddFloatingIP(command.Command): parser.add_argument( "ip_address", metavar="<ip-address>", - help="IP address to add to server", + help="IP address to add to server (name only)", ) parser.add_argument( "server", @@ -56,7 +56,7 @@ class AddFloatingIP(command.Command): class CreateFloatingIP(show.ShowOne): - """Create new floating-ip""" + """Create new floating IP address""" log = logging.getLogger(__name__ + '.CreateFloatingIP') @@ -65,7 +65,7 @@ class CreateFloatingIP(show.ShowOne): parser.add_argument( 'pool', metavar='<pool>', - help='Pool to fetch floating IP from', + help='Pool to fetch IP address from (name or ID)', ) return parser @@ -80,7 +80,7 @@ class CreateFloatingIP(show.ShowOne): class DeleteFloatingIP(command.Command): - """Delete a floating-ip""" + """Delete a floating IP address""" log = logging.getLogger(__name__ + '.DeleteFloatingIP') @@ -89,7 +89,7 @@ class DeleteFloatingIP(command.Command): parser.add_argument( "ip_address", metavar="<ip-address>", - help="IP address to delete", + help="IP address to delete (ID only)", ) return parser @@ -107,7 +107,7 @@ class DeleteFloatingIP(command.Command): class ListFloatingIP(lister.Lister): - """List floating-ips""" + """List floating IP addresses""" log = logging.getLogger(__name__ + '.ListFloatingIP') @@ -127,7 +127,7 @@ class ListFloatingIP(lister.Lister): class RemoveFloatingIP(command.Command): - """Remove floating-ip from server""" + """Remove floating IP address from server""" log = logging.getLogger(__name__ + ".RemoveFloatingIP") @@ -136,7 +136,7 @@ class RemoveFloatingIP(command.Command): parser.add_argument( "ip_address", metavar="<ip-address>", - help="IP address to remove from server", + help="IP address to remove from server (name only)", ) parser.add_argument( "server", diff --git a/openstackclient/compute/v2/floatingippool.py b/openstackclient/compute/v2/floatingippool.py index cc485552..39a2d8fe 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-pools""" + """List pools of floating IP addresses""" log = logging.getLogger(__name__ + '.ListFloatingIPPool') diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index 3dd0c49b..6d38195c 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -23,7 +23,7 @@ from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc try: from novaclient.v2 import security_group_rules @@ -93,6 +93,64 @@ class CreateSecurityGroup(show.ShowOne): return zip(*sorted(six.iteritems(info))) +class CreateSecurityGroupRule(show.ShowOne): + """Create a new security group rule""" + + log = logging.getLogger(__name__ + ".CreateSecurityGroupRule") + + def get_parser(self, prog_name): + parser = super(CreateSecurityGroupRule, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='<group>', + help='Create rule in this security group (name or ID)', + ) + parser.add_argument( + "--proto", + metavar="<proto>", + default="tcp", + help="IP protocol (icmp, tcp, udp; default: tcp)", + ) + parser.add_argument( + "--src-ip", + metavar="<ip-address>", + default="0.0.0.0/0", + help="Source IP (may use CIDR notation; default: 0.0.0.0/0)", + ) + parser.add_argument( + "--dst-port", + metavar="<port-range>", + default=(0, 0), + action=parseractions.RangeAction, + help="Destination port, may be a range: 137:139 (default: 0; " + "only required for proto tcp and udp)", + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + compute_client = self.app.client_manager.compute + group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + if parsed_args.proto.lower() == 'icmp': + from_port, to_port = -1, -1 + else: + from_port, to_port = parsed_args.dst_port + data = compute_client.security_group_rules.create( + group.id, + parsed_args.proto, + from_port, + to_port, + parsed_args.src_ip, + ) + + info = _xform_security_group_rule(data._info) + return zip(*sorted(six.iteritems(info))) + + class DeleteSecurityGroup(command.Command): """Delete a security group""" @@ -103,7 +161,7 @@ class DeleteSecurityGroup(command.Command): parser.add_argument( 'group', metavar='<group>', - help='Name or ID of security group to delete', + help='Security group to delete (name or ID)', ) return parser @@ -119,8 +177,30 @@ class DeleteSecurityGroup(command.Command): return +class DeleteSecurityGroupRule(command.Command): + """Delete a security group rule""" + + log = logging.getLogger(__name__ + '.DeleteSecurityGroupRule') + + def get_parser(self, prog_name): + parser = super(DeleteSecurityGroupRule, self).get_parser(prog_name) + parser.add_argument( + 'rule', + metavar='<rule>', + help='Security group rule to delete (ID only)', + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + + compute_client = self.app.client_manager.compute + compute_client.security_group_rules.delete(parsed_args.rule) + return + + class ListSecurityGroup(lister.Lister): - """List all security groups""" + """List security groups""" log = logging.getLogger(__name__ + ".ListSecurityGroup") @@ -161,7 +241,7 @@ class ListSecurityGroup(lister.Lister): project_hash = {} try: projects = self.app.client_manager.identity.projects.list() - except ksc_exc.ClientException: + except ks_exc.ClientException: # This fails when the user is not an admin, just move along pass else: @@ -175,6 +255,49 @@ class ListSecurityGroup(lister.Lister): ) for s in data)) +class ListSecurityGroupRule(lister.Lister): + """List security group rules""" + + log = logging.getLogger(__name__ + ".ListSecurityGroupRule") + + def get_parser(self, prog_name): + parser = super(ListSecurityGroupRule, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar='<group>', + help='List all rules in this security group (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + + compute_client = self.app.client_manager.compute + group = utils.find_resource( + compute_client.security_groups, + parsed_args.group, + ) + + # Argh, the rules are not Resources... + rules = [] + for rule in group.rules: + rules.append(security_group_rules.SecurityGroupRule( + compute_client.security_group_rules, + _xform_security_group_rule(rule), + )) + + columns = column_headers = ( + "ID", + "IP Protocol", + "IP Range", + "Port Range", + ) + return (column_headers, + (utils.get_item_properties( + s, columns, + ) for s in rules)) + + class SetSecurityGroup(show.ShowOne): """Set security group properties""" @@ -185,7 +308,7 @@ class SetSecurityGroup(show.ShowOne): parser.add_argument( 'group', metavar='<group>', - help='Name or ID of security group to change', + help='Security group to modify (name or ID)', ) parser.add_argument( '--name', @@ -195,7 +318,7 @@ class SetSecurityGroup(show.ShowOne): parser.add_argument( "--description", metavar="<description>", - help="New security group name", + help="New security group description", ) return parser @@ -227,7 +350,7 @@ class SetSecurityGroup(show.ShowOne): class ShowSecurityGroup(show.ShowOne): - """Show a specific security group""" + """Display security group details""" log = logging.getLogger(__name__ + '.ShowSecurityGroup') @@ -236,7 +359,7 @@ class ShowSecurityGroup(show.ShowOne): parser.add_argument( 'group', metavar='<group>', - help='Name or ID of security group to change', + help='Security group to display (name or ID)', ) return parser @@ -263,126 +386,3 @@ class ShowSecurityGroup(show.ShowOne): ) return zip(*sorted(six.iteritems(info))) - - -class CreateSecurityGroupRule(show.ShowOne): - """Create a new security group rule""" - - log = logging.getLogger(__name__ + ".CreateSecurityGroupRule") - - def get_parser(self, prog_name): - parser = super(CreateSecurityGroupRule, self).get_parser(prog_name) - parser.add_argument( - 'group', - metavar='<group>', - help='Create rule in this security group', - ) - parser.add_argument( - "--proto", - metavar="<proto>", - default="tcp", - help="IP protocol (icmp, tcp, udp; default: tcp)", - ) - parser.add_argument( - "--src-ip", - metavar="<ip-address>", - default="0.0.0.0/0", - help="Source IP (may use CIDR notation; default: 0.0.0.0/0)", - ) - parser.add_argument( - "--dst-port", - metavar="<port-range>", - default=(0, 0), - action=parseractions.RangeAction, - help="Destination port, may be a range: 137:139 (default: 0; " - "only required for proto tcp and udp)", - ) - return parser - - def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - - compute_client = self.app.client_manager.compute - group = utils.find_resource( - compute_client.security_groups, - parsed_args.group, - ) - if parsed_args.proto.lower() == 'icmp': - from_port, to_port = -1, -1 - else: - from_port, to_port = parsed_args.dst_port - data = compute_client.security_group_rules.create( - group.id, - parsed_args.proto, - from_port, - to_port, - parsed_args.src_ip, - ) - - info = _xform_security_group_rule(data._info) - return zip(*sorted(six.iteritems(info))) - - -class DeleteSecurityGroupRule(command.Command): - """Delete a security group rule""" - - log = logging.getLogger(__name__ + '.DeleteSecurityGroupRule') - - def get_parser(self, prog_name): - parser = super(DeleteSecurityGroupRule, self).get_parser(prog_name) - parser.add_argument( - 'rule', - metavar='<rule>', - help='Security group rule ID to delete', - ) - return parser - - @utils.log_method(log) - def take_action(self, parsed_args): - - compute_client = self.app.client_manager.compute - compute_client.security_group_rules.delete(parsed_args.rule) - return - - -class ListSecurityGroupRule(lister.Lister): - """List all security group rules""" - - log = logging.getLogger(__name__ + ".ListSecurityGroupRule") - - def get_parser(self, prog_name): - parser = super(ListSecurityGroupRule, self).get_parser(prog_name) - parser.add_argument( - 'group', - metavar='<group>', - help='List all rules in this security group', - ) - return parser - - def take_action(self, parsed_args): - self.log.debug("take_action(%s)", parsed_args) - - compute_client = self.app.client_manager.compute - group = utils.find_resource( - compute_client.security_groups, - parsed_args.group, - ) - - # Argh, the rules are not Resources... - rules = [] - for rule in group.rules: - rules.append(security_group_rules.SecurityGroupRule( - compute_client.security_group_rules, - _xform_security_group_rule(rule), - )) - - columns = column_headers = ( - "ID", - "IP Protocol", - "IP Range", - "Port Range", - ) - return (column_headers, - (utils.get_item_properties( - s, columns, - ) for s in rules)) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 661ce847..d58ebacd 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -43,7 +43,7 @@ from openstackclient.network import common def _format_servers_list_networks(networks): """Return a formatted string of a server's networks - :param server: a Server.networks field + :param networks: a Server.networks field :rtype: a string of formatted network addresses """ output = [] @@ -56,6 +56,29 @@ def _format_servers_list_networks(networks): return '; '.join(output) +def _format_servers_list_power_state(state): + """Return a formatted string of a server's power state + + :param state: the power state number of a server + :rtype: a string mapped to the power state number + """ + power_states = [ + 'NOSTATE', # 0x00 + 'Running', # 0x01 + '', # 0x02 + 'Paused', # 0x03 + 'Shutdown', # 0x04 + '', # 0x05 + 'Crashed', # 0x06 + 'Suspended' # 0x07 + ] + + try: + return power_states[state] + except Exception: + return 'N/A' + + def _get_ip_address(addresses, address_type, ip_address_family): # Old style addresses if address_type in addresses: @@ -425,10 +448,17 @@ class CreateServer(show.ShowOne): dev_key, dev_vol = dev_map.split('=', 1) block_volume = None if dev_vol: - block_volume = utils.find_resource( - volume_client.volumes, - dev_vol, - ).id + vol = dev_vol.split(':', 1)[0] + if vol: + vol_id = utils.find_resource( + volume_client.volumes, + vol, + ).id + block_volume = dev_vol.replace(vol, vol_id) + else: + msg = _("Volume name or ID must be specified if " + "--block-device-mapping is specified") + raise exceptions.CommandError(msg) block_device_mapping.update({dev_key: block_volume}) nics = [] @@ -607,7 +637,7 @@ class DeleteServer(command.Command): def get_parser(self, prog_name): parser = super(DeleteServer, self).get_parser(prog_name) parser.add_argument( - 'servers', + 'server', metavar='<server>', nargs="+", help=_('Server(s) to delete (name or ID)'), @@ -622,7 +652,7 @@ class DeleteServer(command.Command): @utils.log_method(log) def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - for server in parsed_args.servers: + for server in parsed_args.server: server_obj = utils.find_resource( compute_client.servers, server) compute_client.servers.delete(server_obj.id) @@ -682,12 +712,12 @@ class ListServer(lister.Lister): parser.add_argument( '--flavor', metavar='<flavor>', - help=_('Search by flavor'), + help=_('Search by flavor (name or ID)'), ) parser.add_argument( '--image', metavar='<image>', - help=_('Search by image'), + help=_('Search by image (name or ID)'), ) parser.add_argument( '--host', @@ -717,6 +747,24 @@ class ListServer(lister.Lister): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '--marker', + metavar='<marker>', + default=None, + help=('The last server (name or ID) of the previous page. Display' + ' list of servers after marker. Display all servers if not' + ' specified.') + ) + parser.add_argument( + '--limit', + metavar='<limit>', + type=int, + default=None, + help=("Maximum number of servers to display. If limit equals -1," + " all servers will be displayed. If limit is greater than" + " 'osapi_max_limit' option of Nova API," + " 'osapi_max_limit' will be used instead."), + ) return parser @utils.log_method(log) @@ -735,12 +783,26 @@ class ListServer(lister.Lister): user_id = None if parsed_args.user: - user_id = identity_common.find_project( + user_id = identity_common.find_user( identity_client, parsed_args.user, parsed_args.user_domain, ).id + # Nova only supports list servers searching by flavor ID. So if a + # flavor name is given, map it to ID. + flavor_id = None + if parsed_args.flavor: + flavor_id = utils.find_resource(compute_client.flavors, + parsed_args.flavor).id + + # Nova only supports list servers searching by image ID. So if a + # image name is given, map it to ID. + image_id = None + if parsed_args.image: + image_id = utils.find_resource(compute_client.images, + parsed_args.image).id + search_opts = { 'reservation_id': parsed_args.reservation_id, 'ip': parsed_args.ip, @@ -748,8 +810,8 @@ class ListServer(lister.Lister): 'name': parsed_args.name, 'instance_name': parsed_args.instance_name, 'status': parsed_args.status, - 'flavor': parsed_args.flavor, - 'image': parsed_args.image, + 'flavor': flavor_id, + 'image': image_id, 'host': parsed_args.host, 'tenant_id': project_id, 'all_tenants': parsed_args.all_projects, @@ -762,6 +824,8 @@ class ListServer(lister.Lister): 'ID', 'Name', 'Status', + 'OS-EXT-STS:task_state', + 'OS-EXT-STS:power_state', 'Networks', 'OS-EXT-AZ:availability_zone', 'OS-EXT-SRV-ATTR:host', @@ -771,25 +835,49 @@ class ListServer(lister.Lister): 'ID', 'Name', 'Status', + 'Task State', + 'Power State', 'Networks', 'Availability Zone', 'Host', 'Properties', ) mixed_case_fields = [ + 'OS-EXT-STS:task_state', + 'OS-EXT-STS:power_state', 'OS-EXT-AZ:availability_zone', 'OS-EXT-SRV-ATTR:host', ] else: - columns = ('ID', 'Name', 'Status', 'Networks') - column_headers = columns + columns = ( + 'ID', + 'Name', + 'Status', + 'Networks', + ) + column_headers = ( + 'ID', + 'Name', + 'Status', + 'Networks', + ) mixed_case_fields = [] - data = compute_client.servers.list(search_opts=search_opts) + + marker_id = None + if parsed_args.marker: + marker_id = utils.find_resource(compute_client.servers, + parsed_args.marker).id + + data = compute_client.servers.list(search_opts=search_opts, + marker=marker_id, + limit=parsed_args.limit) return (column_headers, (utils.get_item_properties( s, columns, mixed_case_fields=mixed_case_fields, formatters={ + 'OS-EXT-STS:power_state': + _format_servers_list_power_state, 'Networks': _format_servers_list_networks, 'Metadata': utils.format_dict, }, @@ -798,7 +886,7 @@ class ListServer(lister.Lister): class LockServer(command.Command): - """Lock a server. A non-admin user will not be able to execute actions""" + """Lock server(s). A non-admin user will not be able to execute actions""" log = logging.getLogger(__name__ + '.LockServer') @@ -807,7 +895,8 @@ class LockServer(command.Command): parser.add_argument( 'server', metavar='<server>', - help=_('Server (name or ID)'), + nargs='+', + help=_('Server(s) to lock (name or ID)'), ) return parser @@ -815,10 +904,11 @@ class LockServer(command.Command): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - utils.find_resource( - compute_client.servers, - parsed_args.server, - ).lock() + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).lock() # FIXME(dtroyer): Here is what I want, how with argparse/cliff? @@ -915,7 +1005,7 @@ class MigrateServer(command.Command): class PauseServer(command.Command): - """Pause server""" + """Pause server(s)""" log = logging.getLogger(__name__ + '.PauseServer') @@ -924,18 +1014,19 @@ class PauseServer(command.Command): parser.add_argument( 'server', metavar='<server>', - help=_('Server (name or ID)'), + nargs='+', + help=_('Server(s) to pause (name or ID)'), ) return parser @utils.log_method(log) def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - utils.find_resource( - compute_client.servers, - parsed_args.server, - ).pause() + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server + ).pause() class RebootServer(command.Command): @@ -1217,7 +1308,7 @@ class ResizeServer(command.Command): class ResumeServer(command.Command): - """Resume server""" + """Resume server(s)""" log = logging.getLogger(__name__ + '.ResumeServer') @@ -1226,7 +1317,8 @@ class ResumeServer(command.Command): parser.add_argument( 'server', metavar='<server>', - help=_('Server (name or ID)'), + nargs='+', + help=_('Server(s) to resume (name or ID)'), ) return parser @@ -1234,10 +1326,11 @@ class ResumeServer(command.Command): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - utils.find_resource( - compute_client.servers, - parsed_args.server, - ) .resume() + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).resume() class SetServer(command.Command): @@ -1299,6 +1392,31 @@ class SetServer(command.Command): raise exceptions.CommandError(msg) +class ShelveServer(command.Command): + """Shelve server(s)""" + + log = logging.getLogger(__name__ + '.ShelveServer') + + def get_parser(self, prog_name): + parser = super(ShelveServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + nargs='+', + help=_('Server(s) to shelve (name or ID)'), + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).shelve() + + class ShowServer(show.ShowOne): """Show server details""" @@ -1483,8 +1601,58 @@ class SshServer(command.Command): os.system(cmd % (login, ip_address)) +class StartServer(command.Command): + """Start server(s).""" + + log = logging.getLogger(__name__ + '.StartServer') + + def get_parser(self, prog_name): + parser = super(StartServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + nargs="+", + help=_('Server(s) to start (name or ID)'), + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).start() + + +class StopServer(command.Command): + """Stop server(s).""" + + log = logging.getLogger(__name__ + '.StopServer') + + def get_parser(self, prog_name): + parser = super(StopServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + nargs="+", + help=_('Server(s) to stop (name or ID)'), + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).stop() + + class SuspendServer(command.Command): - """Suspend server""" + """Suspend server(s)""" log = logging.getLogger(__name__ + '.SuspendServer') @@ -1493,7 +1661,8 @@ class SuspendServer(command.Command): parser.add_argument( 'server', metavar='<server>', - help=_('Server (name or ID)'), + nargs='+', + help=_('Server(s) to suspend (name or ID)'), ) return parser @@ -1501,14 +1670,15 @@ class SuspendServer(command.Command): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - utils.find_resource( - compute_client.servers, - parsed_args.server, - ).suspend() + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).suspend() class UnlockServer(command.Command): - """Unlock server""" + """Unlock server(s)""" log = logging.getLogger(__name__ + '.UnlockServer') @@ -1517,7 +1687,8 @@ class UnlockServer(command.Command): parser.add_argument( 'server', metavar='<server>', - help=_('Server (name or ID)'), + nargs='+', + help=_('Server(s) to unlock (name or ID)'), ) return parser @@ -1525,14 +1696,15 @@ class UnlockServer(command.Command): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - utils.find_resource( - compute_client.servers, - parsed_args.server, - ).unlock() + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).unlock() class UnpauseServer(command.Command): - """Unpause server""" + """Unpause server(s)""" log = logging.getLogger(__name__ + '.UnpauseServer') @@ -1541,7 +1713,8 @@ class UnpauseServer(command.Command): parser.add_argument( 'server', metavar='<server>', - help=_('Server (name or ID)'), + nargs='+', + help=_('Server(s) to unpause (name or ID)'), ) return parser @@ -1549,10 +1722,11 @@ class UnpauseServer(command.Command): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - utils.find_resource( - compute_client.servers, - parsed_args.server, - ).unpause() + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).unpause() class UnrescueServer(command.Command): @@ -1614,3 +1788,28 @@ class UnsetServer(command.Command): server, parsed_args.property, ) + + +class UnshelveServer(command.Command): + """Unshelve server(s)""" + + log = logging.getLogger(__name__ + '.UnshelveServer') + + def get_parser(self, prog_name): + parser = super(UnshelveServer, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + nargs='+', + help=_('Server(s) to unshelve (name or ID)'), + ) + return parser + + @utils.log_method(log) + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + for server in parsed_args.server: + utils.find_resource( + compute_client.servers, + server, + ).unshelve() diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 3a031bff..c2d51c2a 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -17,11 +17,33 @@ import logging +from cliff import command from cliff import lister from openstackclient.common import utils +class DeleteService(command.Command): + """Delete service command""" + + log = logging.getLogger(__name__ + ".DeleteService") + + def get_parser(self, prog_name): + parser = super(DeleteService, self).get_parser(prog_name) + parser.add_argument( + "service", + metavar="<service>", + help="Compute service to delete (ID only)") + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)", parsed_args) + compute_client = self.app.client_manager.compute + + compute_client.services.delete(parsed_args.service) + return + + class ListService(lister.Lister): """List service command""" diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index 31b90c22..4e7cf100 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -183,25 +183,16 @@ class ShowUsage(show.ShowOne): )) info = {} - info['Servers'] = len(usage.server_usages) - info['RAM MB-Hours'] = float("%.2f" % usage.total_memory_mb_usage) - info['CPU Hours'] = float("%.2f" % usage.total_vcpus_usage) - info['Disk GB-Hours'] = float("%.2f" % usage.total_local_gb_usage) + info['Servers'] = ( + len(usage.server_usages) + if hasattr(usage, "server_usages") else None) + info['RAM MB-Hours'] = ( + float("%.2f" % usage.total_memory_mb_usage) + if hasattr(usage, "total_memory_mb_usage") else None) + info['CPU Hours'] = ( + float("%.2f" % usage.total_vcpus_usage) + if hasattr(usage, "total_vcpus_usage") else None) + info['Disk GB-Hours'] = ( + float("%.2f" % usage.total_local_gb_usage) + if hasattr(usage, "total_local_gb_usage") else None) return zip(*sorted(six.iteritems(info))) - - -# This is out of order due to the subclass, will eventually be removed - -class ListProjectUsage(ListUsage): - """List resource usage per project""" - - deprecated = True - - log = logging.getLogger('DEPRECATED:') - - def take_action(self, parsed_args): - self.log.warning( - "%s is deprecated, use 'usage list'", - getattr(self, 'cmd_name', 'this command'), - ) - return super(ListProjectUsage, self).take_action(parsed_args) diff --git a/openstackclient/identity/client.py b/openstackclient/identity/client.py index b8bb33f4..bd882ce8 100644 --- a/openstackclient/identity/client.py +++ b/openstackclient/identity/client.py @@ -21,7 +21,7 @@ from openstackclient.common import utils LOG = logging.getLogger(__name__) -DEFAULT_API_VERSION = '2' +DEFAULT_API_VERSION = '3' API_VERSION_OPTION = 'os_identity_api_version' API_NAME = 'identity' API_VERSIONS = { diff --git a/openstackclient/identity/v2_0/project.py b/openstackclient/identity/v2_0/project.py index 97a95f28..065f0adf 100644 --- a/openstackclient/identity/v2_0/project.py +++ b/openstackclient/identity/v2_0/project.py @@ -21,7 +21,7 @@ import six from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import parseractions from openstackclient.common import utils @@ -88,7 +88,7 @@ class CreateProject(show.ShowOne): enabled=enabled, **kwargs ) - except ksc_exc.Conflict as e: + except ks_exc.Conflict as e: if parsed_args.or_show: project = utils.find_resource( identity_client.tenants, @@ -264,7 +264,7 @@ class ShowProject(show.ShowOne): parsed_args.project, ) info.update(project._info) - except ksc_exc.Forbidden as e: + except ks_exc.Forbidden as e: auth_ref = self.app.client_manager.auth_ref if ( parsed_args.project == auth_ref.project_id or diff --git a/openstackclient/identity/v2_0/role.py b/openstackclient/identity/v2_0/role.py index e98f8cb3..cab6b4a5 100644 --- a/openstackclient/identity/v2_0/role.py +++ b/openstackclient/identity/v2_0/role.py @@ -21,7 +21,7 @@ import six from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import exceptions from openstackclient.common import utils @@ -98,7 +98,7 @@ class CreateRole(show.ShowOne): identity_client = self.app.client_manager.identity try: role = identity_client.roles.create(parsed_args.role_name) - except ksc_exc.Conflict as e: + except ks_exc.Conflict as e: if parsed_args.or_show: role = utils.find_resource( identity_client.roles, diff --git a/openstackclient/identity/v2_0/user.py b/openstackclient/identity/v2_0/user.py index 76902e69..e2b285bd 100644 --- a/openstackclient/identity/v2_0/user.py +++ b/openstackclient/identity/v2_0/user.py @@ -21,7 +21,7 @@ import six from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -104,7 +104,7 @@ class CreateUser(show.ShowOne): tenant_id=project_id, enabled=enabled, ) - except ksc_exc.Conflict as e: + except ks_exc.Conflict as e: if parsed_args.or_show: user = utils.find_resource( identity_client.users, @@ -373,7 +373,7 @@ class ShowUser(show.ShowOne): parsed_args.user, ) info.update(user._info) - except ksc_exc.Forbidden as e: + except ks_exc.Forbidden as e: auth_ref = self.app.client_manager.auth_ref if ( parsed_args.user == auth_ref.user_id or diff --git a/openstackclient/identity/v3/credential.py b/openstackclient/identity/v3/credential.py index dbd73e2e..f22092d4 100644 --- a/openstackclient/identity/v3/credential.py +++ b/openstackclient/identity/v3/credential.py @@ -17,7 +17,6 @@ import logging import six -import sys from cliff import command from cliff import lister @@ -130,17 +129,20 @@ class SetCredential(command.Command): parser.add_argument( '--user', metavar='<user>', + required=True, help='Name or ID of user that owns the credential', ) parser.add_argument( '--type', metavar='<type>', choices=['ec2', 'cert'], + required=True, help='New credential type', ) parser.add_argument( '--data', metavar='<data>', + required=True, help='New credential data', ) parser.add_argument( @@ -153,25 +155,22 @@ class SetCredential(command.Command): @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - kwargs = {} - if parsed_args.user: - user_id = utils.find_resource(identity_client.users, - parsed_args.user).id - if user_id: - kwargs['user'] = user_id - if parsed_args.type: - kwargs['type'] = parsed_args.type - if parsed_args.data: - kwargs['data'] = parsed_args.data + + user_id = utils.find_resource(identity_client.users, + parsed_args.user).id + if parsed_args.project: project = utils.find_resource(identity_client.projects, parsed_args.project).id - kwargs['project'] = project + else: + project = None + + identity_client.credentials.update(parsed_args.credential, + user=user_id, + type=parsed_args.type, + blob=parsed_args.data, + project=project) - if not kwargs: - sys.stdout.write("Credential not updated, no arguments present") - return - identity_client.credentials.update(parsed_args.credential, **kwargs) return diff --git a/openstackclient/identity/v3/domain.py b/openstackclient/identity/v3/domain.py index aec530a8..8278a300 100644 --- a/openstackclient/identity/v3/domain.py +++ b/openstackclient/identity/v3/domain.py @@ -22,7 +22,7 @@ import sys from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -77,7 +77,7 @@ class CreateDomain(show.ShowOne): description=parsed_args.description, enabled=enabled, ) - except ksc_exc.Conflict as e: + except ks_exc.Conflict as e: if parsed_args.or_show: domain = utils.find_resource(identity_client.domains, parsed_args.name) diff --git a/openstackclient/identity/v3/group.py b/openstackclient/identity/v3/group.py index 82975065..d503a6b8 100644 --- a/openstackclient/identity/v3/group.py +++ b/openstackclient/identity/v3/group.py @@ -22,7 +22,7 @@ import sys from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -156,7 +156,7 @@ class CreateGroup(show.ShowOne): name=parsed_args.name, domain=domain, description=parsed_args.description) - except ksc_exc.Conflict as e: + except ks_exc.Conflict as e: if parsed_args.or_show: group = utils.find_resource(identity_client.groups, parsed_args.name, diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index 27982a9d..4e086b68 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -88,6 +88,8 @@ class CreateIdentityProvider(show.ShowOne): enabled=parsed_args.enabled) idp._info.pop('links', None) + remote_ids = utils.format_list(idp._info.pop('remote_ids', [])) + idp._info['remote_ids'] = remote_ids return zip(*sorted(six.iteritems(idp._info))) @@ -142,6 +144,11 @@ class SetIdentityProvider(command.Command): metavar='<identity-provider>', help='Identity provider to modify', ) + parser.add_argument( + '--description', + metavar='<description>', + help='Set identity provider description', + ) identity_remote_id_provider = parser.add_mutually_exclusive_group() identity_remote_id_provider.add_argument( '--remote-id', @@ -174,8 +181,10 @@ class SetIdentityProvider(command.Command): federation_client = self.app.client_manager.identity.federation # Basic argument checking - if (not parsed_args.enable and not parsed_args.disable and not - parsed_args.remote_id and not parsed_args.remote_id_file): + if (not parsed_args.enable and not parsed_args.disable and + not parsed_args.remote_id and + not parsed_args.remote_id_file and + not parsed_args.description): self.log.error('No changes requested') return (None, None) @@ -190,6 +199,8 @@ class SetIdentityProvider(command.Command): # Setup keyword args for the client kwargs = {} + if parsed_args.description: + kwargs['description'] = parsed_args.description if parsed_args.enable: kwargs['enabled'] = True if parsed_args.disable: @@ -221,9 +232,11 @@ class ShowIdentityProvider(show.ShowOne): @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - identity_provider = utils.find_resource( + idp = utils.find_resource( identity_client.federation.identity_providers, parsed_args.identity_provider) - identity_provider._info.pop('links', None) - return zip(*sorted(six.iteritems(identity_provider._info))) + idp._info.pop('links', None) + remote_ids = utils.format_list(idp._info.pop('remote_ids', [])) + idp._info['remote_ids'] = remote_ids + return zip(*sorted(six.iteritems(idp._info))) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 96d7f97b..f87105dd 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -21,7 +21,7 @@ import six from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import parseractions from openstackclient.common import utils @@ -113,7 +113,7 @@ class CreateProject(show.ShowOne): enabled=enabled, **kwargs ) - except ksc_exc.Conflict as e: + except ks_exc.Conflict as e: if parsed_args.or_show: project = utils.find_resource(identity_client.projects, parsed_args.name, diff --git a/openstackclient/identity/v3/region.py b/openstackclient/identity/v3/region.py index eb4c084c..1ff0b8c0 100644 --- a/openstackclient/identity/v3/region.py +++ b/openstackclient/identity/v3/region.py @@ -48,12 +48,6 @@ class CreateRegion(show.ShowOne): metavar='<description>', help=_('New region description'), ) - parser.add_argument( - '--url', - metavar='<url>', - help=_('New region url'), - ) - return parser @utils.log_method(log) @@ -62,7 +56,6 @@ class CreateRegion(show.ShowOne): region = identity_client.regions.create( id=parsed_args.region, - url=parsed_args.url, parent_region=parsed_args.parent_region, description=parsed_args.description, ) @@ -117,8 +110,8 @@ class ListRegion(lister.Lister): if parsed_args.parent_region: kwargs['parent_region_id'] = parsed_args.parent_region - columns_headers = ('Region', 'Parent Region', 'Description', 'URL') - columns = ('ID', 'Parent Region Id', 'Description', 'URL') + columns_headers = ('Region', 'Parent Region', 'Description') + columns = ('ID', 'Parent Region Id', 'Description') data = identity_client.regions.list(**kwargs) return (columns_headers, @@ -150,25 +143,16 @@ class SetRegion(command.Command): metavar='<description>', help=_('New region description'), ) - parser.add_argument( - '--url', - metavar='<url>', - help=_('New region url'), - ) return parser @utils.log_method(log) def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - if (not parsed_args.url - and not parsed_args.parent_region - and not parsed_args.description): + if not parsed_args.parent_region and not parsed_args.description: return kwargs = {} - if parsed_args.url: - kwargs['url'] = parsed_args.url if parsed_args.description: kwargs['description'] = parsed_args.description if parsed_args.parent_region: diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 0e8c51ca..66f18964 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -22,7 +22,7 @@ import sys from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -172,7 +172,7 @@ class CreateRole(show.ShowOne): try: role = identity_client.roles.create(name=parsed_args.name) - except ksc_exc.Conflict as e: + except ks_exc.Conflict as e: if parsed_args.or_show: role = utils.find_resource(identity_client.roles, parsed_args.name) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 0e894544..eaef8f05 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -22,7 +22,7 @@ import six from cliff import command from cliff import lister from cliff import show -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import utils from openstackclient.i18n import _ # noqa @@ -122,7 +122,7 @@ class CreateUser(show.ShowOne): description=parsed_args.description, enabled=enabled ) - except ksc_exc.Conflict as e: + except ks_exc.Conflict as e: if parsed_args.or_show: user = utils.find_resource(identity_client.users, parsed_args.name, diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 35e9ef43..4ebc8f93 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -354,7 +354,12 @@ class ListImage(lister.Lister): kwargs['public'] = True if parsed_args.private: kwargs['private'] = True - kwargs['detailed'] = bool(parsed_args.property or parsed_args.long) + # Note: We specifically need to do that below to get the 'status' + # column. + # + # Always set kwargs['detailed'] to True, and then filter the columns + # according to whether the --long option is specified or not. + kwargs['detailed'] = True if parsed_args.long: columns = ( @@ -382,7 +387,7 @@ class ListImage(lister.Lister): 'Properties', ) else: - columns = ("ID", "Name") + columns = ("ID", "Name", "Status") column_headers = columns # List of image data received diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 2b3f3bfd..a3c1a99d 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -105,21 +105,20 @@ class AddProjectToImage(show.ShowOne): project_id, ) - return zip(*sorted(six.iteritems(image_member._info))) + return zip(*sorted(six.iteritems(image_member))) class CreateImage(show.ShowOne): """Create/upload an image""" log = logging.getLogger(__name__ + ".CreateImage") - deadopts = ('owner', 'size', 'location', 'copy-from', 'checksum', 'store') + deadopts = ('size', 'location', 'copy-from', 'checksum', 'store') def get_parser(self, prog_name): parser = super(CreateImage, self).get_parser(prog_name) # TODO(bunting): There are additional arguments that v1 supported # that v2 either doesn't support or supports weirdly. # --checksum - could be faked clientside perhaps? - # --owner - could be set as an update after the put? # --location - maybe location add? # --size - passing image size is actually broken in python-glanceclient # --copy-from - does not exist in v2 @@ -149,6 +148,11 @@ class CreateImage(show.ShowOne): "(default: %s)" % DEFAULT_DISK_FORMAT, ) parser.add_argument( + "--owner", + metavar="<owner>", + help="Image owner project name or ID", + ) + parser.add_argument( "--min-disk", metavar="<disk-gb>", type=int, @@ -241,7 +245,7 @@ class CreateImage(show.ShowOne): copy_attrs = ('name', 'id', 'container_format', 'disk_format', 'min_disk', 'min_ram', - 'tags') + 'tags', 'owner') for attr in copy_attrs: if attr in parsed_args: val = getattr(parsed_args, attr, None) @@ -449,7 +453,7 @@ class ListImage(lister.Lister): 'Tags', ) else: - columns = ("ID", "Name") + columns = ("ID", "Name", "Status") column_headers = columns # List of image data received diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index 5f72782b..69ed11fe 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -18,10 +18,11 @@ from openstackclient.common import utils LOG = logging.getLogger(__name__) -DEFAULT_API_VERSION = '2' +DEFAULT_API_VERSION = '2.0' API_VERSION_OPTION = 'os_network_api_version' API_NAME = "network" API_VERSIONS = { + "2.0": "neutronclient.v2_0.client.Client", "2": "neutronclient.v2_0.client.Client", } # Translate our API version to auth plugin version prefix @@ -32,6 +33,7 @@ API_VERSION_MAP = { NETWORK_API_TYPE = 'network' NETWORK_API_VERSIONS = { + '2.0': 'openstackclient.api.network_v2.APIv2', '2': 'openstackclient.api.network_v2.APIv2', } diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py index bd6203bd..31faef25 100644 --- a/openstackclient/network/common.py +++ b/openstackclient/network/common.py @@ -27,7 +27,8 @@ def find(client, resource, resources, name_or_id, name_attr='name'): n = find(netclient, 'network', 'networks', 'matrix') """ list_method = getattr(client, "list_%s" % resources) - # Search for by name + + # Search by name kwargs = {name_attr: name_or_id, 'fields': 'id'} data = list_method(**kwargs) info = data[resources] @@ -36,7 +37,8 @@ def find(client, resource, resources, name_or_id, name_attr='name'): if len(info) > 1: msg = "More than one %s exists with the name '%s'." raise exceptions.CommandError(msg % (resource, name_or_id)) - # Search for by id + + # Search by id data = list_method(id=name_or_id, fields='id') info = data[resources] if len(info) == 1: diff --git a/openstackclient/object/v1/account.py b/openstackclient/object/v1/account.py index 4ff890ce..aa94ff5c 100644 --- a/openstackclient/object/v1/account.py +++ b/openstackclient/object/v1/account.py @@ -55,6 +55,8 @@ class ShowAccount(show.ShowOne): @utils.log_method(log) def take_action(self, parsed_args): data = self.app.client_manager.object_store.account_show() + if 'properties' in data: + data['properties'] = utils.format_dict(data.pop('properties')) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/object/v1/container.py b/openstackclient/object/v1/container.py index b8eb4c25..8c8844e2 100644 --- a/openstackclient/object/v1/container.py +++ b/openstackclient/object/v1/container.py @@ -229,6 +229,8 @@ class ShowContainer(show.ShowOne): data = self.app.client_manager.object_store.container_show( container=parsed_args.container, ) + if 'properties' in data: + data['properties'] = utils.format_dict(data.pop('properties')) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/object/v1/object.py b/openstackclient/object/v1/object.py index a023e3a0..4bd06124 100644 --- a/openstackclient/object/v1/object.py +++ b/openstackclient/object/v1/object.py @@ -284,6 +284,8 @@ class ShowObject(show.ShowOne): container=parsed_args.container, object=parsed_args.object, ) + if 'properties' in data: + data['properties'] = utils.format_dict(data.pop('properties')) return zip(*sorted(six.iteritems(data))) diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 5b36b8b2..5b4939a2 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -25,12 +25,13 @@ from cliff import app from cliff import command from cliff import complete from cliff import help +from oslo_utils import strutils import openstackclient from openstackclient.common import clientmanager from openstackclient.common import commandmanager -from openstackclient.common import context from openstackclient.common import exceptions as exc +from openstackclient.common import logs from openstackclient.common import timing from openstackclient.common import utils @@ -102,7 +103,7 @@ class OpenStackShell(app.App): def configure_logging(self): """Configure logging for the app.""" - self.log_configurator = context.LogConfigurator(self.options) + self.log_configurator = logs.LogConfigurator(self.options) self.dump_stack_trace = self.log_configurator.dump_trace def run(self, argv): @@ -201,8 +202,10 @@ class OpenStackShell(app.App): # Parent __init__ parses argv into self.options super(OpenStackShell, self).initialize_app(argv) - self.log.info("START with options: %s", self.command_options) - self.log.debug("options: %s", self.options) + self.log.info("START with options: %s", + strutils.mask_password(self.command_options)) + self.log.debug("options: %s", + strutils.mask_password(self.options)) # Set the default plugin to token_endpoint if url and token are given if (self.options.url and self.options.token): @@ -238,6 +241,10 @@ class OpenStackShell(app.App): }, ) + # TODO(thowe): Change cliff so the default value for debug + # can be set to None. + if not self.options.debug: + self.options.debug = None self.cloud = cc.get_one_cloud( cloud=self.options.cloud, argparse=self.options, @@ -246,7 +253,8 @@ class OpenStackShell(app.App): self.log_configurator.configure(self.cloud) self.dump_stack_trace = self.log_configurator.dump_trace self.log.debug("defaults: %s", cc.defaults) - self.log.debug("cloud cfg: %s", self.cloud.config) + self.log.debug("cloud cfg: %s", + strutils.mask_password(self.cloud.config)) # Set up client TLS # NOTE(dtroyer): --insecure is the non-default condition that @@ -272,7 +280,7 @@ class OpenStackShell(app.App): for mod in clientmanager.PLUGIN_MODULES: default_version = getattr(mod, 'DEFAULT_API_VERSION', None) option = mod.API_VERSION_OPTION.replace('os_', '') - version_opt = self.cloud.config.get(option, default_version) + version_opt = str(self.cloud.config.get(option, default_version)) if version_opt: api = mod.API_NAME self.api_version[api] = version_opt diff --git a/openstackclient/tests/api/fakes.py b/openstackclient/tests/api/fakes.py index 85617ab7..e285a61c 100644 --- a/openstackclient/tests/api/fakes.py +++ b/openstackclient/tests/api/fakes.py @@ -15,7 +15,7 @@ from requests_mock.contrib import fixture -from keystoneclient import session +from keystoneauth1 import session from openstackclient.tests import utils diff --git a/openstackclient/tests/api/test_image_v1.py b/openstackclient/tests/api/test_image_v1.py index 34fcfca4..f3479756 100644 --- a/openstackclient/tests/api/test_image_v1.py +++ b/openstackclient/tests/api/test_image_v1.py @@ -15,7 +15,7 @@ from requests_mock.contrib import fixture -from keystoneclient import session +from keystoneauth1 import session from openstackclient.api import image_v1 from openstackclient.tests import utils diff --git a/openstackclient/tests/api/test_image_v2.py b/openstackclient/tests/api/test_image_v2.py index ddb160ee..77063997 100644 --- a/openstackclient/tests/api/test_image_v2.py +++ b/openstackclient/tests/api/test_image_v2.py @@ -15,7 +15,7 @@ from requests_mock.contrib import fixture -from keystoneclient import session +from keystoneauth1 import session from openstackclient.api import image_v2 from openstackclient.tests import utils diff --git a/openstackclient/tests/api/test_network_v2.py b/openstackclient/tests/api/test_network_v2.py index 13c5d6ea..80f1d9de 100644 --- a/openstackclient/tests/api/test_network_v2.py +++ b/openstackclient/tests/api/test_network_v2.py @@ -15,7 +15,7 @@ from requests_mock.contrib import fixture -from keystoneclient import session +from keystoneauth1 import session from openstackclient.api import network_v2 as network from openstackclient.tests import utils diff --git a/openstackclient/tests/api/test_object_store_v1.py b/openstackclient/tests/api/test_object_store_v1.py index 323bb8e0..992bf261 100644 --- a/openstackclient/tests/api/test_object_store_v1.py +++ b/openstackclient/tests/api/test_object_store_v1.py @@ -17,7 +17,7 @@ import mock from requests_mock.contrib import fixture -from keystoneclient import session +from keystoneauth1 import session from openstackclient.api import object_store_v1 as object_store from openstackclient.tests import utils @@ -157,11 +157,6 @@ class TestContainer(TestObjectAPIv1): 'container': 'qaz', 'object_count': '1', 'bytes_used': '577', - 'meta-owner': FAKE_ACCOUNT, - 'read_acl': None, - 'write_acl': None, - 'sync_to': None, - 'sync_key': None, } self.requests_mock.register_uri( 'HEAD', @@ -323,10 +318,8 @@ class TestObject(TestObjectAPIv1): 'content-type': 'text/alpha', 'content-length': '577', 'last-modified': '20130101', - 'meta-owner': FAKE_ACCOUNT, 'etag': 'qaz', - 'wife': 'Wilma', - 'x-tra-header': 'yabba-dabba-do', + 'properties': {'wife': 'Wilma'}, } self.requests_mock.register_uri( 'HEAD', diff --git a/openstackclient/tests/common/test_context.py b/openstackclient/tests/common/test_logs.py index 55e42851..fe054a3b 100644 --- a/openstackclient/tests/common/test_context.py +++ b/openstackclient/tests/common/test_logs.py @@ -14,7 +14,7 @@ import logging import mock -from openstackclient.common import context +from openstackclient.common import logs from openstackclient.tests import utils @@ -23,51 +23,51 @@ class TestContext(utils.TestCase): def test_log_level_from_options(self): opts = mock.Mock() opts.verbose_level = 0 - self.assertEqual(logging.ERROR, context.log_level_from_options(opts)) + self.assertEqual(logging.ERROR, logs.log_level_from_options(opts)) opts.verbose_level = 1 - self.assertEqual(logging.WARNING, context.log_level_from_options(opts)) + self.assertEqual(logging.WARNING, logs.log_level_from_options(opts)) opts.verbose_level = 2 - self.assertEqual(logging.INFO, context.log_level_from_options(opts)) + self.assertEqual(logging.INFO, logs.log_level_from_options(opts)) opts.verbose_level = 3 - self.assertEqual(logging.DEBUG, context.log_level_from_options(opts)) + self.assertEqual(logging.DEBUG, logs.log_level_from_options(opts)) def test_log_level_from_config(self): cfg = {'verbose_level': 0} - self.assertEqual(logging.ERROR, context.log_level_from_config(cfg)) + self.assertEqual(logging.ERROR, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1} - self.assertEqual(logging.WARNING, context.log_level_from_config(cfg)) + self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 2} - self.assertEqual(logging.INFO, context.log_level_from_config(cfg)) + self.assertEqual(logging.INFO, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 3} - self.assertEqual(logging.DEBUG, context.log_level_from_config(cfg)) + self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'critical'} - self.assertEqual(logging.CRITICAL, context.log_level_from_config(cfg)) + self.assertEqual(logging.CRITICAL, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'error'} - self.assertEqual(logging.ERROR, context.log_level_from_config(cfg)) + self.assertEqual(logging.ERROR, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'warning'} - self.assertEqual(logging.WARNING, context.log_level_from_config(cfg)) + self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'info'} - self.assertEqual(logging.INFO, context.log_level_from_config(cfg)) + self.assertEqual(logging.INFO, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'debug'} - self.assertEqual(logging.DEBUG, context.log_level_from_config(cfg)) + self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'bogus'} - self.assertEqual(logging.WARNING, context.log_level_from_config(cfg)) + self.assertEqual(logging.WARNING, logs.log_level_from_config(cfg)) cfg = {'verbose_level': 1, 'log_level': 'info', 'debug': True} - self.assertEqual(logging.DEBUG, context.log_level_from_config(cfg)) + self.assertEqual(logging.DEBUG, logs.log_level_from_config(cfg)) @mock.patch('warnings.simplefilter') def test_set_warning_filter(self, simplefilter): - context.set_warning_filter(logging.ERROR) + logs.set_warning_filter(logging.ERROR) simplefilter.assert_called_with("ignore") - context.set_warning_filter(logging.WARNING) + logs.set_warning_filter(logging.WARNING) simplefilter.assert_called_with("ignore") - context.set_warning_filter(logging.INFO) + logs.set_warning_filter(logging.INFO) simplefilter.assert_called_with("once") class TestFileFormatter(utils.TestCase): def test_nothing(self): - formatter = context._FileFormatter() + formatter = logs._FileFormatter() self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' '%(name)s %(message)s'), formatter.fmt) @@ -77,7 +77,7 @@ class TestFileFormatter(utils.TestCase): os_project_name = 'projecty' username = 'usernamey' options = Opts() - formatter = context._FileFormatter(options=options) + formatter = logs._FileFormatter(options=options) self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' '%(name)s [cloudy usernamey projecty] %(message)s'), formatter.fmt) @@ -86,7 +86,7 @@ class TestFileFormatter(utils.TestCase): config = mock.Mock() config.config = {'cloud': 'cloudy'} config.auth = {'project_name': 'projecty', 'username': 'usernamey'} - formatter = context._FileFormatter(config=config) + formatter = logs._FileFormatter(config=config) self.assertEqual(('%(asctime)s.%(msecs)03d %(process)d %(levelname)s ' '%(name)s [cloudy usernamey projecty] %(message)s'), formatter.fmt) @@ -119,7 +119,7 @@ class TestLogConfigurator(utils.TestCase): @mock.patch('logging.StreamHandler') @mock.patch('logging.getLogger') - @mock.patch('openstackclient.common.context.set_warning_filter') + @mock.patch('openstackclient.common.logs.set_warning_filter') def test_init(self, warning_filter, getLogger, handle): getLogger.side_effect = self.loggers console_logger = mock.Mock() @@ -127,7 +127,7 @@ class TestLogConfigurator(utils.TestCase): console_logger.setLevel = mock.Mock() handle.return_value = console_logger - configurator = context.LogConfigurator(self.options) + configurator = logs.LogConfigurator(self.options) getLogger.assert_called_with('iso8601') # last call warning_filter.assert_called_with(logging.WARNING) @@ -140,12 +140,12 @@ class TestLogConfigurator(utils.TestCase): self.assertEqual(False, configurator.dump_trace) @mock.patch('logging.getLogger') - @mock.patch('openstackclient.common.context.set_warning_filter') + @mock.patch('openstackclient.common.logs.set_warning_filter') def test_init_no_debug(self, warning_filter, getLogger): getLogger.side_effect = self.loggers self.options.debug = True - configurator = context.LogConfigurator(self.options) + configurator = logs.LogConfigurator(self.options) warning_filter.assert_called_with(logging.DEBUG) self.requests_log.setLevel.assert_called_with(logging.DEBUG) @@ -153,8 +153,8 @@ class TestLogConfigurator(utils.TestCase): @mock.patch('logging.FileHandler') @mock.patch('logging.getLogger') - @mock.patch('openstackclient.common.context.set_warning_filter') - @mock.patch('openstackclient.common.context._FileFormatter') + @mock.patch('openstackclient.common.logs.set_warning_filter') + @mock.patch('openstackclient.common.logs._FileFormatter') def test_init_log_file(self, formatter, warning_filter, getLogger, handle): getLogger.side_effect = self.loggers self.options.log_file = '/tmp/log_file' @@ -165,7 +165,7 @@ class TestLogConfigurator(utils.TestCase): mock_formatter = mock.Mock() formatter.return_value = mock_formatter - context.LogConfigurator(self.options) + logs.LogConfigurator(self.options) handle.assert_called_with(filename=self.options.log_file) self.root_logger.addHandler.assert_called_with(file_logger) @@ -174,11 +174,11 @@ class TestLogConfigurator(utils.TestCase): @mock.patch('logging.FileHandler') @mock.patch('logging.getLogger') - @mock.patch('openstackclient.common.context.set_warning_filter') - @mock.patch('openstackclient.common.context._FileFormatter') + @mock.patch('openstackclient.common.logs.set_warning_filter') + @mock.patch('openstackclient.common.logs._FileFormatter') def test_configure(self, formatter, warning_filter, getLogger, handle): getLogger.side_effect = self.loggers - configurator = context.LogConfigurator(self.options) + configurator = logs.LogConfigurator(self.options) cloud_config = mock.Mock() config_log = '/tmp/config_log' cloud_config.config = { diff --git a/openstackclient/tests/common/test_parseractions.py b/openstackclient/tests/common/test_parseractions.py index 8afcb632..b75c4814 100644 --- a/openstackclient/tests/common/test_parseractions.py +++ b/openstackclient/tests/common/test_parseractions.py @@ -102,3 +102,58 @@ class TestKeyValueAction(utils.TestCase): expect = {'green': '100%'} self.assertDictEqual(expect, actual) self.assertEqual(None, failhere) + + +class TestNonNegativeAction(utils.TestCase): + def test_negative_values(self): + parser = argparse.ArgumentParser() + + # Set up our typical usage + parser.add_argument( + '--foo', + metavar='<foo>', + type=int, + action=parseractions.NonNegativeAction, + ) + + self.assertRaises( + argparse.ArgumentTypeError, + parser.parse_args, + "--foo -1".split() + ) + + def test_zero_values(self): + parser = argparse.ArgumentParser() + + # Set up our typical usage + parser.add_argument( + '--foo', + metavar='<foo>', + type=int, + action=parseractions.NonNegativeAction, + ) + + results = parser.parse_args( + '--foo 0'.split() + ) + + actual = getattr(results, 'foo', None) + self.assertEqual(actual, 0) + + def test_positive_values(self): + parser = argparse.ArgumentParser() + + # Set up our typical usage + parser.add_argument( + '--foo', + metavar='<foo>', + type=int, + action=parseractions.NonNegativeAction, + ) + + results = parser.parse_args( + '--foo 1'.split() + ) + + actual = getattr(results, 'foo', None) + self.assertEqual(actual, 1) diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py index b6ad1566..485b8a8b 100644 --- a/openstackclient/tests/common/test_quota.py +++ b/openstackclient/tests/common/test_quota.py @@ -17,6 +17,8 @@ import mock from openstackclient.common import quota from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes +from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.network.v2 import fakes as network_fakes class FakeQuotaResource(fakes.FakeResource): @@ -40,11 +42,23 @@ class TestQuota(compute_fakes.TestComputev2): super(TestQuota, self).setUp() self.quotas_mock = self.app.client_manager.compute.quotas self.quotas_mock.reset_mock() + self.quotas_class_mock = self.app.client_manager.compute.quota_classes + self.quotas_class_mock.reset_mock() volume_mock = mock.Mock() volume_mock.quotas = mock.Mock() self.app.client_manager.volume = volume_mock self.volume_quotas_mock = volume_mock.quotas self.volume_quotas_mock.reset_mock() + self.volume_quotas_class_mock = \ + self.app.client_manager.volume.quota_classes + self.volume_quotas_class_mock.reset_mock() + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + self.app.client_manager.auth_ref = mock.Mock() + self.app.client_manager.auth_ref.service_catalog = mock.Mock() + self.service_catalog_mock = \ + self.app.client_manager.auth_ref.service_catalog + self.service_catalog_mock.reset_mock() class TestQuotaSet(TestQuota): @@ -76,6 +90,12 @@ class TestQuotaSet(TestQuota): loaded=True, ) + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + self.cmd = quota.SetQuota(self.app, None) def test_quota_set(self): @@ -83,15 +103,32 @@ class TestQuotaSet(TestQuota): '--floating-ips', str(compute_fakes.floating_ip_num), '--fixed-ips', str(compute_fakes.fix_ip_num), '--injected-files', str(compute_fakes.injected_file_num), + '--injected-file-size', str(compute_fakes.injected_file_size_num), + '--injected-path-size', str(compute_fakes.injected_path_size_num), '--key-pairs', str(compute_fakes.key_pair_num), - compute_fakes.project_name, + '--cores', str(compute_fakes.core_num), + '--ram', str(compute_fakes.ram_num), + '--instances', str(compute_fakes.instance_num), + '--properties', str(compute_fakes.property_num), + '--secgroup-rules', str(compute_fakes.secgroup_rule_num), + '--secgroups', str(compute_fakes.secgroup_num), + identity_fakes.project_name, ] verifylist = [ ('floating_ips', compute_fakes.floating_ip_num), ('fixed_ips', compute_fakes.fix_ip_num), ('injected_files', compute_fakes.injected_file_num), + ('injected_file_content_bytes', + compute_fakes.injected_file_size_num), + ('injected_file_path_bytes', compute_fakes.injected_path_size_num), ('key_pairs', compute_fakes.key_pair_num), - ('project', compute_fakes.project_name), + ('cores', compute_fakes.core_num), + ('ram', compute_fakes.ram_num), + ('instances', compute_fakes.instance_num), + ('metadata_items', compute_fakes.property_num), + ('security_group_rules', compute_fakes.secgroup_rule_num), + ('security_groups', compute_fakes.secgroup_num), + ('project', identity_fakes.project_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -102,17 +139,29 @@ class TestQuotaSet(TestQuota): 'floating_ips': compute_fakes.floating_ip_num, 'fixed_ips': compute_fakes.fix_ip_num, 'injected_files': compute_fakes.injected_file_num, + 'injected_file_content_bytes': + compute_fakes.injected_file_size_num, + 'injected_file_path_bytes': compute_fakes.injected_path_size_num, 'key_pairs': compute_fakes.key_pair_num, + 'cores': compute_fakes.core_num, + 'ram': compute_fakes.ram_num, + 'instances': compute_fakes.instance_num, + 'metadata_items': compute_fakes.property_num, + 'security_group_rules': compute_fakes.secgroup_rule_num, + 'security_groups': compute_fakes.secgroup_num, } - self.quotas_mock.update.assert_called_with('project_test', **kwargs) + self.quotas_mock.update.assert_called_with( + identity_fakes.project_id, + **kwargs + ) def test_quota_set_volume(self): arglist = [ '--gigabytes', str(compute_fakes.floating_ip_num), '--snapshots', str(compute_fakes.fix_ip_num), '--volumes', str(compute_fakes.injected_file_num), - compute_fakes.project_name, + identity_fakes.project_name, ] verifylist = [ ('gigabytes', compute_fakes.floating_ip_num), @@ -130,5 +179,117 @@ class TestQuotaSet(TestQuota): 'volumes': compute_fakes.injected_file_num, } - self.volume_quotas_mock.update.assert_called_with('project_test', - **kwargs) + self.volume_quotas_mock.update.assert_called_with( + identity_fakes.project_id, + **kwargs + ) + + +class TestQuotaShow(TestQuota): + + def setUp(self): + super(TestQuotaShow, self).setUp() + + self.quotas_mock.get.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.quotas_mock.defaults.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.volume_quotas_mock.get.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.volume_quotas_mock.defaults.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.service_catalog_mock.get_endpoints.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.ENDPOINT), + loaded=True, + ) + ] + + self.quotas_class_mock.get.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.volume_quotas_class_mock.get.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.projects_mock.get.return_value = fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ) + + self.app.client_manager.network = network_fakes.FakeNetworkV2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + + self.cmd = quota.ShowQuota(self.app, None) + + def test_quota_show(self): + arglist = [ + identity_fakes.project_name, + ] + verifylist = [ + ('project', identity_fakes.project_name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.quotas_mock.get.assert_called_with(identity_fakes.project_id) + + def test_quota_show_with_default(self): + arglist = [ + '--default', + identity_fakes.project_name, + ] + verifylist = [ + ('default', True), + ('project', identity_fakes.project_name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.quotas_mock.defaults.assert_called_with(identity_fakes.project_id) + + def test_quota_show_with_class(self): + arglist = [ + '--class', + identity_fakes.project_name, + ] + verifylist = [ + ('quota_class', True), + ('project', identity_fakes.project_name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.quotas_class_mock.get.assert_called_with( + identity_fakes.project_id) diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py index 373c0de4..b564ffab 100644 --- a/openstackclient/tests/common/test_utils.py +++ b/openstackclient/tests/common/test_utils.py @@ -137,6 +137,46 @@ class TestUtils(test_utils.TestCase): items, sort_str) @mock.patch.object(time, 'sleep') + def test_wait_for_status_ok(self, mock_sleep): + # Tests the normal flow that the resource is status=active + resource = mock.MagicMock(status='ACTIVE') + status_f = mock.Mock(return_value=resource) + res_id = str(uuid.uuid4()) + self.assertTrue(utils.wait_for_status(status_f, res_id,)) + self.assertFalse(mock_sleep.called) + + @mock.patch.object(time, 'sleep') + def test_wait_for_status_ok__with_overrides(self, mock_sleep): + # Tests the normal flow that the resource is status=complete + resource = mock.MagicMock(my_status='COMPLETE') + status_f = mock.Mock(return_value=resource) + res_id = str(uuid.uuid4()) + self.assertTrue(utils.wait_for_status(status_f, res_id, + status_field='my_status', + success_status=['complete'])) + self.assertFalse(mock_sleep.called) + + @mock.patch.object(time, 'sleep') + def test_wait_for_status_error(self, mock_sleep): + # Tests that we fail if the resource is status=error + resource = mock.MagicMock(status='ERROR') + status_f = mock.Mock(return_value=resource) + res_id = str(uuid.uuid4()) + self.assertFalse(utils.wait_for_status(status_f, res_id)) + self.assertFalse(mock_sleep.called) + + @mock.patch.object(time, 'sleep') + def test_wait_for_status_error_with_overrides(self, mock_sleep): + # Tests that we fail if the resource is my_status=failed + resource = mock.MagicMock(my_status='FAILED') + status_f = mock.Mock(return_value=resource) + res_id = str(uuid.uuid4()) + self.assertFalse(utils.wait_for_status(status_f, res_id, + status_field='my_status', + error_status=['failed'])) + self.assertFalse(mock_sleep.called) + + @mock.patch.object(time, 'sleep') def test_wait_for_delete_ok(self, mock_sleep): # Tests the normal flow that the resource is deleted with a 404 coming # back on the 2nd iteration of the wait loop. diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index e798bd40..91cc2bd2 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -13,24 +13,18 @@ # under the License. # +import copy import mock +import uuid from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils +from openstackclient.tests.volume.v2 import fakes as volume_fakes -server_id = 'serv1' -server_name = 'waiter' - -SERVER = { - 'id': server_id, - 'name': server_name, - 'metadata': {}, -} - extension_name = 'Multinic' extension_namespace = 'http://docs.openstack.org/compute/ext/'\ 'multinic/api/v1.1' @@ -50,34 +44,49 @@ 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, -} - floating_ip_num = 100 fix_ip_num = 100 injected_file_num = 100 +injected_file_size_num = 10240 +injected_path_size_num = 255 key_pair_num = 100 +core_num = 20 +ram_num = 51200 +instance_num = 10 +property_num = 128 +secgroup_rule_num = 20 +secgroup_num = 10 project_name = 'project_test' QUOTA = { 'project': project_name, 'floating-ips': floating_ip_num, 'fix-ips': fix_ip_num, 'injected-files': injected_file_num, + 'injected-file-size': injected_file_size_num, + 'injected-path-size': injected_path_size_num, 'key-pairs': key_pair_num, + 'cores': core_num, + 'ram': ram_num, + 'instances': instance_num, + 'properties': property_num, + 'secgroup_rules': secgroup_rule_num, + 'secgroups': secgroup_num, } QUOTA_columns = tuple(sorted(QUOTA)) QUOTA_data = tuple(QUOTA[x] for x in sorted(QUOTA)) +block_device_mapping = 'vda=' + volume_fakes.volume_name + ':::0' + +service_host = 'host_test' +service_binary = 'compute_test' +service_status = 'enabled' +SERVICE = { + 'host': service_host, + 'binary': service_binary, + 'status': service_status, +} + class FakeComputev2Client(object): def __init__(self, **kwargs): @@ -85,12 +94,18 @@ class FakeComputev2Client(object): self.images.resource_class = fakes.FakeResource(None, {}) self.servers = mock.Mock() self.servers.resource_class = fakes.FakeResource(None, {}) + self.services = mock.Mock() + self.services.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.quotas = mock.Mock() self.quotas.resource_class = fakes.FakeResource(None, {}) + self.quota_classes = mock.Mock() + self.quota_classes.resource_class = fakes.FakeResource(None, {}) + self.volumes = mock.Mock() + self.volumes.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -118,3 +133,161 @@ class TestComputev2(utils.TestCommand): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + + self.app.client_manager.volume = volume_fakes.FakeVolumeClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + + +class FakeServer(object): + """Fake one or more compute servers.""" + + @staticmethod + def create_one_server(attrs={}, methods={}): + """Create a fake server. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, name, metadata + """ + # Set default attributes. + server_info = { + 'id': 'server-id-' + uuid.uuid4().hex, + 'name': 'server-name-' + uuid.uuid4().hex, + 'metadata': {}, + } + + # Overwrite default attributes. + server_info.update(attrs) + + server = fakes.FakeResource(info=copy.deepcopy(server_info), + methods=methods, + loaded=True) + return server + + @staticmethod + def create_servers(attrs={}, methods={}, count=2): + """Create multiple fake servers. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of servers to fake + :return: + A list of FakeResource objects faking the servers + """ + servers = [] + for i in range(0, count): + servers.append(FakeServer.create_one_server(attrs, methods)) + + return servers + + @staticmethod + def get_servers(servers=None, count=2): + """Get an iterable MagicMock object with a list of faked servers. + + If servers list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List servers: + A list of FakeResource objects faking servers + :param int count: + The number of servers to fake + :return: + An iterable Mock object with side_effect set to a list of faked + servers + """ + if servers is None: + servers = FakeServer.create_servers(count) + return mock.MagicMock(side_effect=servers) + + +class FakeFlavorResource(fakes.FakeResource): + """Fake flavor object's methods to help test. + + The flavor object has three methods to get, set, unset its properties. + Need to fake them, otherwise the functions to be tested won't run properly. + """ + + # Fake properties. + _keys = {'property': 'value'} + + def set_keys(self, args): + self._keys.update(args) + + def unset_keys(self, keys): + for key in keys: + self._keys.pop(key, None) + + def get_keys(self): + return self._keys + + +class FakeFlavor(object): + """Fake one or more flavors.""" + + @staticmethod + def create_one_flavor(attrs={}): + """Create a fake flavor. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeFlavorResource object, with id, name, ram, vcpus, properties + """ + # Set default attributes. + flavor_info = { + 'id': 'flavor-id-' + uuid.uuid4().hex, + 'name': 'flavor-name-' + uuid.uuid4().hex, + 'ram': 8192, + 'vcpus': 4, + } + + # Overwrite default attributes. + flavor_info.update(attrs) + + flavor = FakeFlavorResource(info=copy.deepcopy(flavor_info), + loaded=True) + return flavor + + @staticmethod + def create_flavors(attrs={}, count=2): + """Create multiple fake flavors. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of flavors to fake + :return: + A list of FakeFlavorResource objects faking the flavors + """ + flavors = [] + for i in range(0, count): + flavors.append(FakeFlavor.create_one_flavor(attrs)) + + return flavors + + @staticmethod + def get_flavors(flavors=None, count=2): + """Get an iterable MagicMock object with a list of faked flavors. + + If flavors list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List flavors: + A list of FakeFlavorResource objects faking flavors + :param int count: + The number of flavors to fake + :return: + An iterable Mock object with side_effect set to a list of faked + flavors + """ + if flavors is None: + flavors = FakeServer.create_flavors(count) + return mock.MagicMock(side_effect=flavors) diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 19be8124..60356efa 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -13,26 +13,9 @@ # under the License. # -import copy - +from openstackclient.common import exceptions from openstackclient.compute.v2 import flavor from openstackclient.tests.compute.v2 import fakes as compute_fakes -from openstackclient.tests import fakes - - -class FakeFlavorResource(fakes.FakeResource): - - _keys = {'property': 'value'} - - def set_keys(self, args): - self._keys.update(args) - - def unset_keys(self, keys): - for key in keys: - self._keys.pop(key, None) - - def get_keys(self): - return self._keys class TestFlavor(compute_fakes.TestComputev2): @@ -45,18 +28,90 @@ class TestFlavor(compute_fakes.TestComputev2): self.flavors_mock.reset_mock() +class TestFlavorDelete(TestFlavor): + + flavor = compute_fakes.FakeFlavor.create_one_flavor() + + def setUp(self): + super(TestFlavorDelete, self).setUp() + + self.flavors_mock.get.return_value = self.flavor + self.flavors_mock.delete.return_value = None + + self.cmd = flavor.DeleteFlavor(self.app, None) + + def test_flavor_delete(self): + arglist = [ + self.flavor.id + ] + verifylist = [ + ('flavor', self.flavor.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.flavors_mock.delete.assert_called_with(self.flavor.id) + + def test_flavor_delete_with_unexist_flavor(self): + self.flavors_mock.get.side_effect = exceptions.NotFound(None) + self.flavors_mock.find.side_effect = exceptions.NotFound(None) + + arglist = [ + 'unexist_flavor' + ] + verifylist = [ + ('flavor', 'unexist_flavor'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + class TestFlavorList(TestFlavor): + # Return value of self.flavors_mock.list(). + flavors = compute_fakes.FakeFlavor.create_flavors(count=1) + + columns = ( + 'ID', + 'Name', + 'RAM', + 'Disk', + 'Ephemeral', + 'VCPUs', + 'Is Public', + ) + columns_long = columns + ( + 'Swap', + 'RXTX Factor', + 'Properties' + ) + + data = (( + flavors[0].id, + flavors[0].name, + flavors[0].ram, + '', + '', + flavors[0].vcpus, + '' + ), ) + data_long = (data[0] + ( + '', + '', + 'property=\'value\'' + ), ) + def setUp(self): super(TestFlavorList, self).setUp() - self.flavors_mock.list.return_value = [ - FakeFlavorResource( - None, - copy.deepcopy(compute_fakes.FLAVOR), - loaded=True, - ), - ] + self.flavors_mock.list.return_value = self.flavors # Get the command object to test self.cmd = flavor.ListFlavor(self.app, None) @@ -76,33 +131,17 @@ class TestFlavorList(TestFlavor): # Set expected values kwargs = { - 'is_public': True + 'is_public': True, + 'limit': None, + 'marker': None } self.flavors_mock.list.assert_called_with( **kwargs ) - collist = ( - 'ID', - 'Name', - 'RAM', - 'Disk', - 'Ephemeral', - 'VCPUs', - 'Is Public', - ) - self.assertEqual(collist, columns) - datalist = (( - compute_fakes.flavor_id, - compute_fakes.flavor_name, - compute_fakes.flavor_ram, - '', - '', - compute_fakes.flavor_vcpus, - '' - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) def test_flavor_list_all_flavors(self): arglist = [ @@ -119,33 +158,17 @@ class TestFlavorList(TestFlavor): # Set expected values kwargs = { - 'is_public': None + 'is_public': None, + 'limit': None, + 'marker': None } self.flavors_mock.list.assert_called_with( **kwargs ) - collist = ( - 'ID', - 'Name', - 'RAM', - 'Disk', - 'Ephemeral', - 'VCPUs', - 'Is Public', - ) - self.assertEqual(collist, columns) - datalist = (( - compute_fakes.flavor_id, - compute_fakes.flavor_name, - compute_fakes.flavor_ram, - '', - '', - compute_fakes.flavor_vcpus, - '' - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) def test_flavor_list_private_flavors(self): arglist = [ @@ -162,33 +185,17 @@ class TestFlavorList(TestFlavor): # Set expected values kwargs = { - 'is_public': False + 'is_public': False, + 'limit': None, + 'marker': None } self.flavors_mock.list.assert_called_with( **kwargs ) - collist = ( - 'ID', - 'Name', - 'RAM', - 'Disk', - 'Ephemeral', - 'VCPUs', - 'Is Public', - ) - self.assertEqual(collist, columns) - datalist = (( - compute_fakes.flavor_id, - compute_fakes.flavor_name, - compute_fakes.flavor_ram, - '', - '', - compute_fakes.flavor_vcpus, - '' - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) def test_flavor_list_public_flavors(self): arglist = [ @@ -205,33 +212,17 @@ class TestFlavorList(TestFlavor): # Set expected values kwargs = { - 'is_public': True + 'is_public': True, + 'limit': None, + 'marker': None } self.flavors_mock.list.assert_called_with( **kwargs ) - collist = ( - 'ID', - 'Name', - 'RAM', - 'Disk', - 'Ephemeral', - 'VCPUs', - 'Is Public', - ) - self.assertEqual(collist, columns) - datalist = (( - compute_fakes.flavor_id, - compute_fakes.flavor_name, - compute_fakes.flavor_ram, - '', - '', - compute_fakes.flavor_vcpus, - '' - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) def test_flavor_list_long(self): arglist = [ @@ -248,51 +239,28 @@ class TestFlavorList(TestFlavor): # Set expected values kwargs = { - 'is_public': True + 'is_public': True, + 'limit': None, + 'marker': None } self.flavors_mock.list.assert_called_with( **kwargs ) - collist = ( - 'ID', - 'Name', - 'RAM', - 'Disk', - 'Ephemeral', - 'VCPUs', - 'Is Public', - 'Swap', - 'RXTX Factor', - 'Properties' - ) - self.assertEqual(collist, columns) - datalist = (( - compute_fakes.flavor_id, - compute_fakes.flavor_name, - compute_fakes.flavor_ram, - '', - '', - compute_fakes.flavor_vcpus, - '', - '', - '', - 'property=\'value\'' - ), ) - self.assertEqual(datalist, tuple(data)) + self.assertEqual(self.columns_long, columns) + self.assertEqual(tuple(self.data_long), tuple(data)) class TestFlavorSet(TestFlavor): + # Return value of self.flavors_mock.find(). + flavor = compute_fakes.FakeFlavor.create_one_flavor() + def setUp(self): super(TestFlavorSet, self).setUp() - self.flavors_mock.find.return_value = FakeFlavorResource( - None, - copy.deepcopy(compute_fakes.FLAVOR), - loaded=True, - ) + self.flavors_mock.find.return_value = self.flavor self.cmd = flavor.SetFlavor(self.app, None) @@ -318,14 +286,13 @@ class TestFlavorSet(TestFlavor): class TestFlavorUnset(TestFlavor): + # Return value of self.flavors_mock.find(). + flavor = compute_fakes.FakeFlavor.create_one_flavor() + def setUp(self): super(TestFlavorUnset, self).setUp() - self.flavors_mock.find.return_value = FakeFlavorResource( - None, - copy.deepcopy(compute_fakes.FLAVOR), - loaded=True, - ) + self.flavors_mock.find.return_value = self.flavor self.cmd = flavor.UnsetFlavor(self.app, None) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 1e99bcd0..359920f2 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -17,6 +17,7 @@ import copy import mock import testtools +from mock import call from openstackclient.common import exceptions from openstackclient.common import utils as common_utils from openstackclient.compute.v2 import server @@ -24,6 +25,7 @@ from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests import utils +from openstackclient.tests.volume.v2 import fakes as volume_fakes class TestServer(compute_fakes.TestComputev2): @@ -47,24 +49,63 @@ class TestServer(compute_fakes.TestComputev2): self.images_mock = self.app.client_manager.image.images self.images_mock.reset_mock() + # Get a shortcut to the VolumeManager Mock + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + + # Set object attributes to be tested. Could be overwriten in subclass. + self.attrs = {} + + # Set object methods to be tested. Could be overwriten in subclass. + self.methods = {} + + def setup_servers_mock(self, count): + servers = compute_fakes.FakeServer.create_servers(attrs=self.attrs, + methods=self.methods, + count=count) + + # This is the return value for utils.find_resource() + self.servers_mock.get = compute_fakes.FakeServer.get_servers(servers, + 0) + return servers + + def run_method_with_servers(self, method_name, server_count): + servers = self.setup_servers_mock(server_count) + + arglist = [] + verifylist = [] + + for s in servers: + arglist.append(s.id) + verifylist = [ + ('server', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + for s in servers: + method = getattr(s, method_name) + method.assert_called_with() + class TestServerCreate(TestServer): def setUp(self): super(TestServerCreate, self).setUp() - self.servers_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(compute_fakes.SERVER), - loaded=True, - ) - new_server = fakes.FakeResource( - None, - copy.deepcopy(compute_fakes.SERVER), - loaded=True, - ) - new_server.__dict__['networks'] = {} - self.servers_mock.get.return_value = new_server + attrs = { + 'networks': {}, + } + self.new_server = compute_fakes.FakeServer.create_one_server( + attrs=attrs) + + # This is the return value for utils.find_resource(). + # This is for testing --wait option. + self.servers_mock.get.return_value = self.new_server + + self.servers_mock.create.return_value = self.new_server self.image = fakes.FakeResource( None, @@ -73,22 +114,25 @@ class TestServerCreate(TestServer): ) self.cimages_mock.get.return_value = self.image - self.flavor = fakes.FakeResource( + self.flavor = compute_fakes.FakeFlavor.create_one_flavor() + self.flavors_mock.get.return_value = self.flavor + + self.volume = fakes.FakeResource( None, - copy.deepcopy(compute_fakes.FLAVOR), + copy.deepcopy(volume_fakes.VOLUME), loaded=True, ) - self.flavors_mock.get.return_value = self.flavor + self.volumes_mock.get.return_value = self.volume # Get the command object to test self.cmd = server.CreateServer(self.app, None) def test_server_create_no_options(self): arglist = [ - compute_fakes.server_id, + self.new_server.name, ] verifylist = [ - ('server_name', compute_fakes.server_id), + ('server_name', self.new_server.name), ] try: # Missing required args should bail here @@ -100,13 +144,13 @@ class TestServerCreate(TestServer): arglist = [ '--image', 'image1', '--flavor', 'flavor1', - compute_fakes.server_id, + self.new_server.name, ] verifylist = [ ('image', 'image1'), ('flavor', 'flavor1'), ('config_drive', False), - ('server_name', compute_fakes.server_id), + ('server_name', self.new_server.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -131,19 +175,27 @@ class TestServerCreate(TestServer): ) # ServerManager.create(name, image, flavor, **kwargs) self.servers_mock.create.assert_called_with( - compute_fakes.server_id, + self.new_server.name, self.image, self.flavor, **kwargs ) - collist = ('addresses', 'flavor', 'id', 'name', 'properties') + collist = ( + 'addresses', + 'flavor', + 'id', + 'name', + 'networks', + 'properties', + ) self.assertEqual(collist, columns) datalist = ( '', - 'Large ()', - compute_fakes.server_id, - compute_fakes.server_name, + self.flavor.name + ' ()', + self.new_server.id, + self.new_server.name, + self.new_server.networks, '', ) self.assertEqual(datalist, data) @@ -154,14 +206,14 @@ class TestServerCreate(TestServer): '--flavor', 'flavor1', '--nic', 'net-id=net1', '--nic', 'port-id=port1', - compute_fakes.server_id, + self.new_server.name, ] verifylist = [ ('image', 'image1'), ('flavor', 'flavor1'), ('nic', ['net-id=net1', 'port-id=port1']), ('config_drive', False), - ('server_name', compute_fakes.server_id), + ('server_name', self.new_server.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -207,19 +259,27 @@ class TestServerCreate(TestServer): ) # ServerManager.create(name, image, flavor, **kwargs) self.servers_mock.create.assert_called_with( - compute_fakes.server_id, + self.new_server.name, self.image, self.flavor, **kwargs ) - collist = ('addresses', 'flavor', 'id', 'name', 'properties') + collist = ( + 'addresses', + 'flavor', + 'id', + 'name', + 'networks', + 'properties', + ) self.assertEqual(collist, columns) datalist = ( '', - 'Large ()', - compute_fakes.server_id, - compute_fakes.server_name, + self.flavor.name + ' ()', + self.new_server.id, + self.new_server.name, + self.new_server.networks, '', ) self.assertEqual(datalist, data) @@ -234,14 +294,14 @@ class TestServerCreate(TestServer): '--image', 'image1', '--flavor', 'flavor1', '--user-data', 'userdata.sh', - compute_fakes.server_id, + self.new_server.name, ] verifylist = [ ('image', 'image1'), ('flavor', 'flavor1'), ('user_data', 'userdata.sh'), ('config_drive', False), - ('server_name', compute_fakes.server_id), + ('server_name', self.new_server.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -272,19 +332,96 @@ class TestServerCreate(TestServer): ) # ServerManager.create(name, image, flavor, **kwargs) self.servers_mock.create.assert_called_with( - compute_fakes.server_id, + self.new_server.name, self.image, self.flavor, **kwargs ) - collist = ('addresses', 'flavor', 'id', 'name', 'properties') + collist = ( + 'addresses', + 'flavor', + 'id', + 'name', + 'networks', + 'properties', + ) self.assertEqual(collist, columns) datalist = ( '', - 'Large ()', - compute_fakes.server_id, - compute_fakes.server_name, + self.flavor.name + ' ()', + self.new_server.id, + self.new_server.name, + self.new_server.networks, + '', + ) + self.assertEqual(datalist, data) + + def test_server_create_with_block_device_mapping(self): + arglist = [ + '--image', 'image1', + '--flavor', self.flavor.id, + '--block-device-mapping', compute_fakes.block_device_mapping, + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', self.flavor.id), + ('block_device_mapping', [compute_fakes.block_device_mapping]), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # CreateServer.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + real_volume_mapping = ( + (compute_fakes.block_device_mapping.split('=', 1)[1]).replace( + volume_fakes.volume_name, + volume_fakes.volume_id)) + + # Set expected values + kwargs = dict( + meta=None, + files={}, + reservation_id=None, + min_count=1, + max_count=1, + security_groups=[], + userdata=None, + key_name=None, + availability_zone=None, + block_device_mapping={ + 'vda': real_volume_mapping + }, + nics=[], + scheduler_hints={}, + config_drive=None, + ) + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + collist = ( + 'addresses', + 'flavor', + 'id', + 'name', + 'networks', + 'properties', + ) + self.assertEqual(collist, columns) + datalist = ( + '', + self.flavor.name + ' ()', + self.new_server.id, + self.new_server.name, + self.new_server.networks, '', ) self.assertEqual(datalist, data) @@ -295,23 +432,19 @@ class TestServerDelete(TestServer): def setUp(self): super(TestServerDelete, self).setUp() - # This is the return value for utils.find_resource() - self.servers_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(compute_fakes.SERVER), - loaded=True, - ) self.servers_mock.delete.return_value = None # Get the command object to test self.cmd = server.DeleteServer(self.app, None) def test_server_delete_no_options(self): + servers = self.setup_servers_mock(count=1) + arglist = [ - compute_fakes.server_id, + servers[0].id, ] verifylist = [ - ('servers', [compute_fakes.server_id]), + ('server', [servers[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -319,16 +452,39 @@ class TestServerDelete(TestServer): self.cmd.take_action(parsed_args) self.servers_mock.delete.assert_called_with( - compute_fakes.server_id, + servers[0].id, ) + def test_server_delete_multi_servers(self): + servers = self.setup_servers_mock(count=3) + + arglist = [] + verifylist = [] + + for s in servers: + arglist.append(s.id) + verifylist = [ + ('server', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + calls = [] + for s in servers: + calls.append(call(s.id)) + self.servers_mock.delete.assert_has_calls(calls) + @mock.patch.object(common_utils, 'wait_for_delete', return_value=True) def test_server_delete_wait_ok(self, mock_wait_for_delete): + servers = self.setup_servers_mock(count=1) + arglist = [ - compute_fakes.server_id, '--wait' + servers[0].id, '--wait' ] verifylist = [ - ('servers', [compute_fakes.server_id]), + ('server', [servers[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -336,22 +492,24 @@ class TestServerDelete(TestServer): self.cmd.take_action(parsed_args) self.servers_mock.delete.assert_called_with( - compute_fakes.server_id, + servers[0].id, ) mock_wait_for_delete.assert_called_once_with( self.servers_mock, - compute_fakes.server_id, + servers[0].id, callback=server._show_progress ) @mock.patch.object(common_utils, 'wait_for_delete', return_value=False) def test_server_delete_wait_fails(self, mock_wait_for_delete): + servers = self.setup_servers_mock(count=1) + arglist = [ - compute_fakes.server_id, '--wait' + servers[0].id, '--wait' ] verifylist = [ - ('servers', [compute_fakes.server_id]), + ('server', [servers[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -359,12 +517,12 @@ class TestServerDelete(TestServer): self.assertRaises(SystemExit, self.cmd.take_action, parsed_args) self.servers_mock.delete.assert_called_with( - compute_fakes.server_id, + servers[0].id, ) mock_wait_for_delete.assert_called_once_with( self.servers_mock, - compute_fakes.server_id, + servers[0].id, callback=server._show_progress ) @@ -374,12 +532,10 @@ class TestServerImageCreate(TestServer): def setUp(self): super(TestServerImageCreate, self).setUp() + self.server = compute_fakes.FakeServer.create_one_server() + # This is the return value for utils.find_resource() - self.servers_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(compute_fakes.SERVER), - loaded=True, - ) + self.servers_mock.get.return_value = self.server self.servers_mock.create_image.return_value = image_fakes.image_id @@ -394,10 +550,10 @@ class TestServerImageCreate(TestServer): def test_server_image_create_no_options(self): arglist = [ - compute_fakes.server_id, + self.server.id, ] verifylist = [ - ('server', compute_fakes.server_id), + ('server', self.server.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -407,7 +563,7 @@ class TestServerImageCreate(TestServer): # ServerManager.create_image(server, image_name, metadata=) self.servers_mock.create_image.assert_called_with( self.servers_mock.get.return_value, - compute_fakes.server_name, + self.server.name, ) collist = ('id', 'name', 'owner', 'protected', 'tags', 'visibility') @@ -425,11 +581,11 @@ class TestServerImageCreate(TestServer): def test_server_image_create_name(self): arglist = [ '--name', 'img-nam', - compute_fakes.server_id, + self.server.id, ] verifylist = [ ('name', 'img-nam'), - ('server', compute_fakes.server_id), + ('server', self.server.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -455,29 +611,144 @@ class TestServerImageCreate(TestServer): self.assertEqual(datalist, data) +class TestServerList(TestServer): + + # Columns to be listed up. + columns = ( + 'ID', + 'Name', + 'Status', + 'Networks', + ) + + # Data returned by corresponding Nova API. The elements in this list are + # tuples filled with server attributes. + data = [] + + # Default search options, in the case of no commandline option specified. + search_opts = { + 'reservation_id': None, + 'ip': None, + 'ip6': None, + 'name': None, + 'instance_name': None, + 'status': None, + 'flavor': None, + 'image': None, + 'host': None, + 'tenant_id': None, + 'all_tenants': False, + 'user_id': None, + } + + # Default params of the core function of the command in the case of no + # commandline option specified. + kwargs = { + 'search_opts': search_opts, + 'marker': None, + 'limit': None, + } + + def setUp(self): + super(TestServerList, self).setUp() + + # The fake servers' attributes. + self.attrs = { + 'status': 'ACTIVE', + 'networks': { + u'public': [u'10.20.30.40', u'2001:db8::5'] + }, + } + + # The servers to be listed. + self.servers = self.setup_servers_mock(3) + + self.servers_mock.list.return_value = self.servers + + # Get the command object to test + self.cmd = server.ListServer(self.app, None) + + # Prepare data returned by fake Nova API. + for s in self.servers: + self.data.append(( + s.id, + s.name, + s.status, + u'public=10.20.30.40, 2001:db8::5', + )) + + def test_server_list_no_option(self): + arglist = [] + verifylist = [ + ('all_projects', False), + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.servers_mock.list.assert_called_with(**self.kwargs) + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + +class TestServerLock(TestServer): + + def setUp(self): + super(TestServerLock, self).setUp() + + # Get the command object to test + self.cmd = server.LockServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'lock': None, + } + + def test_server_lock_one_server(self): + self.run_method_with_servers('lock', 1) + + def test_server_lock_multi_servers(self): + self.run_method_with_servers('lock', 3) + + +class TestServerPause(TestServer): + + def setUp(self): + super(TestServerPause, self).setUp() + + # Get the command object to test + self.cmd = server.PauseServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'pause': None, + } + + def test_server_pause_one_server(self): + self.run_method_with_servers('pause', 1) + + def test_server_pause_multi_servers(self): + self.run_method_with_servers('pause', 3) + + class TestServerResize(TestServer): def setUp(self): super(TestServerResize, self).setUp() + self.server = compute_fakes.FakeServer.create_one_server() + # 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.get.return_value = self.server 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_get_return_value = \ + compute_fakes.FakeFlavor.create_one_flavor() self.flavors_mock.get.return_value = self.flavors_get_return_value # Get the command object to test @@ -485,12 +756,12 @@ class TestServerResize(TestServer): def test_server_resize_no_options(self): arglist = [ - compute_fakes.server_id, + self.server.id, ] verifylist = [ ('confirm', False), ('revert', False), - ('server', compute_fakes.server_id), + ('server', self.server.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -498,7 +769,7 @@ class TestServerResize(TestServer): self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with( - compute_fakes.server_id, + self.server.id, ) self.assertNotCalled(self.servers_mock.resize) @@ -507,14 +778,14 @@ class TestServerResize(TestServer): def test_server_resize(self): arglist = [ - '--flavor', compute_fakes.flavor_id, - compute_fakes.server_id, + '--flavor', self.flavors_get_return_value.id, + self.server.id, ] verifylist = [ - ('flavor', compute_fakes.flavor_id), + ('flavor', self.flavors_get_return_value.id), ('confirm', False), ('revert', False), - ('server', compute_fakes.server_id), + ('server', self.server.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -522,14 +793,14 @@ class TestServerResize(TestServer): self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with( - compute_fakes.server_id, + self.server.id, ) self.flavors_mock.get.assert_called_with( - compute_fakes.flavor_id, + self.flavors_get_return_value.id, ) self.servers_mock.resize.assert_called_with( - self.servers_get_return_value, + self.server, self.flavors_get_return_value, ) self.assertNotCalled(self.servers_mock.confirm_resize) @@ -538,12 +809,12 @@ class TestServerResize(TestServer): def test_server_resize_confirm(self): arglist = [ '--confirm', - compute_fakes.server_id, + self.server.id, ] verifylist = [ ('confirm', True), ('revert', False), - ('server', compute_fakes.server_id), + ('server', self.server.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -551,24 +822,24 @@ class TestServerResize(TestServer): self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with( - compute_fakes.server_id, + self.server.id, ) self.assertNotCalled(self.servers_mock.resize) self.servers_mock.confirm_resize.assert_called_with( - self.servers_get_return_value, + self.server, ) self.assertNotCalled(self.servers_mock.revert_resize) def test_server_resize_revert(self): arglist = [ '--revert', - compute_fakes.server_id, + self.server.id, ] verifylist = [ ('confirm', False), ('revert', True), - ('server', compute_fakes.server_id), + ('server', self.server.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -576,16 +847,176 @@ class TestServerResize(TestServer): self.cmd.take_action(parsed_args) self.servers_mock.get.assert_called_with( - compute_fakes.server_id, + self.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, + self.server, ) +class TestServerResume(TestServer): + + def setUp(self): + super(TestServerResume, self).setUp() + + # Get the command object to test + self.cmd = server.ResumeServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'resume': None, + } + + def test_server_resume_one_server(self): + self.run_method_with_servers('resume', 1) + + def test_server_resume_multi_servers(self): + self.run_method_with_servers('resume', 3) + + +class TestServerShelve(TestServer): + + def setUp(self): + super(TestServerShelve, self).setUp() + + # Get the command object to test + self.cmd = server.ShelveServer(self.app, None) + + # Set shelve method to be tested. + self.methods = { + 'shelve': None, + } + + def test_shelve_one_server(self): + self.run_method_with_servers('shelve', 1) + + def test_shelve_multi_servers(self): + self.run_method_with_servers('shelve', 3) + + +class TestServerStart(TestServer): + + def setUp(self): + super(TestServerStart, self).setUp() + + # Get the command object to test + self.cmd = server.StartServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'start': None, + } + + def test_server_start_one_server(self): + self.run_method_with_servers('start', 1) + + def test_server_start_multi_servers(self): + self.run_method_with_servers('start', 3) + + +class TestServerStop(TestServer): + + def setUp(self): + super(TestServerStop, self).setUp() + + # Get the command object to test + self.cmd = server.StopServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'stop': None, + } + + def test_server_stop_one_server(self): + self.run_method_with_servers('stop', 1) + + def test_server_stop_multi_servers(self): + self.run_method_with_servers('stop', 3) + + +class TestServerSuspend(TestServer): + + def setUp(self): + super(TestServerSuspend, self).setUp() + + # Get the command object to test + self.cmd = server.SuspendServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'suspend': None, + } + + def test_server_suspend_one_server(self): + self.run_method_with_servers('suspend', 1) + + def test_server_suspend_multi_servers(self): + self.run_method_with_servers('suspend', 3) + + +class TestServerUnlock(TestServer): + + def setUp(self): + super(TestServerUnlock, self).setUp() + + # Get the command object to test + self.cmd = server.UnlockServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'unlock': None, + } + + def test_server_unlock_one_server(self): + self.run_method_with_servers('unlock', 1) + + def test_server_unlock_multi_servers(self): + self.run_method_with_servers('unlock', 3) + + +class TestServerUnpause(TestServer): + + def setUp(self): + super(TestServerUnpause, self).setUp() + + # Get the command object to test + self.cmd = server.UnpauseServer(self.app, None) + + # Set methods to be tested. + self.methods = { + 'unpause': None, + } + + def test_server_unpause_one_server(self): + self.run_method_with_servers('unpause', 1) + + def test_server_unpause_multi_servers(self): + self.run_method_with_servers('unpause', 3) + + +class TestServerUnshelve(TestServer): + + def setUp(self): + super(TestServerUnshelve, self).setUp() + + # Get the command object to test + self.cmd = server.UnshelveServer(self.app, None) + + # Set unshelve method to be tested. + self.methods = { + 'unshelve': None, + } + + def test_unshelve_one_server(self): + self.run_method_with_servers('unshelve', 1) + + def test_unshelve_multi_servers(self): + self.run_method_with_servers('unshelve', 3) + + class TestServerGeneral(testtools.TestCase): OLD = { 'private': [ diff --git a/openstackclient/tests/compute/v2/test_service.py b/openstackclient/tests/compute/v2/test_service.py new file mode 100644 index 00000000..380fbc4f --- /dev/null +++ b/openstackclient/tests/compute/v2/test_service.py @@ -0,0 +1,151 @@ +# Copyright 2015 Mirantis, 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. +# + +import copy + +from openstackclient.compute.v2 import service +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import fakes + + +class TestService(compute_fakes.TestComputev2): + + def setUp(self): + super(TestService, self).setUp() + + # Get a shortcut to the ServiceManager Mock + self.service_mock = self.app.client_manager.compute.services + self.service_mock.reset_mock() + + +class TestServiceDelete(TestService): + + def setUp(self): + super(TestServiceDelete, self).setUp() + + self.service_mock.delete.return_value = None + + # Get the command object to test + self.cmd = service.DeleteService(self.app, None) + + def test_service_delete_no_options(self): + arglist = [ + compute_fakes.service_binary, + ] + verifylist = [ + ('service', compute_fakes.service_binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + self.service_mock.delete.assert_called_with( + compute_fakes.service_binary, + ) + + +class TestServiceList(TestService): + + def setUp(self): + super(TestServiceList, self).setUp() + + self.service_mock.list.return_value = [fakes.FakeResource( + None, + copy.deepcopy(compute_fakes.SERVICE), + loaded=True, + )] + + # Get the command object to test + self.cmd = service.ListService(self.app, None) + + def test_service_list(self): + arglist = [ + '--host', compute_fakes.service_host, + '--service', compute_fakes.service_binary, + ] + verifylist = [ + ('host', compute_fakes.service_host), + ('service', compute_fakes.service_binary), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + + self.service_mock.list.assert_called_with( + compute_fakes.service_host, + compute_fakes.service_binary, + ) + + +class TestServiceSet(TestService): + + def setUp(self): + super(TestServiceSet, self).setUp() + + self.service_mock.enable.return_value = [fakes.FakeResource( + None, + copy.deepcopy(compute_fakes.SERVICE), + loaded=True, + )] + + self.service_mock.disable.return_value = [fakes.FakeResource( + None, + copy.deepcopy(compute_fakes.SERVICE), + loaded=True, + )] + + self.cmd = service.SetService(self.app, None) + + def test_service_set_enable(self): + arglist = [ + compute_fakes.service_host, + compute_fakes.service_binary, + '--enable', + ] + verifylist = [ + ('host', compute_fakes.service_host), + ('service', compute_fakes.service_binary), + ('enabled', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.service_mock.enable.assert_called_with( + compute_fakes.service_host, + compute_fakes.service_binary, + ) + + def test_service_set_disable(self): + arglist = [ + compute_fakes.service_host, + compute_fakes.service_binary, + '--disable', + ] + verifylist = [ + ('host', compute_fakes.service_host), + ('service', compute_fakes.service_binary), + ('enabled', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + self.service_mock.disable.assert_called_with( + compute_fakes.service_host, + compute_fakes.service_binary, + ) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index 979f9481..9f4dcc50 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -14,10 +14,11 @@ # import json +import mock import six import sys -from keystoneclient import fixture +from keystoneauth1 import fixture import requests @@ -122,17 +123,41 @@ class FakeModule(object): class FakeResource(object): - def __init__(self, manager, info, loaded=False): + def __init__(self, manager=None, info={}, loaded=False, methods={}): + """Set attributes and methods for a resource. + + :param manager: + The resource manager + :param Dictionary info: + A dictionary with all attributes + :param bool loaded: + True if the resource is loaded in memory + :param Dictionary methods: + A dictionary with all methods + """ self.__name__ = type(self).__name__ self.manager = manager self._info = info self._add_details(info) + self._add_methods(methods) self._loaded = loaded def _add_details(self, info): for (k, v) in six.iteritems(info): setattr(self, k, v) + def _add_methods(self, methods): + """Fake methods with MagicMock objects. + + For each <@key, @value> pairs in methods, add an callable MagicMock + object named @key as an attribute, and set the mock's return_value to + @value. When users access the attribute with (), @value will be + returned, which looks like a function call. + """ + for (name, ret) in six.iteritems(methods): + method = mock.MagicMock(return_value=ret) + setattr(self, name, method) + def __repr__(self): reprkeys = sorted(k for k in self.__dict__.keys() if k[0] != '_' and k != 'manager') @@ -150,3 +175,11 @@ class FakeResponse(requests.Response): self._content = json.dumps(data) if not isinstance(self._content, six.binary_type): self._content = self._content.encode() + + +class FakeModel(dict): + def __getattr__(self, key): + try: + return self[key] + except KeyError: + raise AttributeError(key) diff --git a/openstackclient/tests/identity/v2_0/test_project.py b/openstackclient/tests/identity/v2_0/test_project.py index 18e862eb..16ab1957 100644 --- a/openstackclient/tests/identity/v2_0/test_project.py +++ b/openstackclient/tests/identity/v2_0/test_project.py @@ -15,7 +15,7 @@ import copy -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.identity.v2_0 import project from openstackclient.tests import fakes @@ -223,7 +223,7 @@ class TestProjectCreate(TestProject): def test_project_create_or_show_exists(self): def _raise_conflict(*args, **kwargs): - raise ksc_exc.Conflict(None) + raise ks_exc.Conflict(None) # need to make this throw an exception... self.projects_mock.create.side_effect = _raise_conflict diff --git a/openstackclient/tests/identity/v2_0/test_role.py b/openstackclient/tests/identity/v2_0/test_role.py index 478a4ff2..ec484333 100644 --- a/openstackclient/tests/identity/v2_0/test_role.py +++ b/openstackclient/tests/identity/v2_0/test_role.py @@ -16,7 +16,7 @@ import copy import mock -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc from openstackclient.common import exceptions from openstackclient.identity.v2_0 import role @@ -146,7 +146,7 @@ class TestRoleCreate(TestRole): def test_role_create_or_show_exists(self): def _raise_conflict(*args, **kwargs): - raise ksc_exc.Conflict(None) + raise ks_exc.Conflict(None) # need to make this throw an exception... self.roles_mock.create.side_effect = _raise_conflict diff --git a/openstackclient/tests/identity/v2_0/test_user.py b/openstackclient/tests/identity/v2_0/test_user.py index 6fe82f93..bf25681e 100644 --- a/openstackclient/tests/identity/v2_0/test_user.py +++ b/openstackclient/tests/identity/v2_0/test_user.py @@ -16,7 +16,8 @@ import copy import mock -from keystoneclient import exceptions as ksc_exc +from keystoneauth1 import exceptions as ks_exc + from openstackclient.identity.v2_0 import user from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes @@ -345,7 +346,7 @@ class TestUserCreate(TestUser): def test_user_create_or_show_exists(self): def _raise_conflict(*args, **kwargs): - raise ksc_exc.Conflict(None) + raise ks_exc.Conflict(None) # need to make this throw an exception... self.users_mock.create.side_effect = _raise_conflict diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index 9c4de9cc..9fe341ed 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -123,13 +123,11 @@ PROJECT_2 = { } region_id = 'region_one' -region_url = 'http://localhost:1111' region_parent_region_id = 'region_two' region_description = 'region one' REGION = { 'id': region_id, - 'url': region_url, 'description': region_description, 'parent_region_id': region_parent_region_id, 'links': base_url + 'regions/' + region_id, @@ -195,6 +193,8 @@ SERVICE_WITHOUT_NAME = { 'links': base_url + 'services/' + service_id, } +credential_id = 'c-123' + endpoint_id = 'e-123' endpoint_url = 'http://127.0.0.1:35357' endpoint_region = 'RegionOne' @@ -261,6 +261,7 @@ TOKEN_WITH_DOMAIN_ID = { idp_id = 'test_idp' idp_description = 'super exciting IdP description' idp_remote_ids = ['entity1', 'entity2'] +formatted_idp_remote_ids = 'entity1, entity2' IDENTITY_PROVIDER = { 'id': idp_id, @@ -400,6 +401,8 @@ class FakeIdentityv3Client(object): def __init__(self, **kwargs): self.domains = mock.Mock() self.domains.resource_class = fakes.FakeResource(None, {}) + self.credentials = mock.Mock() + self.credentials.resource_class = fakes.FakeResource(None, {}) self.endpoints = mock.Mock() self.endpoints.resource_class = fakes.FakeResource(None, {}) self.groups = mock.Mock() diff --git a/openstackclient/tests/identity/v3/test_credential.py b/openstackclient/tests/identity/v3/test_credential.py new file mode 100644 index 00000000..e2e690c3 --- /dev/null +++ b/openstackclient/tests/identity/v3/test_credential.py @@ -0,0 +1,112 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json + +from openstackclient.identity.v3 import credential +from openstackclient.tests.identity.v3 import fakes as identity_fakes +from openstackclient.tests import utils + + +class TestCredential(identity_fakes.TestIdentityv3): + data = { + "access": "abc123", + "secret": "hidden-message", + "trust_id": None + } + + def __init__(self, *args): + super(TestCredential, self).__init__(*args) + + self.json_data = json.dumps(self.data) + + def setUp(self): + super(TestCredential, self).setUp() + + # Get a shortcut to the CredentialManager Mock + self.credentials_mock = self.app.client_manager.identity.credentials + self.credentials_mock.reset_mock() + + # Get a shortcut to the UserManager Mock + self.users_mock = self.app.client_manager.identity.users + self.users_mock.reset_mock() + + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock.reset_mock() + + +class TestCredentialSet(TestCredential): + def setUp(self): + super(TestCredentialSet, self).setUp() + self.cmd = credential.SetCredential(self.app, None) + + def test_credential_set_no_options(self): + arglist = [ + identity_fakes.credential_id, + ] + + self.assertRaises(utils.ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_credential_set_missing_user(self): + arglist = [ + '--type', 'ec2', + '--data', self.json_data, + identity_fakes.credential_id, + ] + + self.assertRaises(utils.ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_credential_set_missing_type(self): + arglist = [ + '--user', identity_fakes.user_name, + '--data', self.json_data, + identity_fakes.credential_id, + ] + + self.assertRaises(utils.ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_credential_set_missing_data(self): + arglist = [ + '--user', identity_fakes.user_name, + '--type', 'ec2', + identity_fakes.credential_id, + ] + + self.assertRaises(utils.ParserException, + self.check_parser, self.cmd, arglist, []) + + def test_credential_set_valid(self): + arglist = [ + '--user', identity_fakes.user_name, + '--type', 'ec2', + '--data', self.json_data, + identity_fakes.credential_id, + ] + + parsed_args = self.check_parser(self.cmd, arglist, []) + self.cmd.take_action(parsed_args) + + def test_credential_set_valid_with_project(self): + arglist = [ + '--user', identity_fakes.user_name, + '--type', 'ec2', + '--data', self.json_data, + '--project', identity_fakes.project_name, + identity_fakes.credential_id, + ] + + parsed_args = self.check_parser(self.cmd, arglist, []) + self.cmd.take_action(parsed_args) diff --git a/openstackclient/tests/identity/v3/test_identity_provider.py b/openstackclient/tests/identity/v3/test_identity_provider.py index cd328c1d..db097d5d 100644 --- a/openstackclient/tests/identity/v3/test_identity_provider.py +++ b/openstackclient/tests/identity/v3/test_identity_provider.py @@ -69,7 +69,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): identity_fakes.idp_description, True, identity_fakes.idp_id, - identity_fakes.idp_remote_ids + identity_fakes.formatted_idp_remote_ids ) self.assertEqual(datalist, data) @@ -103,7 +103,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): identity_fakes.idp_description, True, identity_fakes.idp_id, - identity_fakes.idp_remote_ids + identity_fakes.formatted_idp_remote_ids ) self.assertEqual(datalist, data) @@ -137,7 +137,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): identity_fakes.idp_description, True, identity_fakes.idp_id, - identity_fakes.idp_remote_ids + identity_fakes.formatted_idp_remote_ids ) self.assertEqual(datalist, data) @@ -172,7 +172,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): identity_fakes.idp_description, True, identity_fakes.idp_id, - identity_fakes.idp_remote_ids + identity_fakes.formatted_idp_remote_ids ) self.assertEqual(datalist, data) @@ -211,7 +211,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): identity_fakes.idp_description, True, identity_fakes.idp_id, - identity_fakes.idp_remote_ids + identity_fakes.formatted_idp_remote_ids ) self.assertEqual(datalist, data) @@ -253,7 +253,7 @@ class TestIdentityProviderCreate(TestIdentityProvider): None, False, identity_fakes.idp_id, - identity_fakes.idp_remote_ids + identity_fakes.formatted_idp_remote_ids ) self.assertEqual(datalist, data) @@ -363,7 +363,7 @@ class TestIdentityProviderShow(TestIdentityProvider): identity_fakes.idp_description, True, identity_fakes.idp_id, - identity_fakes.idp_remote_ids + identity_fakes.formatted_idp_remote_ids ) self.assertEqual(datalist, data) @@ -374,6 +374,50 @@ class TestIdentityProviderSet(TestIdentityProvider): super(TestIdentityProviderSet, self).setUp() self.cmd = identity_provider.SetIdentityProvider(self.app, None) + def test_identity_provider_set_description(self): + """Set Identity Provider's description. """ + def prepare(self): + """Prepare fake return objects before the test is executed""" + updated_idp = copy.deepcopy(identity_fakes.IDENTITY_PROVIDER) + updated_idp['enabled'] = False + resources = fakes.FakeResource( + None, + updated_idp, + loaded=True + ) + self.identity_providers_mock.update.return_value = resources + + prepare(self) + new_description = 'new desc' + arglist = [ + '--description', new_description, + identity_fakes.idp_id + ] + verifylist = [ + ('identity_provider', identity_fakes.idp_id), + ('description', new_description), + ('enable', False), + ('disable', False), + ('remote_id', None) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.identity_providers_mock.update.assert_called_with( + identity_fakes.idp_id, + description=new_description + ) + + collist = ('description', 'enabled', 'id', 'remote_ids') + self.assertEqual(collist, columns) + datalist = ( + identity_fakes.idp_description, + False, + identity_fakes.idp_id, + identity_fakes.idp_remote_ids + ) + self.assertEqual(datalist, data) + def test_identity_provider_disable(self): """Disable Identity Provider @@ -398,6 +442,7 @@ class TestIdentityProviderSet(TestIdentityProvider): ] verifylist = [ ('identity_provider', identity_fakes.idp_id), + ('description', None), ('enable', False), ('disable', True), ('remote_id', identity_fakes.idp_remote_ids) @@ -443,6 +488,7 @@ class TestIdentityProviderSet(TestIdentityProvider): ] verifylist = [ ('identity_provider', identity_fakes.idp_id), + ('description', None), ('enable', True), ('disable', False), ('remote_id', identity_fakes.idp_remote_ids) @@ -488,6 +534,7 @@ class TestIdentityProviderSet(TestIdentityProvider): ] verifylist = [ ('identity_provider', identity_fakes.idp_id), + ('description', None), ('enable', True), ('disable', False), ('remote_id', [self.new_remote_id]) @@ -533,6 +580,7 @@ class TestIdentityProviderSet(TestIdentityProvider): ] verifylist = [ ('identity_provider', identity_fakes.idp_id), + ('description', None), ('enable', True), ('disable', False), ('remote_id_file', self.new_remote_id), diff --git a/openstackclient/tests/identity/v3/test_region.py b/openstackclient/tests/identity/v3/test_region.py index 7f6ced9f..0ebbbecf 100644 --- a/openstackclient/tests/identity/v3/test_region.py +++ b/openstackclient/tests/identity/v3/test_region.py @@ -61,19 +61,17 @@ class TestRegionCreate(TestRegion): 'description': identity_fakes.region_description, 'id': identity_fakes.region_id, 'parent_region': None, - 'url': None, } self.regions_mock.create.assert_called_with( **kwargs ) - collist = ('description', 'parent_region', 'region', 'url') + collist = ('description', 'parent_region', 'region') self.assertEqual(collist, columns) datalist = ( identity_fakes.region_description, identity_fakes.region_parent_region_id, identity_fakes.region_id, - identity_fakes.region_url, ) self.assertEqual(datalist, data) @@ -94,19 +92,17 @@ class TestRegionCreate(TestRegion): 'description': None, 'id': identity_fakes.region_id, 'parent_region': None, - 'url': None, } self.regions_mock.create.assert_called_with( **kwargs ) - collist = ('description', 'parent_region', 'region', 'url') + collist = ('description', 'parent_region', 'region') self.assertEqual(collist, columns) datalist = ( identity_fakes.region_description, identity_fakes.region_parent_region_id, identity_fakes.region_id, - identity_fakes.region_url, ) self.assertEqual(datalist, data) @@ -129,54 +125,17 @@ class TestRegionCreate(TestRegion): 'description': None, 'id': identity_fakes.region_id, 'parent_region': identity_fakes.region_parent_region_id, - 'url': None, } self.regions_mock.create.assert_called_with( **kwargs ) - collist = ('description', 'parent_region', 'region', 'url') + collist = ('description', 'parent_region', 'region') self.assertEqual(collist, columns) datalist = ( identity_fakes.region_description, identity_fakes.region_parent_region_id, identity_fakes.region_id, - identity_fakes.region_url, - ) - self.assertEqual(datalist, data) - - def test_region_create_url(self): - arglist = [ - identity_fakes.region_id, - '--url', identity_fakes.region_url, - ] - verifylist = [ - ('region', identity_fakes.region_id), - ('url', identity_fakes.region_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 = { - 'description': None, - 'id': identity_fakes.region_id, - 'parent_region': None, - 'url': identity_fakes.region_url, - } - self.regions_mock.create.assert_called_with( - **kwargs - ) - - collist = ('description', 'parent_region', 'region', 'url') - self.assertEqual(collist, columns) - datalist = ( - identity_fakes.region_description, - identity_fakes.region_parent_region_id, - identity_fakes.region_id, - identity_fakes.region_url, ) self.assertEqual(datalist, data) @@ -233,13 +192,12 @@ class TestRegionList(TestRegion): columns, data = self.cmd.take_action(parsed_args) self.regions_mock.list.assert_called_with() - collist = ('Region', 'Parent Region', 'Description', 'URL') + collist = ('Region', 'Parent Region', 'Description') self.assertEqual(collist, columns) datalist = (( identity_fakes.region_id, identity_fakes.region_parent_region_id, identity_fakes.region_description, - identity_fakes.region_url, ), ) self.assertEqual(datalist, tuple(data)) @@ -257,13 +215,12 @@ class TestRegionList(TestRegion): self.regions_mock.list.assert_called_with( parent_region_id=identity_fakes.region_parent_region_id) - collist = ('Region', 'Parent Region', 'Description', 'URL') + collist = ('Region', 'Parent Region', 'Description') self.assertEqual(collist, columns) datalist = (( identity_fakes.region_id, identity_fakes.region_parent_region_id, identity_fakes.region_description, - identity_fakes.region_url, ), ) self.assertEqual(datalist, tuple(data)) @@ -319,29 +276,6 @@ class TestRegionSet(TestRegion): **kwargs ) - def test_region_set_url(self): - arglist = [ - '--url', 'new url', - identity_fakes.region_id, - ] - verifylist = [ - ('url', 'new url'), - ('region', identity_fakes.region_id), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.run(parsed_args) - self.assertEqual(0, result) - - # Set expected values - kwargs = { - 'url': 'new url', - } - self.regions_mock.update.assert_called_with( - identity_fakes.region_id, - **kwargs - ) - def test_region_set_parent_region_id(self): arglist = [ '--parent-region', 'new_parent', @@ -395,12 +329,11 @@ class TestRegionShow(TestRegion): identity_fakes.region_id, ) - collist = ('description', 'parent_region', 'region', 'url') + collist = ('description', 'parent_region', 'region') self.assertEqual(collist, columns) datalist = ( identity_fakes.region_description, identity_fakes.region_parent_region_id, identity_fakes.region_id, - identity_fakes.region_url, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py index ee3dc946..8ad4b099 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/identity/v3/test_role.py @@ -367,6 +367,39 @@ class TestRoleList(TestRole): ), ) self.assertEqual(datalist, tuple(data)) + def test_user_list_inherited(self): + arglist = [ + '--user', identity_fakes.user_id, + '--inherited', + ] + verifylist = [ + ('user', identity_fakes.user_id), + ('inherited', True), + ] + 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 = { + 'domain': 'default', + 'user': self.users_mock.get(), + 'os_inherit_extension_inherited': True, + } + # RoleManager.list(user=, group=, domain=, project=, **kwargs) + self.roles_mock.list.assert_called_with( + **kwargs + ) + + collist = ('ID', 'Name') + self.assertEqual(collist, columns) + datalist = (( + identity_fakes.role_id, + identity_fakes.role_name, + ), ) + self.assertEqual(datalist, tuple(data)) + def test_user_list_user(self): arglist = [ '--user', identity_fakes.user_id, diff --git a/openstackclient/tests/image/v1/test_image.py b/openstackclient/tests/image/v1/test_image.py index d10d3b15..4d964bdb 100644 --- a/openstackclient/tests/image/v1/test_image.py +++ b/openstackclient/tests/image/v1/test_image.py @@ -261,16 +261,17 @@ class TestImageList(TestImage): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( - detailed=False, + detailed=True, marker=image_fakes.image_id, ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(collist, columns) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -288,17 +289,18 @@ class TestImageList(TestImage): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( - detailed=False, + detailed=True, public=True, marker=image_fakes.image_id, ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(collist, columns) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -316,17 +318,18 @@ class TestImageList(TestImage): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( - detailed=False, + detailed=True, private=True, marker=image_fakes.image_id, ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(collist, columns) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -401,12 +404,13 @@ class TestImageList(TestImage): property_field='properties', ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(columns, collist) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -423,7 +427,7 @@ class TestImageList(TestImage): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( - detailed=False, + detailed=True, marker=image_fakes.image_id, ) si_mock.assert_called_with( @@ -431,12 +435,13 @@ class TestImageList(TestImage): 'name:asc' ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(collist, columns) datalist = (( image_fakes.image_id, - image_fakes.image_name + image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 11ad455d..692ef104 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -15,7 +15,10 @@ import copy import mock +import random +import uuid +from openstackclient.common import utils as common_utils from openstackclient.tests import fakes from openstackclient.tests import utils @@ -167,3 +170,133 @@ class TestImagev2(utils.TestCommand): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + + +class FakeImage(object): + """Fake one or more images. + + TODO(xiexs): Currently, only image API v2 is supported by this class. + """ + + @staticmethod + def create_one_image(attrs={}): + """Create a fake image. + + :param Dictionary attrs: + A dictionary with all attrbutes of image + :retrun: + A FakeResource object with id, name, owner, protected, + visibility and tags attrs + """ + # Set default attribute + image_info = { + 'id': 'image-id' + uuid.uuid4().hex, + 'name': 'image-name' + uuid.uuid4().hex, + 'owner': 'image-owner' + uuid.uuid4().hex, + 'protected': bool(random.choice([0, 1])), + 'visibility': random.choice(['public', 'private']), + 'tags': [uuid.uuid4().hex for r in range(random.randint(1, 5))], + } + + # Overwrite default attributes if there are some attributes set + image_info.update(attrs) + + image = fakes.FakeResource( + None, + image_info, + loaded=True) + return image + + @staticmethod + def create_images(attrs={}, count=2): + """Create multiple fake images. + + :param Dictionary attrs: + A dictionary with all attributes of image + :param Integer count: + The number of images to be faked + :return: + A list of FakeResource objects + """ + images = [] + for n in range(0, count): + images.append(FakeImage.create_one_image(attrs)) + + return images + + @staticmethod + def get_images(images=None, count=2): + """Get an iterable MagicMock object with a list of faked images. + + If images list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List images: + A list of FakeResource objects faking images + :param Integer count: + The number of images to be faked + :return + An iterable Mock object with side_effect set to a list of faked + images + """ + if images is None: + images = FakeImage.create_images(count) + + return mock.MagicMock(side_effect=images) + + @staticmethod + def get_image_info(image=None): + """Get the image info from a faked image object. + + :param image: + A FakeResource objects faking image + :return + A dictionary which includes the faked image info as follows: + { + 'id': image_id, + 'name': image_name, + 'owner': image_owner, + 'protected': image_protected, + 'visibility': image_visibility, + 'tags': image_tags + } + """ + if image is not None: + return image._info + return {} + + @staticmethod + def get_image_columns(image=None): + """Get the image columns from a faked image object. + + :param image: + A FakeResource objects faking image + :return + A tuple which may include the following keys: + ('id', 'name', 'owner', 'protected', 'visibility', 'tags') + """ + if image is not None: + return tuple(k for k in sorted( + FakeImage.get_image_info(image).keys())) + return tuple([]) + + @staticmethod + def get_image_data(image=None): + """Get the image data from a faked image object. + + :param image: + A FakeResource objects faking image + :return + A tuple which may include the following values: + ('image-123', 'image-foo', 'admin', False, 'public', 'bar, baz') + """ + data_list = [] + if image is not None: + for x in sorted(FakeImage.get_image_info(image).keys()): + if x == 'tags': + # The 'tags' should be format_list + data_list.append( + common_utils.format_list(getattr(image, x))) + else: + data_list.append(getattr(image, x)) + return tuple(data_list) diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 4ce85475..be73c4ca 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -112,6 +112,7 @@ class TestImageCreate(TestImage): '--disk-format', 'fs', '--min-disk', '10', '--min-ram', '4', + '--owner', '123456', '--protected', '--private', image_fakes.image_name, @@ -121,6 +122,7 @@ class TestImageCreate(TestImage): ('disk_format', 'fs'), ('min_disk', 10), ('min_ram', 4), + ('owner', '123456'), ('protected', True), ('unprotected', False), ('public', False), @@ -139,6 +141,7 @@ class TestImageCreate(TestImage): disk_format='fs', min_disk=10, min_ram=4, + owner='123456', protected=True, visibility='private', ) @@ -213,11 +216,10 @@ class TestImageCreate(TestImage): def test_image_create_dead_options(self): arglist = [ - '--owner', 'nobody', + '--store', 'somewhere', image_fakes.image_name, ] verifylist = [ - ('owner', 'nobody'), ('name', image_fakes.image_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -238,10 +240,8 @@ class TestAddProjectToImage(TestImage): copy.deepcopy(image_fakes.IMAGE), loaded=True, ) - self.image_members_mock.create.return_value = fakes.FakeResource( - None, + self.image_members_mock.create.return_value = fakes.FakeModel( copy.deepcopy(image_fakes.MEMBER), - loaded=True, ) self.project_mock.get.return_value = fakes.FakeResource( None, @@ -374,12 +374,13 @@ class TestImageList(TestImage): marker=image_fakes.image_id, ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(collist, columns) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -402,12 +403,13 @@ class TestImageList(TestImage): marker=image_fakes.image_id, ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(collist, columns) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -430,12 +432,13 @@ class TestImageList(TestImage): marker=image_fakes.image_id, ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(collist, columns) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -458,12 +461,13 @@ class TestImageList(TestImage): marker=image_fakes.image_id, ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(columns, collist) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -536,12 +540,13 @@ class TestImageList(TestImage): property_field='properties', ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(columns, collist) datalist = (( image_fakes.image_id, image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) @@ -565,12 +570,13 @@ class TestImageList(TestImage): 'name:asc' ) - collist = ('ID', 'Name') + collist = ('ID', 'Name', 'Status') self.assertEqual(collist, columns) datalist = (( image_fakes.image_id, - image_fakes.image_name + image_fakes.image_name, + '', ), ) self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/object/v1/fakes.py b/openstackclient/tests/object/v1/fakes.py index 6aef05b1..986ab2f3 100644 --- a/openstackclient/tests/object/v1/fakes.py +++ b/openstackclient/tests/object/v1/fakes.py @@ -13,7 +13,7 @@ # under the License. # -from keystoneclient import session +from keystoneauth1 import session from openstackclient.api import object_store_v1 as object_store from openstackclient.tests import utils diff --git a/openstackclient/tests/object/v1/test_container_all.py b/openstackclient/tests/object/v1/test_container_all.py index 4477f2e0..69fc0f39 100644 --- a/openstackclient/tests/object/v1/test_container_all.py +++ b/openstackclient/tests/object/v1/test_container_all.py @@ -286,7 +286,6 @@ class TestContainerShow(TestContainerAll): def test_object_show_container(self): headers = { - 'x-container-meta-owner': object_fakes.ACCOUNT_ID, 'x-container-object-count': '42', 'x-container-bytes-used': '123', 'x-container-read': 'qaz', @@ -316,7 +315,6 @@ class TestContainerShow(TestContainerAll): 'account', 'bytes_used', 'container', - 'meta-owner', 'object_count', 'read_acl', 'sync_key', @@ -328,7 +326,6 @@ class TestContainerShow(TestContainerAll): object_fakes.ACCOUNT_ID, '123', 'ernie', - object_fakes.ACCOUNT_ID, '42', 'qaz', 'rfv', diff --git a/openstackclient/tests/object/v1/test_object_all.py b/openstackclient/tests/object/v1/test_object_all.py index 41fe6324..7a76ab76 100644 --- a/openstackclient/tests/object/v1/test_object_all.py +++ b/openstackclient/tests/object/v1/test_object_all.py @@ -160,7 +160,6 @@ class TestObjectShow(TestObjectAll): 'content-type', 'etag', 'last-modified', - 'meta-owner', 'object', 'x-object-manifest', ) @@ -172,7 +171,6 @@ class TestObjectShow(TestObjectAll): 'text/plain', '4c4e39a763d58392724bccf76a58783a', 'yesterday', - object_fakes.ACCOUNT_ID, object_fakes.object_name_1, 'manifest', ) diff --git a/openstackclient/tests/volume/v1/test_volume.py b/openstackclient/tests/volume/v1/test_volume.py index f73260e9..70ff50de 100644 --- a/openstackclient/tests/volume/v1/test_volume.py +++ b/openstackclient/tests/volume/v1/test_volume.py @@ -527,6 +527,188 @@ class TestVolumeCreate(TestVolume): self.assertEqual(datalist, data) +class TestVolumeList(TestVolume): + + def setUp(self): + super(TestVolumeList, self).setUp() + + self.volumes_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = volume.ListVolume(self.app, None) + + def test_volume_list_no_options(self): + arglist = [] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, columns) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + '', + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_name(self): + arglist = [ + '--name', volume_fakes.volume_name, + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', volume_fakes.volume_name), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + '', + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_status(self): + arglist = [ + '--status', volume_fakes.volume_status, + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', volume_fakes.volume_status), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + '', + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('long', False), + ('all_projects', True), + ('name', None), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, columns) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + '', + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_long(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ('all_projects', False), + ('name', None), + ('status', None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Type', + 'Bootable', + 'Attached to', + 'Properties', + ) + self.assertEqual(collist, columns) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + volume_fakes.volume_type, + '', + '', + "Alpha='a', Beta='b', Gamma='g'", + ), ) + self.assertEqual(datalist, tuple(data)) + + class TestVolumeSet(TestVolume): def setUp(self): diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 7b7758a3..60cec335 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -14,9 +14,11 @@ import copy import mock +import random +import uuid from openstackclient.tests import fakes -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests import utils @@ -212,7 +214,7 @@ class TestVolume(utils.TestCommand): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN ) - self.app.client_manager.identity = identity_fakes.FakeIdentityv2Client( + self.app.client_manager.identity = identity_fakes.FakeIdentityv3Client( endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN ) @@ -220,3 +222,85 @@ class TestVolume(utils.TestCommand): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN ) + + +class FakeVolume(object): + """Fake one or more volumes. + + TODO(xiexs): Currently, only volume API v2 is supported by this class. + """ + + @staticmethod + def create_one_volume(attrs={}): + """Create a fake volume. + + :param Dictionary attrs: + A dictionary with all attributes of volume + :retrun: + A FakeResource object with id, name, status, etc. + """ + # Set default attribute + volume_info = { + 'id': 'volume-id' + uuid.uuid4().hex, + 'name': 'volume-name' + uuid.uuid4().hex, + 'description': 'description' + uuid.uuid4().hex, + 'status': random.choice(['available', 'in_use']), + 'size': random.randint(1, 20), + 'volume_type': + random.choice(['fake_lvmdriver-1', 'fake_lvmdriver-2']), + 'metadata': { + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex}, + 'snapshot_id': random.randint(1, 5), + 'availability_zone': 'zone' + uuid.uuid4().hex, + 'attachments': { + 'device': '/dev/' + uuid.uuid4().hex, + 'server_id': uuid.uuid4().hex}, + } + + # Overwrite default attributes if there are some attributes set + volume_info.update(attrs) + + volume = fakes.FakeResource( + None, + volume_info, + loaded=True) + return volume + + @staticmethod + def create_volumes(attrs={}, count=2): + """Create multiple fake volumes. + + :param Dictionary attrs: + A dictionary with all attributes of volume + :param Integer count: + The number of volumes to be faked + :return: + A list of FakeResource objects + """ + volumes = [] + for n in range(0, count): + volumes.append(FakeVolume.create_one_volume(attrs)) + + return volumes + + @staticmethod + def get_volumes(volumes=None, count=2): + """Get an iterable MagicMock object with a list of faked volumes. + + If volumes list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List volumes: + A list of FakeResource objects faking volumes + :param Integer count: + The number of volumes to be faked + :return + An iterable Mock object with side_effect set to a list of faked + volumes + """ + if volumes is None: + volumes = FakeVolume.create_volumes(count) + + return mock.MagicMock(side_effect=volumes) diff --git a/openstackclient/tests/volume/v2/test_snapshot.py b/openstackclient/tests/volume/v2/test_snapshot.py index 3ceb57fa..3b30d4ef 100644 --- a/openstackclient/tests/volume/v2/test_snapshot.py +++ b/openstackclient/tests/volume/v2/test_snapshot.py @@ -225,6 +225,7 @@ class TestSnapshotList(TestSnapshot): def test_snapshot_list_without_options(self): arglist = [] verifylist = [ + ('all_projects', False), ("long", False) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -243,7 +244,7 @@ class TestSnapshotList(TestSnapshot): def test_snapshot_list_with_options(self): arglist = ["--long"] - verifylist = [("long", True)] + verifylist = [("long", True), ('all_projects', False)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) @@ -263,3 +264,33 @@ class TestSnapshotList(TestSnapshot): volume_fakes.EXPECTED_SNAPSHOT.get("properties") ),) self.assertEqual(datalist, tuple(data)) + + def test_snapshot_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('long', False), + ('all_projects', True) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = [ + "ID", + "Name", + "Description", + "Status", + "Size" + ] + self.assertEqual(collist, columns) + + datalist = (( + volume_fakes.snapshot_id, + volume_fakes.snapshot_name, + volume_fakes.snapshot_description, + "available", + volume_fakes.snapshot_size + ), ) + self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index 9a07263b..c63cd1fa 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -73,7 +73,7 @@ class TestTypeCreate(TestType): self.types_mock.create.assert_called_with( volume_fakes.type_name, description=volume_fakes.type_description, - public=True, + is_public=True, ) collist = ( @@ -93,7 +93,7 @@ class TestTypeCreate(TestType): arglist = [ volume_fakes.type_name, "--description", volume_fakes.type_description, - "--private" + "--private", ] verifylist = [ ("name", volume_fakes.type_name), @@ -107,7 +107,7 @@ class TestTypeCreate(TestType): self.types_mock.create.assert_called_with( volume_fakes.type_name, description=volume_fakes.type_description, - private=True, + is_public=False, ) collist = ( diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index b15fd02f..50826c31 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -14,8 +14,10 @@ import copy +from mock import call + from openstackclient.tests import fakes -from openstackclient.tests.identity.v2_0 import fakes as identity_fakes +from openstackclient.tests.identity.v3 import fakes as identity_fakes from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import volume @@ -27,7 +29,7 @@ class TestVolume(volume_fakes.TestVolume): self.volumes_mock = self.app.client_manager.volume.volumes self.volumes_mock.reset_mock() - self.projects_mock = self.app.client_manager.identity.tenants + self.projects_mock = self.app.client_manager.identity.projects self.projects_mock.reset_mock() self.users_mock = self.app.client_manager.identity.users @@ -36,6 +38,14 @@ class TestVolume(volume_fakes.TestVolume): self.images_mock = self.app.client_manager.image.images self.images_mock.reset_mock() + def setup_volumes_mock(self, count): + volumes = volume_fakes.FakeVolume.create_volumes(count=count) + + self.volumes_mock.get = volume_fakes.FakeVolume.get_volumes( + volumes, + 0) + return volumes + class TestVolumeCreate(TestVolume): def setUp(self): @@ -560,32 +570,145 @@ class TestVolumeList(TestVolume): ), ) self.assertEqual(datalist, tuple(data)) - def test_volume_list_all_projects_option(self): + def test_volume_list_project(self): arglist = [ - '--all-projects', + '--project', identity_fakes.project_name, ] verifylist = [ + ('project', identity_fakes.project_name), ('long', False), - ('all_projects', True), - ('name', None), + ('all_projects', False), ('status', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - collist = [ + collist = ( 'ID', 'Display Name', 'Status', 'Size', 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_project_domain(self): + arglist = [ + '--project', identity_fakes.project_name, + '--project-domain', identity_fakes.domain_name, ] - self.assertEqual(collist, columns) + verifylist = [ + ('project', identity_fakes.project_name), + ('project_domain', identity_fakes.domain_name), + ('long', False), + ('all_projects', False), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_user(self): + arglist = [ + '--user', identity_fakes.user_name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('long', False), + ('all_projects', False), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) server = volume_fakes.volume_attachment_server['server_id'] device = volume_fakes.volume_attachment_server['device'] msg = 'Attached to %s on %s ' % (server, device) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_user_domain(self): + arglist = [ + '--user', identity_fakes.user_name, + '--user-domain', identity_fakes.domain_name, + ] + verifylist = [ + ('user', identity_fakes.user_name), + ('user_domain', identity_fakes.domain_name), + ('long', False), + ('all_projects', False), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( volume_fakes.volume_id, volume_fakes.volume_name, @@ -666,6 +789,41 @@ class TestVolumeList(TestVolume): ), ) self.assertEqual(datalist, tuple(data)) + def test_volume_list_all_projects(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('long', False), + ('all_projects', True), + ('name', None), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = [ + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ] + self.assertEqual(collist, columns) + + server = volume_fakes.volume_attachment_server['server_id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + def test_volume_list_long(self): arglist = [ '--long', @@ -740,24 +898,38 @@ class TestVolumeDelete(TestVolume): def setUp(self): super(TestVolumeDelete, self).setUp() - self.volumes_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True) self.volumes_mock.delete.return_value = None # Get the command object to mock self.cmd = volume.DeleteVolume(self.app, None) - def test_volume_delete(self): + def test_volume_delete_one_volume(self): + volumes = self.setup_volumes_mock(count=1) + arglist = [ - volume_fakes.volume_id + volumes[0].id ] verifylist = [ - ("volumes", [volume_fakes.volume_id]) + ("volumes", [volumes[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.volumes_mock.delete.assert_called_with(volume_fakes.volume_id) + self.volumes_mock.delete.assert_called_with(volumes[0].id) + + def test_volume_delete_multi_volumes(self): + volumes = self.setup_volumes_mock(count=3) + + arglist = [v.id for v in volumes] + verifylist = [ + ('volumes', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + calls = [call(v.id) for v in volumes] + + self.volumes_mock.delete.assert_has_calls(calls) diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index de7bb5b9..93e17eb8 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -111,6 +111,12 @@ class ListSnapshot(lister.Lister): def get_parser(self, prog_name): parser = super(ListSnapshot, self).get_parser(prog_name) parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help='Include all projects (admin only)', + ) + parser.add_argument( '--long', action='store_true', default=False, @@ -157,7 +163,12 @@ class ListSnapshot(lister.Lister): # Just forget it if there's any trouble pass - data = self.app.client_manager.volume.volume_snapshots.list() + search_opts = { + 'all_tenants': parsed_args.all_projects, + } + + data = self.app.client_manager.volume.volume_snapshots.list( + search_opts=search_opts) return (column_headers, (utils.get_item_properties( s, columns, diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 52b0eb2e..0691d884 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -207,16 +207,16 @@ class ListVolume(lister.Lister): def get_parser(self, prog_name): parser = super(ListVolume, self).get_parser(prog_name) parser.add_argument( + '--name', + metavar='<name>', + help='Filter results by volume name', + ) + parser.add_argument( '--status', metavar='<status>', help='Filter results by status', ) parser.add_argument( - '--name', - metavar='<name>', - help='Filter results by name', - ) - parser.add_argument( '--all-projects', action='store_true', default=False, @@ -239,7 +239,7 @@ class ListVolume(lister.Lister): def _format_attach(attachments): """Return a formatted string of a volume's attached instances - :param volume: a volume.attachments field + :param attachments: a volume.attachments field :rtype: a string of formatted instances """ diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index bbc92c48..aa7630ae 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -108,6 +108,12 @@ class ListSnapshot(lister.Lister): def get_parser(self, prog_name): parser = super(ListSnapshot, self).get_parser(prog_name) parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help='Include all projects (admin only)', + ) + parser.add_argument( '--long', action='store_true', default=False, @@ -149,7 +155,12 @@ class ListSnapshot(lister.Lister): # Just forget it if there's any trouble pass - data = self.app.client_manager.volume.volume_snapshots.list() + search_opts = { + 'all_tenants': parsed_args.all_projects, + } + + data = self.app.client_manager.volume.volume_snapshots.list( + search_opts=search_opts) return (column_headers, (utils.get_item_properties( s, columns, diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 758f312b..bbcceca6 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -16,7 +16,6 @@ import copy import logging -import os from cliff import command from cliff import lister @@ -25,6 +24,7 @@ import six from openstackclient.common import parseractions from openstackclient.common import utils +from openstackclient.identity import common as identity_common class CreateVolume(show.ShowOne): @@ -185,10 +185,10 @@ class DeleteVolume(command.Command): for volume in parsed_args.volumes: volume_obj = utils.find_resource( volume_client.volumes, volume) - if parsed_args.force: - volume_client.volumes.force_delete(volume_obj.id) - else: - volume_client.volumes.delete(volume_obj.id) + if parsed_args.force: + volume_client.volumes.force_delete(volume_obj.id) + else: + volume_client.volumes.delete(volume_obj.id) return @@ -200,27 +200,39 @@ class ListVolume(lister.Lister): def get_parser(self, prog_name): parser = super(ListVolume, self).get_parser(prog_name) parser.add_argument( - '--all-projects', - action='store_true', - default=bool(int(os.environ.get("ALL_PROJECTS", 0))), - help='Include all projects (admin only)', + '--project', + metavar='<project-id>', + help='Filter results by project (name or ID) (admin only)' ) + identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( - '--long', - action='store_true', - default=False, - help='List additional fields in output', + '--user', + metavar='<user-id>', + help='Filter results by user (name or ID) (admin only)' ) + identity_common.add_user_domain_option_to_parser(parser) parser.add_argument( '--name', metavar='<name>', - help='Filter results by name', + help='Filter results by volume name', ) parser.add_argument( '--status', metavar='<status>', help='Filter results by status', ) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help='Include all projects (admin only)', + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) return parser @utils.log_method(log) @@ -228,11 +240,12 @@ class ListVolume(lister.Lister): volume_client = self.app.client_manager.volume compute_client = self.app.client_manager.compute + identity_client = self.app.client_manager.identity def _format_attach(attachments): """Return a formatted string of a volume's attached instances - :param volume: a volume.attachments field + :param attachments: a volume.attachments field :rtype: a string of formatted instances """ @@ -282,8 +295,23 @@ class ListVolume(lister.Lister): # Just forget it if there's any trouble pass + project_id = None + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain) + + user_id = None + if parsed_args.user: + user_id = identity_common.find_user(identity_client, + parsed_args.user, + parsed_args.user_domain) + search_opts = { - 'all_projects': parsed_args.all_projects, + 'all_tenants': parsed_args.all_projects, + 'project_id': project_id, + 'user_id': user_id, 'display_name': parsed_args.name, 'status': parsed_args.status, } @@ -298,7 +326,7 @@ class ListVolume(lister.Lister): ) for s in data)) -class SetVolume(show.ShowOne): +class SetVolume(command.Command): """Set volume properties""" log = logging.getLogger(__name__ + '.SetVolume') diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 8cca86f9..583e6ed9 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -73,9 +73,9 @@ class CreateVolumeType(show.ShowOne): kwargs = {} if parsed_args.public: - kwargs['public'] = True + kwargs['is_public'] = True if parsed_args.private: - kwargs['private'] = True + kwargs['is_public'] = False volume_type = volume_client.volume_types.create( parsed_args.name, |
