diff options
Diffstat (limited to 'openstackclient')
74 files changed, 3243 insertions, 723 deletions
diff --git a/openstackclient/api/auth.py b/openstackclient/api/auth.py index d62a82dc..7c520f49 100644 --- a/openstackclient/api/auth.py +++ b/openstackclient/api/auth.py @@ -14,12 +14,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.api.auth import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.api.auth\n" % __name__ + "Please use osc_lib.api.auth. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/command.py b/openstackclient/common/command.py index 29c1534d..44954da3 100644 --- a/openstackclient/common/command.py +++ b/openstackclient/common/command.py @@ -15,12 +15,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.command.command import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.command.command\n" % __name__ + "Please use osc_lib.command.command. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/exceptions.py b/openstackclient/common/exceptions.py index 7124074c..ed497e7b 100644 --- a/openstackclient/common/exceptions.py +++ b/openstackclient/common/exceptions.py @@ -14,12 +14,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.exceptions import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.exceptions\n" % __name__ + "Please use osc_lib.exceptions. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/logs.py b/openstackclient/common/logs.py index 8aa97d5b..24bf07eb 100644 --- a/openstackclient/common/logs.py +++ b/openstackclient/common/logs.py @@ -14,13 +14,16 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.logs import * # noqa from osc_lib.logs import _FileFormatter # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.logs\n" % __name__ + "Please use osc_lib.logs. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index fa5148ec..3af3a017 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -14,12 +14,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.cli.parseractions import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.cli.parseractions\n" % __name__ + "Please use osc_lib.cli.parseractions. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 5d53171c..8f099cc9 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -43,11 +43,20 @@ COMPUTE_QUOTAS = { } VOLUME_QUOTAS = { + 'backups': 'backups', + 'backup_gigabytes': 'backup-gigabytes', 'gigabytes': 'gigabytes', + 'per_volume_gigabytes': 'per-volume-gigabytes', 'snapshots': 'snapshots', 'volumes': 'volumes', } +IMPACT_VOLUME_TYPE_QUOTAS = [ + 'gigabytes', + 'snapshots', + 'volumes', +] + NOVA_NETWORK_QUOTAS = { 'floating_ips': 'floating-ips', 'security_group_rules': 'secgroup-rules', @@ -65,8 +74,8 @@ NETWORK_QUOTAS = { 'rbac_policy': 'rbac-policies', 'vip': 'vips', 'subnetpool': 'subnetpools', - 'member': 'members', - 'health_monitor': 'health-monitors', + 'healthmonitor': 'health-monitors', + 'l7policy': 'l7policies', } @@ -128,7 +137,8 @@ class SetQuota(command.Command): for k, v in VOLUME_QUOTAS.items(): value = getattr(parsed_args, k, None) if value is not None: - if parsed_args.volume_type: + if (parsed_args.volume_type and + k in IMPACT_VOLUME_TYPE_QUOTAS): k = k + '_%s' % parsed_args.volume_type volume_kwargs[k] = value @@ -144,36 +154,34 @@ class SetQuota(command.Command): if value is not None: compute_kwargs[k] = value - 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( - project.id, + parsed_args.project, **compute_kwargs) if volume_kwargs: volume_client.quota_classes.update( - project.id, + parsed_args.project, **volume_kwargs) if network_kwargs: sys.stderr.write("Network quotas are ignored since quota class" "is not supported.") else: + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ).id if compute_kwargs: compute_client.quotas.update( - project.id, + project, **compute_kwargs) if volume_kwargs: volume_client.quotas.update( - project.id, + project, **volume_kwargs) if network_kwargs: network_client.update_quota( - project.id, + project, **network_kwargs) @@ -220,15 +228,15 @@ class ShowQuota(command.ShowOne): return project def get_compute_volume_quota(self, client, parsed_args): - project = self._get_project(parsed_args) - try: if parsed_args.quota_class: - quota = client.quota_classes.get(project) - elif parsed_args.default: - quota = client.quotas.defaults(project) + quota = client.quota_classes.get(parsed_args.project) else: - quota = client.quotas.get(project) + project = self._get_project(parsed_args) + if parsed_args.default: + quota = client.quotas.defaults(project) + else: + quota = client.quotas.get(project) except Exception as e: if type(e).__name__ == 'EndpointNotFound': return {} @@ -237,11 +245,16 @@ class ShowQuota(command.ShowOne): return quota._info def get_network_quota(self, parsed_args): - if parsed_args.quota_class or parsed_args.default: + if parsed_args.quota_class: return {} if self.app.client_manager.is_network_endpoint_enabled(): project = self._get_project(parsed_args) - return self.app.client_manager.network.get_quota(project) + client = self.app.client_manager.network + if parsed_args.default: + network_quota = client.get_quota_default(project) + else: + network_quota = client.get_quota(project) + return network_quota else: return {} diff --git a/openstackclient/common/timing.py b/openstackclient/common/timing.py index facbec35..444f0cb2 100644 --- a/openstackclient/common/timing.py +++ b/openstackclient/common/timing.py @@ -14,12 +14,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.command.timing import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.command.timing\n" % __name__ + "Please use osc_lib.command.timing. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 73cd3dc9..aeb3aea7 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -14,12 +14,15 @@ # NOTE(dtroyer): This file is deprecated in Jun 2016, remove after 4.x release # or Jun 2017. +import inspect import sys from osc_lib.utils import * # noqa +parent_import = inspect.getouterframes(inspect.currentframe())[1][1] sys.stderr.write( "WARNING: %s is deprecated and will be removed after Jun 2017. " - "Please use osc_lib.utils\n" % __name__ + "Please use osc_lib.utils. This warning is caused by an " + "out-of-date import in %s\n" % (__name__, parent_import) ) diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 00625050..0222e899 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -17,6 +17,7 @@ import re +from novaclient import exceptions as nova_exceptions from osc_lib.command import command from osc_lib import utils import six @@ -94,17 +95,22 @@ class ShowHypervisor(command.ShowOne): if service_host in aggregate.hosts] hypervisor["aggregates"] = member_of - uptime = compute_client.hypervisors.uptime(hypervisor['id'])._info - # Extract data from uptime value - # format: 0 up 0, 0 users, load average: 0, 0, 0 - # example: 17:37:14 up 2:33, 3 users, load average: 0.33, 0.36, 0.34 - m = re.match("(.+)\sup\s+(.+),\s+(.+)\susers,\s+load average:\s(.+)", - uptime['uptime']) - if m: - hypervisor["host_time"] = m.group(1) - hypervisor["uptime"] = m.group(2) - hypervisor["users"] = m.group(3) - hypervisor["load_average"] = m.group(4) + try: + uptime = compute_client.hypervisors.uptime(hypervisor['id'])._info + # Extract data from uptime value + # format: 0 up 0, 0 users, load average: 0, 0, 0 + # example: 17:37:14 up 2:33, 3 users, + # load average: 0.33, 0.36, 0.34 + m = re.match( + "\s*(.+)\sup\s+(.+),\s+(.+)\susers?,\s+load average:\s(.+)", + uptime['uptime']) + if m: + hypervisor["host_time"] = m.group(1) + hypervisor["uptime"] = m.group(2) + hypervisor["users"] = m.group(3) + hypervisor["load_average"] = m.group(4) + except nova_exceptions.HTTPNotImplemented: + pass hypervisor["service_id"] = hypervisor["service"]["id"] hypervisor["service_host"] = hypervisor["service"]["host"] diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 3e6903b7..1ca31497 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1677,7 +1677,7 @@ class SshServer(command.Command): if parsed_args.login: login = parsed_args.login else: - login = self.app.client_manager._username + login = self.app.client_manager.auth_ref.username if parsed_args.verbose: cmd += " -v" diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index ccc02fd8..31b173e1 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -78,6 +78,10 @@ def _get_attrs(client_manager, parsed_args): parsed_args.availability_zone_hints is not None: attrs['availability_zone_hints'] = parsed_args.availability_zone_hints + # set description + if parsed_args.description: + attrs['description'] = parsed_args.description + # update_external_network_options if parsed_args.internal: attrs['router:external'] = False @@ -191,6 +195,11 @@ class CreateNetwork(common.NetworkAndComputeShowOne): metavar='<project>', help=_("Owner's project (name or ID)") ) + parser.add_argument( + '--description', + metavar='<description>', + help=_("Set network description") + ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--availability-zone-hint', @@ -420,6 +429,11 @@ class SetNetwork(command.Command): action='store_true', help=_("Do not share the network between projects") ) + parser.add_argument( + '--description', + metavar="<description", + help=_("Set network description") + ) port_security_group = parser.add_mutually_exclusive_group() port_security_group.add_argument( '--enable-port-security', diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 05c5a012..0df78e43 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -346,15 +346,26 @@ class ListPort(command.Lister): "network:dhcp).") ) parser.add_argument( + '--network', + metavar='<network>', + help=_("List only ports connected to this network (name or ID)")) + device_group = parser.add_mutually_exclusive_group() + device_group.add_argument( '--router', metavar='<router>', dest='router', help=_("List only ports attached to this router (name or ID)") ) + device_group.add_argument( + '--server', + metavar='<server>', + help=_("List only ports attached to this server (name or ID)"), + ) return parser def take_action(self, parsed_args): - client = self.app.client_manager.network + network_client = self.app.client_manager.network + compute_client = self.app.client_manager.compute columns = ( 'id', @@ -373,11 +384,19 @@ class ListPort(command.Lister): if parsed_args.device_owner is not None: filters['device_owner'] = parsed_args.device_owner if parsed_args.router: - _router = client.find_router(parsed_args.router, - ignore_missing=False) + _router = network_client.find_router(parsed_args.router, + ignore_missing=False) filters['device_id'] = _router.id - - data = client.ports(**filters) + if parsed_args.server: + server = utils.find_resource(compute_client.servers, + parsed_args.server) + filters['device_id'] = server.id + if parsed_args.network: + network = network_client.find_network(parsed_args.network, + ignore_missing=False) + filters['network_id'] = network.id + + data = network_client.ports(**filters) return (column_headers, (utils.get_item_properties( diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index f6d96d03..03134b8c 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -119,7 +119,7 @@ class AddPortToRouter(command.Command): def take_action(self, parsed_args): client = self.app.client_manager.network port = client.find_port(parsed_args.port, ignore_missing=False) - client.router_add_interface(client.find_router( + client.add_interface_to_router(client.find_router( parsed_args.router, ignore_missing=False), port_id=port.id) @@ -144,7 +144,7 @@ class AddSubnetToRouter(command.Command): client = self.app.client_manager.network subnet = client.find_subnet(parsed_args.subnet, ignore_missing=False) - client.router_add_interface( + client.add_interface_to_router( client.find_router(parsed_args.router, ignore_missing=False), subnet_id=subnet.id) @@ -281,13 +281,20 @@ class ListRouter(command.Lister): columns = columns + ( 'routes', 'external_gateway_info', - 'availability_zones' ) column_headers = column_headers + ( 'Routes', 'External gateway info', - 'Availability zones' ) + # availability zone will be available only when + # router_availability_zone extension is enabled + if client.find_extension("router_availability_zone"): + columns = columns + ( + 'availability_zones', + ) + column_headers = column_headers + ( + 'Availability zones', + ) data = client.routers() return (column_headers, @@ -317,7 +324,7 @@ class RemovePortFromRouter(command.Command): def take_action(self, parsed_args): client = self.app.client_manager.network port = client.find_port(parsed_args.port, ignore_missing=False) - client.router_remove_interface(client.find_router( + client.remove_interface_from_router(client.find_router( parsed_args.router, ignore_missing=False), port_id=port.id) @@ -342,7 +349,7 @@ class RemoveSubnetFromRouter(command.Command): client = self.app.client_manager.network subnet = client.find_subnet(parsed_args.subnet, ignore_missing=False) - client.router_remove_interface( + client.remove_interface_from_router( client.find_router(parsed_args.router, ignore_missing=False), subnet_id=subnet.id) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 6feb8aa0..76453487 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -28,9 +28,14 @@ from openstackclient.identity import common as identity_common LOG = logging.getLogger(__name__) -def _update_arguments(obj_list, parsed_args_list): +def _update_arguments(obj_list, parsed_args_list, option): for item in parsed_args_list: - obj_list.remove(item) + try: + obj_list.remove(item) + except ValueError: + msg = (_("Subnet does not contain %(option)s %(value)s") % + {'option': option, 'value': item}) + raise exceptions.CommandError(msg) def _format_allocation_pools(data): @@ -191,6 +196,8 @@ def _get_attrs(client_manager, parsed_args, is_create=True): if ('service_types' in parsed_args and parsed_args.service_types is not None): attrs['service_types'] = parsed_args.service_types + if parsed_args.description is not None: + attrs['description'] = parsed_args.description return attrs @@ -289,6 +296,11 @@ class CreateSubnet(command.ShowOne): metavar='<network>', help=_("Network this subnet belongs to (name or ID)") ) + parser.add_argument( + '--description', + metavar='<description>', + help=_("Set subnet description") + ) _get_common_parse_arguments(parser) return parser @@ -376,9 +388,41 @@ class ListSubnet(command.Lister): "Must be a valid device owner value for a network port " "(repeat option to list multiple service types)") ) + parser.add_argument( + '--project', + metavar='<project>', + help=_("List only subnets which belong to a given project " + "(name or ID) in output") + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--network', + metavar='<network>', + help=_("List only subnets which belong to a given network " + "(name or ID) in output") + ) + parser.add_argument( + '--gateway', + metavar='<gateway>', + help=_("List only subnets of given gateway IP in output") + ) + parser.add_argument( + '--name', + metavar='<name>', + help=_("List only subnets of given name in output") + ) + parser.add_argument( + '--subnet-range', + metavar='<subnet-range>', + help=_("List only subnets of given subnet range " + "(in CIDR notation) in output " + "e.g.: --subnet-range 10.10.0.0/16") + ) return parser def take_action(self, parsed_args): + identity_client = self.app.client_manager.identity + network_client = self.app.client_manager.network filters = {} if parsed_args.ip_version: filters['ip_version'] = parsed_args.ip_version @@ -388,7 +432,24 @@ class ListSubnet(command.Lister): filters['enable_dhcp'] = False if parsed_args.service_types: filters['service_types'] = parsed_args.service_types - data = self.app.client_manager.network.subnets(**filters) + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + filters['tenant_id'] = project_id + if parsed_args.network: + network_id = network_client.find_network(parsed_args.network, + ignore_missing=False).id + filters['network_id'] = network_id + if parsed_args.gateway: + filters['gateway_ip'] = parsed_args.gateway + if parsed_args.name: + filters['name'] = parsed_args.name + if parsed_args.subnet_range: + filters['cidr'] = parsed_args.subnet_range + data = network_client.subnets(**filters) headers = ('ID', 'Name', 'Network', 'Subnet') columns = ('id', 'name', 'network_id', 'cidr') @@ -442,6 +503,11 @@ class SetSubnet(command.Command): "'none': This subnet will not use a gateway, " "e.g.: --gateway 192.168.9.1, --gateway none.") ) + parser.add_argument( + '--description', + metavar='<description>', + help=_("Set subnet description") + ) _get_common_parse_arguments(parser) return parser @@ -493,9 +559,9 @@ class UnsetSubnet(command.Command): dest='allocation_pools', action=parseractions.MultiKeyValueAction, required_keys=['start', 'end'], - help=_('Allocation pool to be removed from this subnet ' - 'e.g.: start=192.168.199.2,end=192.168.199.254 ' - '(repeat option to unset multiple Allocation pools)') + help=_('Allocation pool IP addresses to be removed from this ' + 'subnet e.g.: start=192.168.199.2,end=192.168.199.254 ' + '(repeat option to unset multiple allocation pools)') ) parser.add_argument( '--dns-nameserver', @@ -503,7 +569,7 @@ class UnsetSubnet(command.Command): action='append', dest='dns_nameservers', help=_('DNS server to be removed from this subnet ' - '(repeat option to set multiple DNS servers)') + '(repeat option to unset multiple DNS servers)') ) parser.add_argument( '--host-route', @@ -540,39 +606,25 @@ class UnsetSubnet(command.Command): tmp_obj = copy.deepcopy(obj) attrs = {} if parsed_args.dns_nameservers: - try: - _update_arguments(tmp_obj.dns_nameservers, - parsed_args.dns_nameservers) - except ValueError as error: - msg = (_("%s not in dns-nameservers") % str(error)) - raise exceptions.CommandError(msg) + _update_arguments(tmp_obj.dns_nameservers, + parsed_args.dns_nameservers, + 'dns-nameserver') attrs['dns_nameservers'] = tmp_obj.dns_nameservers if parsed_args.host_routes: - try: - _update_arguments( - tmp_obj.host_routes, - convert_entries_to_nexthop(parsed_args.host_routes)) - except ValueError as error: - msg = (_("Subnet does not have %s in host-routes") % - str(error)) - raise exceptions.CommandError(msg) + _update_arguments( + tmp_obj.host_routes, + convert_entries_to_nexthop(parsed_args.host_routes), + 'host-route') attrs['host_routes'] = tmp_obj.host_routes if parsed_args.allocation_pools: - try: - _update_arguments(tmp_obj.allocation_pools, - parsed_args.allocation_pools) - except ValueError as error: - msg = (_("Subnet does not have %s in allocation-pools") % - str(error)) - raise exceptions.CommandError(msg) + _update_arguments(tmp_obj.allocation_pools, + parsed_args.allocation_pools, + 'allocation-pool') attrs['allocation_pools'] = tmp_obj.allocation_pools if parsed_args.service_types: - try: - _update_arguments(tmp_obj.service_types, - parsed_args.service_types) - except ValueError as error: - msg = (_("%s not in service-types") % str(error)) - raise exceptions.CommandError(msg) + _update_arguments(tmp_obj.service_types, + parsed_args.service_types, + 'service-type') attrs['service_types'] = tmp_obj.service_types if attrs: client.update_subnet(obj, **attrs) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index d3fab8ac..a01d2f7b 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -81,6 +81,9 @@ def _get_attrs(client_manager, parsed_args): ).id attrs['tenant_id'] = project_id + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + return attrs @@ -167,6 +170,11 @@ class CreateSubnetPool(command.ShowOne): action='store_true', help=_("Set this subnet pool as not shared"), ) + parser.add_argument( + '--description', + metavar='<description>', + help=_("Set subnet pool description") + ) return parser def take_action(self, parsed_args): @@ -226,41 +234,82 @@ class ListSubnetPool(command.Lister): default=False, help=_("List additional fields in output") ) + shared_group = parser.add_mutually_exclusive_group() + shared_group.add_argument( + '--share', + action='store_true', + help=_("List subnets shared between projects"), + ) + shared_group.add_argument( + '--no-share', + action='store_true', + help=_("List subnets not shared between projects"), + ) + default_group = parser.add_mutually_exclusive_group() + default_group.add_argument( + '--default', + action='store_true', + help=_("List subnets used as the default external subnet pool"), + ) + default_group.add_argument( + '--no-default', + action='store_true', + help=_("List subnets not used as the default external subnet pool") + ) + parser.add_argument( + '--project', + metavar='<project>', + help=_("List subnets according to their project (name or ID)") + ) + identity_common.add_project_domain_option_to_parser(parser) + parser.add_argument( + '--name', + metavar='<name>', + help=_("List only subnets of given name in output") + ) + parser.add_argument( + '--address-scope', + metavar='<address-scope>', + help=_("List only subnets of given address scope (name or ID) " + "in output") + ) return parser def take_action(self, parsed_args): - data = self.app.client_manager.network.subnet_pools() - + identity_client = self.app.client_manager.identity + network_client = self.app.client_manager.network + filters = {} + if parsed_args.share: + filters['shared'] = True + elif parsed_args.no_share: + filters['shared'] = False + if parsed_args.default: + filters['is_default'] = True + elif parsed_args.no_default: + filters['is_default'] = False + if parsed_args.project: + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + filters['tenant_id'] = project_id + if parsed_args.name is not None: + filters['name'] = parsed_args.name + if parsed_args.address_scope: + address_scope = network_client.find_address_scope( + parsed_args.address_scope, + ignore_missing=False) + filters['address_scope_id'] = address_scope.id + data = network_client.subnet_pools(**filters) + + headers = ('ID', 'Name', 'Prefixes') + columns = ('id', 'name', 'prefixes') if parsed_args.long: - headers = ( - 'ID', - 'Name', - 'Prefixes', - 'Default Prefix Length', - 'Address Scope', - 'Default Subnet Pool', - 'Shared', - ) - columns = ( - 'id', - 'name', - 'prefixes', - 'default_prefixlen', - 'address_scope_id', - 'is_default', - 'shared', - ) - else: - headers = ( - 'ID', - 'Name', - 'Prefixes', - ) - columns = ( - 'id', - 'name', - 'prefixes', - ) + headers += ('Default Prefix Length', 'Address Scope', + 'Default Subnet Pool', 'Shared') + columns += ('default_prefixlen', 'address_scope_id', + 'is_default', 'shared') return (headers, (utils.get_item_properties( @@ -299,6 +348,11 @@ class SetSubnetPool(command.Command): help=_("Remove address scope associated with the subnet pool") ) _add_default_options(parser) + parser.add_argument( + '--description', + metavar='<description>', + help=_("Set subnet pool description") + ) return parser diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index fd45be38..c1de9aa9 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -17,6 +17,7 @@ class QuotaTests(base.TestCase): """Functional tests for quota. """ # Test quota information for compute, network and volume. EXPECTED_FIELDS = ['instances', 'networks', 'volumes'] + EXPECTED_CLASS_FIELDS = ['instances', 'volumes'] PROJECT_NAME = None @classmethod @@ -25,8 +26,8 @@ class QuotaTests(base.TestCase): cls.get_openstack_configuration_value('auth.project_name') def test_quota_set(self): - self.openstack('quota set --instances 11 --volumes 11 --networks 11 ' - + self.PROJECT_NAME) + self.openstack('quota set --instances 11 --volumes 11 --networks 11 ' + + self.PROJECT_NAME) opts = self.get_opts(self.EXPECTED_FIELDS) raw_output = self.openstack('quota show ' + self.PROJECT_NAME + opts) self.assertEqual("11\n11\n11\n", raw_output) @@ -40,3 +41,22 @@ class QuotaTests(base.TestCase): raw_output = self.openstack('quota show') for expected_field in self.EXPECTED_FIELDS: self.assertIn(expected_field, raw_output) + + def test_quota_show_with_default_option(self): + raw_output = self.openstack('quota show --default') + for expected_field in self.EXPECTED_FIELDS: + self.assertIn(expected_field, raw_output) + + def test_quota_show_with_class_option(self): + raw_output = self.openstack('quota show --class') + for expected_field in self.EXPECTED_CLASS_FIELDS: + self.assertIn(expected_field, raw_output) + + def test_quota_class_set(self): + class_name = 'default' + class_expected_fields = ['instances', 'volumes'] + self.openstack('quota set --instances 11 --volumes 11 --class ' + + class_name) + opts = self.get_opts(class_expected_fields) + raw_output = self.openstack('quota show --class ' + class_name + opts) + self.assertEqual("11\n11\n", raw_output) diff --git a/openstackclient/tests/functional/post_test_hook.sh b/openstackclient/tests/functional/post_test_hook.sh index e555470d..b7a39cfe 100755 --- a/openstackclient/tests/functional/post_test_hook.sh +++ b/openstackclient/tests/functional/post_test_hook.sh @@ -18,16 +18,16 @@ function generate_testr_results { fi } -OPENSTACKCLIENT_DIR=$(cd $(dirname "$0") && pwd) +export OPENSTACKCLIENT_DIR="$BASE/new/python-openstackclient" sudo chown -R jenkins:stack $OPENSTACKCLIENT_DIR +# Go to the openstackclient dir +cd $OPENSTACKCLIENT_DIR + # Run tests echo "Running openstackclient functional test suite" set +e -# Go to the openstackclient dir -cd $OPENSTACKCLIENT_DIR - # Source environment variables to kick things off source ~stack/devstack/openrc admin admin echo 'Running tests with:' diff --git a/openstackclient/tests/functional/volume/v1/test_transfer_request.py b/openstackclient/tests/functional/volume/v1/test_transfer_request.py new file mode 100644 index 00000000..d8406b02 --- /dev/null +++ b/openstackclient/tests/functional/volume/v1/test_transfer_request.py @@ -0,0 +1,53 @@ +# 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 uuid + +from openstackclient.tests.functional.volume.v1 import common + + +class TransferRequestTests(common.BaseVolumeTests): + """Functional tests for transfer request. """ + + NAME = uuid.uuid4().hex + VOLUME_NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + super(TransferRequestTests, cls).setUpClass() + opts = cls.get_opts(['display_name']) + raw_output = cls.openstack( + 'volume create --size 1 ' + cls.VOLUME_NAME + opts) + cls.assertOutput(cls.VOLUME_NAME + '\n', raw_output) + + opts = cls.get_opts(cls.FIELDS) + raw_output = cls.openstack( + 'volume transfer request create ' + + cls.VOLUME_NAME + + ' --name ' + cls.NAME + opts) + cls.assertOutput(cls.NAME + '\n', raw_output) + + @classmethod + def tearDownClass(cls): + raw_output_transfer = cls.openstack( + 'volume transfer request delete ' + cls.NAME) + raw_output_volume = cls.openstack( + 'volume delete ' + cls.VOLUME_NAME) + cls.assertOutput('', raw_output_transfer) + cls.assertOutput('', raw_output_volume) + + def test_volume_transfer_request_list(self): + opts = self.get_opts(self.HEADERS) + raw_output = self.openstack('volume transfer request list' + opts) + self.assertIn(self.NAME, raw_output) diff --git a/openstackclient/tests/functional/volume/v1/test_volume.py b/openstackclient/tests/functional/volume/v1/test_volume.py index 6ac7f2bf..5e4bcbea 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume.py +++ b/openstackclient/tests/functional/volume/v1/test_volume.py @@ -75,3 +75,14 @@ class VolumeTests(common.BaseVolumeTests): opts = self.get_opts(["display_name", "size"]) raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n2\n", raw_output) + + def test_volume_set_bootable(self): + self.openstack('volume set --bootable ' + self.NAME) + opts = self.get_opts(["bootable"]) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual("true\n", raw_output) + + self.openstack('volume set --non-bootable ' + self.NAME) + opts = self.get_opts(["bootable"]) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual("false\n", raw_output) diff --git a/openstackclient/tests/functional/volume/v1/test_volume_type.py b/openstackclient/tests/functional/volume/v1/test_volume_type.py index 538545ab..955759b6 100644 --- a/openstackclient/tests/functional/volume/v1/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v1/test_volume_type.py @@ -61,6 +61,21 @@ class VolumeTypeTests(common.BaseVolumeTests): raw_output = self.openstack('volume type show ' + self.NAME + opts) self.assertEqual("c='d'\n", raw_output) + def test_volume_type_set_unset_multiple_properties(self): + raw_output = self.openstack( + 'volume type set --property a=b --property c=d ' + self.NAME) + self.assertEqual("", raw_output) + + opts = self.get_opts(["properties"]) + raw_output = self.openstack('volume type show ' + self.NAME + opts) + self.assertEqual("a='b', c='d'\n", raw_output) + + raw_output = self.openstack( + 'volume type unset --property a --property c ' + self.NAME) + self.assertEqual("", raw_output) + raw_output = self.openstack('volume type show ' + self.NAME + opts) + self.assertEqual("\n", raw_output) + def test_multi_delete(self): vol_type1 = uuid.uuid4().hex vol_type2 = uuid.uuid4().hex diff --git a/openstackclient/tests/functional/volume/v2/test_transfer_request.py b/openstackclient/tests/functional/volume/v2/test_transfer_request.py new file mode 100644 index 00000000..4f02238b --- /dev/null +++ b/openstackclient/tests/functional/volume/v2/test_transfer_request.py @@ -0,0 +1,53 @@ +# 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 uuid + +from openstackclient.tests.functional.volume.v2 import common + + +class TransferRequestTests(common.BaseVolumeTests): + """Functional tests for transfer request. """ + + NAME = uuid.uuid4().hex + VOLUME_NAME = uuid.uuid4().hex + HEADERS = ['Name'] + FIELDS = ['name'] + + @classmethod + def setUpClass(cls): + super(TransferRequestTests, cls).setUpClass() + opts = cls.get_opts(cls.FIELDS) + + raw_output = cls.openstack( + 'volume create --size 1 ' + cls.VOLUME_NAME + opts) + cls.assertOutput(cls.VOLUME_NAME + '\n', raw_output) + + raw_output = cls.openstack( + 'volume transfer request create ' + + cls.VOLUME_NAME + + ' --name ' + cls.NAME + opts) + cls.assertOutput(cls.NAME + '\n', raw_output) + + @classmethod + def tearDownClass(cls): + raw_output_transfer = cls.openstack( + 'volume transfer request delete ' + cls.NAME) + raw_output_volume = cls.openstack( + 'volume delete ' + cls.VOLUME_NAME) + cls.assertOutput('', raw_output_transfer) + cls.assertOutput('', raw_output_volume) + + def test_volume_transfer_request_list(self): + opts = self.get_opts(self.HEADERS) + raw_output = self.openstack('volume transfer request list' + opts) + self.assertIn(self.NAME, raw_output) diff --git a/openstackclient/tests/functional/volume/v2/test_volume.py b/openstackclient/tests/functional/volume/v2/test_volume.py index 73273573..fb880578 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume.py +++ b/openstackclient/tests/functional/volume/v2/test_volume.py @@ -91,6 +91,17 @@ class VolumeTests(common.BaseVolumeTests): raw_output = self.openstack('volume show ' + self.NAME + opts) self.assertEqual(self.NAME + "\n2\n", raw_output) + def test_volume_set_bootable(self): + self.openstack('volume set --bootable ' + self.NAME) + opts = self.get_opts(["bootable"]) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual("true\n", raw_output) + + self.openstack('volume set --non-bootable ' + self.NAME) + opts = self.get_opts(["bootable"]) + raw_output = self.openstack('volume show ' + self.NAME + opts) + self.assertEqual("false\n", raw_output) + def test_volume_snapshot(self): opts = self.get_opts(self.FIELDS) diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py index b62cbb39..d8bd3a96 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -62,6 +62,21 @@ class VolumeTypeTests(common.BaseVolumeTests): raw_output = self.openstack('volume type show ' + self.NAME + opts) self.assertEqual("c='d'\n", raw_output) + def test_volume_type_set_unset_multiple_properties(self): + raw_output = self.openstack( + 'volume type set --property a=b --property c=d ' + self.NAME) + self.assertEqual("", raw_output) + + opts = self.get_opts(["properties"]) + raw_output = self.openstack('volume type show ' + self.NAME + opts) + self.assertEqual("a='b', c='d'\n", raw_output) + + raw_output = self.openstack( + 'volume type unset --property a --property c ' + self.NAME) + self.assertEqual("", raw_output) + raw_output = self.openstack('volume type show ' + self.NAME + opts) + self.assertEqual("\n", raw_output) + def test_volume_type_set_unset_project(self): raw_output = self.openstack( 'volume type set --project admin ' + self.NAME) diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 4a80a2b2..7dd23373 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -18,6 +18,7 @@ from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit.volume.v2 import fakes as volume_fakes class FakeQuotaResource(fakes.FakeResource): @@ -43,10 +44,7 @@ class TestQuota(compute_fakes.TestComputev2): 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 = self.app.client_manager.volume.quotas self.volume_quotas_mock.reset_mock() self.volume_quotas_class_mock = \ self.app.client_manager.volume.quota_classes @@ -66,33 +64,33 @@ class TestQuotaSet(TestQuota): def setUp(self): super(TestQuotaSet, self).setUp() - self.quotas_mock.find.return_value = FakeQuotaResource( + self.quotas_mock.update.return_value = FakeQuotaResource( None, copy.deepcopy(compute_fakes.QUOTA), loaded=True, ) - self.quotas_mock.update.return_value = FakeQuotaResource( + self.volume_quotas_mock.update.return_value = FakeQuotaResource( None, copy.deepcopy(compute_fakes.QUOTA), loaded=True, ) - self.volume_quotas_mock.find.return_value = FakeQuotaResource( + self.projects_mock.get.return_value = fakes.FakeResource( None, - copy.deepcopy(compute_fakes.QUOTA), + copy.deepcopy(identity_fakes.PROJECT), loaded=True, ) - self.volume_quotas_mock.update.return_value = FakeQuotaResource( + self.quotas_class_mock.update.return_value = FakeQuotaResource( None, copy.deepcopy(compute_fakes.QUOTA), loaded=True, ) - self.projects_mock.get.return_value = fakes.FakeResource( + self.volume_quotas_class_mock.update.return_value = FakeQuotaResource( None, - copy.deepcopy(identity_fakes.PROJECT), + copy.deepcopy(compute_fakes.QUOTA), loaded=True, ) @@ -141,7 +139,7 @@ class TestQuotaSet(TestQuota): self.app.client_manager.network_endpoint_enabled = False parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'floating_ips': compute_fakes.floating_ip_num, @@ -161,39 +159,95 @@ class TestQuotaSet(TestQuota): 'server_group_members': compute_fakes.servgroup_members_num, } - self.quotas_mock.update.assert_called_with( + self.quotas_mock.update.assert_called_once_with( identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) 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), + '--gigabytes', str(volume_fakes.QUOTA['gigabytes']), + '--snapshots', str(volume_fakes.QUOTA['snapshots']), + '--volumes', str(volume_fakes.QUOTA['volumes']), + '--backups', str(volume_fakes.QUOTA['backups']), + '--backup-gigabytes', str(volume_fakes.QUOTA['backup_gigabytes']), + '--per-volume-gigabytes', + str(volume_fakes.QUOTA['per_volume_gigabytes']), identity_fakes.project_name, ] verifylist = [ - ('gigabytes', compute_fakes.floating_ip_num), - ('snapshots', compute_fakes.fix_ip_num), - ('volumes', compute_fakes.injected_file_num), + ('gigabytes', volume_fakes.QUOTA['gigabytes']), + ('snapshots', volume_fakes.QUOTA['snapshots']), + ('volumes', volume_fakes.QUOTA['volumes']), + ('backups', volume_fakes.QUOTA['backups']), + ('backup_gigabytes', volume_fakes.QUOTA['backup_gigabytes']), + ('per_volume_gigabytes', + volume_fakes.QUOTA['per_volume_gigabytes']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { - 'gigabytes': compute_fakes.floating_ip_num, - 'snapshots': compute_fakes.fix_ip_num, - 'volumes': compute_fakes.injected_file_num, + 'gigabytes': volume_fakes.QUOTA['gigabytes'], + 'snapshots': volume_fakes.QUOTA['snapshots'], + 'volumes': volume_fakes.QUOTA['volumes'], + 'backups': volume_fakes.QUOTA['backups'], + 'backup_gigabytes': volume_fakes.QUOTA['backup_gigabytes'], + 'per_volume_gigabytes': volume_fakes.QUOTA['per_volume_gigabytes'] } - self.volume_quotas_mock.update.assert_called_with( + self.volume_quotas_mock.update.assert_called_once_with( identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) + + def test_quota_set_volume_with_volume_type(self): + arglist = [ + '--gigabytes', str(volume_fakes.QUOTA['gigabytes']), + '--snapshots', str(volume_fakes.QUOTA['snapshots']), + '--volumes', str(volume_fakes.QUOTA['volumes']), + '--backups', str(volume_fakes.QUOTA['backups']), + '--backup-gigabytes', str(volume_fakes.QUOTA['backup_gigabytes']), + '--per-volume-gigabytes', + str(volume_fakes.QUOTA['per_volume_gigabytes']), + '--volume-type', 'volume_type_backend', + identity_fakes.project_name, + ] + verifylist = [ + ('gigabytes', volume_fakes.QUOTA['gigabytes']), + ('snapshots', volume_fakes.QUOTA['snapshots']), + ('volumes', volume_fakes.QUOTA['volumes']), + ('backups', volume_fakes.QUOTA['backups']), + ('backup_gigabytes', volume_fakes.QUOTA['backup_gigabytes']), + ('per_volume_gigabytes', + volume_fakes.QUOTA['per_volume_gigabytes']), + ('volume_type', 'volume_type_backend'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs = { + 'gigabytes_volume_type_backend': volume_fakes.QUOTA['gigabytes'], + 'snapshots_volume_type_backend': volume_fakes.QUOTA['snapshots'], + 'volumes_volume_type_backend': volume_fakes.QUOTA['volumes'], + 'backups': volume_fakes.QUOTA['backups'], + 'backup_gigabytes': volume_fakes.QUOTA['backup_gigabytes'], + 'per_volume_gigabytes': volume_fakes.QUOTA['per_volume_gigabytes'] + } + + self.volume_quotas_mock.update.assert_called_once_with( + identity_fakes.project_id, + **kwargs + ) + self.assertIsNone(result) + def test_quota_set_network(self): arglist = [ '--subnets', str(network_fakes.QUOTA['subnet']), @@ -207,8 +261,8 @@ class TestQuotaSet(TestQuota): '--rbac-policies', str(network_fakes.QUOTA['rbac_policy']), '--ports', str(network_fakes.QUOTA['port']), '--vips', str(network_fakes.QUOTA['vip']), - '--members', str(network_fakes.QUOTA['member']), - '--health-monitors', str(network_fakes.QUOTA['health_monitor']), + '--health-monitors', str(network_fakes.QUOTA['healthmonitor']), + '--l7policies', str(network_fakes.QUOTA['l7policy']), identity_fakes.project_name, ] verifylist = [ @@ -223,12 +277,12 @@ class TestQuotaSet(TestQuota): ('rbac_policy', network_fakes.QUOTA['rbac_policy']), ('port', network_fakes.QUOTA['port']), ('vip', network_fakes.QUOTA['vip']), - ('member', network_fakes.QUOTA['member']), - ('health_monitor', network_fakes.QUOTA['health_monitor']), + ('healthmonitor', network_fakes.QUOTA['healthmonitor']), + ('l7policy', network_fakes.QUOTA['l7policy']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.cmd.take_action(parsed_args) + result = self.cmd.take_action(parsed_args) kwargs = { 'subnet': network_fakes.QUOTA['subnet'], 'network': network_fakes.QUOTA['network'], @@ -241,13 +295,86 @@ class TestQuotaSet(TestQuota): 'rbac_policy': network_fakes.QUOTA['rbac_policy'], 'port': network_fakes.QUOTA['port'], 'vip': network_fakes.QUOTA['vip'], - 'member': network_fakes.QUOTA['member'], - 'health_monitor': network_fakes.QUOTA['health_monitor'], + 'healthmonitor': network_fakes.QUOTA['healthmonitor'], + 'l7policy': network_fakes.QUOTA['l7policy'], } - self.network_mock.update_quota.assert_called_with( + self.network_mock.update_quota.assert_called_once_with( identity_fakes.project_id, **kwargs ) + self.assertIsNone(result) + + def test_quota_set_with_class(self): + arglist = [ + '--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), + '--cores', str(compute_fakes.core_num), + '--ram', str(compute_fakes.ram_num), + '--instances', str(compute_fakes.instance_num), + '--properties', str(compute_fakes.property_num), + '--server-groups', str(compute_fakes.servgroup_num), + '--server-group-members', str(compute_fakes.servgroup_members_num), + '--gigabytes', str(compute_fakes.floating_ip_num), + '--snapshots', str(compute_fakes.fix_ip_num), + '--volumes', str(volume_fakes.QUOTA['volumes']), + '--network', str(network_fakes.QUOTA['network']), + '--class', identity_fakes.project_name, + ] + verifylist = [ + ('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), + ('server_groups', compute_fakes.servgroup_num), + ('server_group_members', compute_fakes.servgroup_members_num), + ('gigabytes', compute_fakes.floating_ip_num), + ('snapshots', compute_fakes.fix_ip_num), + ('volumes', volume_fakes.QUOTA['volumes']), + ('network', network_fakes.QUOTA['network']), + ('project', identity_fakes.project_name), + ('quota_class', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs_compute = { + '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, + 'server_groups': compute_fakes.servgroup_num, + 'server_group_members': compute_fakes.servgroup_members_num, + } + kwargs_volume = { + 'gigabytes': compute_fakes.floating_ip_num, + 'snapshots': compute_fakes.fix_ip_num, + 'volumes': volume_fakes.QUOTA['volumes'], + } + + self.quotas_class_mock.update.assert_called_with( + identity_fakes.project_name, + **kwargs_compute + ) + self.volume_quotas_class_mock.update.assert_called_with( + identity_fakes.project_name, + **kwargs_volume + ) + self.assertNotCalled(self.network_mock.update_quota) + self.assertIsNone(result) class TestQuotaShow(TestQuota): @@ -269,13 +396,13 @@ class TestQuotaShow(TestQuota): self.volume_quotas_mock.get.return_value = FakeQuotaResource( None, - copy.deepcopy(compute_fakes.QUOTA), + copy.deepcopy(volume_fakes.QUOTA), loaded=True, ) self.volume_quotas_mock.defaults.return_value = FakeQuotaResource( None, - copy.deepcopy(compute_fakes.QUOTA), + copy.deepcopy(volume_fakes.QUOTA), loaded=True, ) @@ -297,7 +424,7 @@ class TestQuotaShow(TestQuota): self.volume_quotas_class_mock.get.return_value = FakeQuotaResource( None, - copy.deepcopy(compute_fakes.QUOTA), + copy.deepcopy(volume_fakes.QUOTA), loaded=True, ) @@ -313,6 +440,8 @@ class TestQuotaShow(TestQuota): ) self.network = self.app.client_manager.network self.network.get_quota = mock.Mock(return_value=network_fakes.QUOTA) + self.network.get_quota_default = mock.Mock( + return_value=network_fakes.QUOTA) self.cmd = quota.ShowQuota(self.app, None) @@ -328,10 +457,12 @@ class TestQuotaShow(TestQuota): self.cmd.take_action(parsed_args) - self.quotas_mock.get.assert_called_with(identity_fakes.project_id) - self.volume_quotas_mock.get.assert_called_with( + self.quotas_mock.get.assert_called_once_with(identity_fakes.project_id) + self.volume_quotas_mock.get.assert_called_once_with( identity_fakes.project_id) - self.network.get_quota.assert_called_with(identity_fakes.project_id) + self.network.get_quota.assert_called_once_with( + identity_fakes.project_id) + self.assertNotCalled(self.network.get_quota_default) def test_quota_show_with_default(self): arglist = [ @@ -347,9 +478,13 @@ class TestQuotaShow(TestQuota): self.cmd.take_action(parsed_args) - self.quotas_mock.defaults.assert_called_with(identity_fakes.project_id) - self.volume_quotas_mock.defaults.assert_called_with( + self.quotas_mock.defaults.assert_called_once_with( + identity_fakes.project_id) + self.volume_quotas_mock.defaults.assert_called_once_with( + identity_fakes.project_id) + self.network.get_quota_default.assert_called_once_with( identity_fakes.project_id) + self.assertNotCalled(self.network.get_quota) def test_quota_show_with_class(self): arglist = [ @@ -365,17 +500,21 @@ class TestQuotaShow(TestQuota): self.cmd.take_action(parsed_args) - self.quotas_class_mock.get.assert_called_with( - identity_fakes.project_id) - self.volume_quotas_class_mock.get.assert_called_with( - identity_fakes.project_id) + self.quotas_class_mock.get.assert_called_once_with( + identity_fakes.project_name) + self.volume_quotas_class_mock.get.assert_called_once_with( + identity_fakes.project_name) + self.assertNotCalled(self.network.get_quota) + self.assertNotCalled(self.network.get_quota_default) def test_quota_show_no_project(self): parsed_args = self.check_parser(self.cmd, [], []) self.cmd.take_action(parsed_args) - self.quotas_mock.get.assert_called_with(identity_fakes.project_id) - self.volume_quotas_mock.get.assert_called_with( + self.quotas_mock.get.assert_called_once_with(identity_fakes.project_id) + self.volume_quotas_mock.get.assert_called_once_with( + identity_fakes.project_id) + self.network.get_quota.assert_called_once_with( identity_fakes.project_id) - self.network.get_quota.assert_called_with(identity_fakes.project_id) + self.assertNotCalled(self.network.get_quota_default) diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 0e3d47ba..3c829773 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -128,7 +128,7 @@ class FakeAggregate(object): """ if aggregates is None: aggregates = FakeAggregate.create_aggregates(count) - return mock.MagicMock(side_effect=aggregates) + return mock.Mock(side_effect=aggregates) class FakeComputev2Client(object): @@ -519,7 +519,7 @@ class FakeSecurityGroup(object): """ if security_groups is None: security_groups = FakeSecurityGroup.create_security_groups(count) - return mock.MagicMock(side_effect=security_groups) + return mock.Mock(side_effect=security_groups) class FakeSecurityGroupRule(object): @@ -649,7 +649,7 @@ class FakeServer(object): """ if servers is None: servers = FakeServer.create_servers(count) - return mock.MagicMock(side_effect=servers) + return mock.Mock(side_effect=servers) class FakeService(object): @@ -788,7 +788,7 @@ class FakeFlavor(object): """ if flavors is None: flavors = FakeFlavor.create_flavors(count) - return mock.MagicMock(side_effect=flavors) + return mock.Mock(side_effect=flavors) class FakeFlavorAccess(object): @@ -887,7 +887,7 @@ class FakeKeypair(object): """ if keypairs is None: keypairs = FakeKeypair.create_keypairs(count) - return mock.MagicMock(side_effect=keypairs) + return mock.Mock(side_effect=keypairs) class FakeAvailabilityZone(object): @@ -1011,7 +1011,7 @@ class FakeFloatingIP(object): """ if floating_ips is None: floating_ips = FakeFloatingIP.create_floating_ips(count) - return mock.MagicMock(side_effect=floating_ips) + return mock.Mock(side_effect=floating_ips) class FakeFloatingIPPool(object): diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index ace650eb..93ad9d14 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -292,7 +292,7 @@ class TestFlavorDelete(TestFlavor): find_mock_result = [self.flavors[0], exceptions.CommandError] self.flavors_mock.get = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) self.flavors_mock.find.side_effect = exceptions.NotFound(None) diff --git a/openstackclient/tests/unit/compute/v2/test_hypervisor.py b/openstackclient/tests/unit/compute/v2/test_hypervisor.py index d94a107c..e39570af 100644 --- a/openstackclient/tests/unit/compute/v2/test_hypervisor.py +++ b/openstackclient/tests/unit/compute/v2/test_hypervisor.py @@ -15,6 +15,7 @@ import copy +from novaclient import exceptions as nova_exceptions from osc_lib import exceptions from openstackclient.compute.v2 import hypervisor @@ -159,10 +160,12 @@ class TestHypervisorShow(TestHypervisor): 'free_disk_gb', 'free_ram_mb', 'host_ip', + 'host_time', 'hypervisor_hostname', 'hypervisor_type', 'hypervisor_version', 'id', + 'load_average', 'local_gb', 'local_gb_used', 'memory_mb', @@ -172,6 +175,8 @@ class TestHypervisorShow(TestHypervisor): 'service_id', 'state', 'status', + 'uptime', + 'users', 'vcpus', 'vcpus_used', ) @@ -183,10 +188,12 @@ class TestHypervisorShow(TestHypervisor): 50, 1024, '192.168.0.10', + '01:28:24', self.hypervisor.hypervisor_hostname, 'QEMU', 2004001, self.hypervisor.id, + '0.94, 0.62, 0.50', 50, 0, 1024, @@ -196,6 +203,8 @@ class TestHypervisorShow(TestHypervisor): 1, 'up', 'enabled', + '3 days, 11:15', + '1', 4, 0, ) @@ -219,3 +228,72 @@ class TestHypervisorShow(TestHypervisor): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + + def test_hyprvisor_show_uptime_not_implemented(self): + arglist = [ + self.hypervisor.hypervisor_hostname, + ] + verifylist = [ + ('hypervisor', self.hypervisor.hypervisor_hostname), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.hypervisors_mock.uptime.side_effect = ( + nova_exceptions.HTTPNotImplemented(501)) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns a two-part tuple with a tuple of column names and a tuple of + # data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + expected_columns = ( + 'aggregates', + 'cpu_info', + 'current_workload', + 'disk_available_least', + 'free_disk_gb', + 'free_ram_mb', + 'host_ip', + 'hypervisor_hostname', + 'hypervisor_type', + 'hypervisor_version', + 'id', + 'local_gb', + 'local_gb_used', + 'memory_mb', + 'memory_mb_used', + 'running_vms', + 'service_host', + 'service_id', + 'state', + 'status', + 'vcpus', + 'vcpus_used', + ) + expected_data = ( + [], + {'aaa': 'aaa'}, + 0, + 50, + 50, + 1024, + '192.168.0.10', + self.hypervisor.hypervisor_hostname, + 'QEMU', + 2004001, + self.hypervisor.id, + 50, + 0, + 1024, + 512, + 0, + 'aaa', + 1, + 'up', + 'enabled', + 4, + 0, + ) + + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, data) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index d4843f51..1081b9a3 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -417,7 +417,7 @@ class TestServerCreate(TestServer): @mock.patch('openstackclient.compute.v2.server.io.open') def test_server_create_userdata(self, mock_open): - mock_file = mock.MagicMock(name='File') + mock_file = mock.Mock(name='File') mock_open.return_value = mock_file mock_open.read.return_value = '#!/bin/sh' diff --git a/openstackclient/tests/unit/compute/v2/test_server_backup.py b/openstackclient/tests/unit/compute/v2/test_server_backup.py index 9aa63fc7..9a481e0a 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_backup.py +++ b/openstackclient/tests/unit/compute/v2/test_server_backup.py @@ -101,7 +101,7 @@ class TestServerBackupCreate(TestServerBackup): count=count, ) - self.images_mock.get = mock.MagicMock(side_effect=images) + self.images_mock.get = mock.Mock(side_effect=images) return images def test_server_backup_defaults(self): @@ -181,7 +181,7 @@ class TestServerBackupCreate(TestServerBackup): count=5, ) - self.images_mock.get = mock.MagicMock( + self.images_mock.get = mock.Mock( side_effect=images, ) @@ -230,7 +230,7 @@ class TestServerBackupCreate(TestServerBackup): count=5, ) - self.images_mock.get = mock.MagicMock( + self.images_mock.get = mock.Mock( side_effect=images, ) diff --git a/openstackclient/tests/unit/compute/v2/test_server_image.py b/openstackclient/tests/unit/compute/v2/test_server_image.py index f53f08e6..47bd682f 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_image.py +++ b/openstackclient/tests/unit/compute/v2/test_server_image.py @@ -99,8 +99,8 @@ class TestServerImageCreate(TestServerImage): count=count, ) - self.images_mock.get = mock.MagicMock(side_effect=images) - self.servers_mock.create_image = mock.MagicMock( + self.images_mock.get = mock.Mock(side_effect=images) + self.servers_mock.create_image = mock.Mock( return_value=images[0].id, ) return images diff --git a/openstackclient/tests/unit/compute/v2/test_service.py b/openstackclient/tests/unit/compute/v2/test_service.py index 1fd3b7d5..8403efc9 100644 --- a/openstackclient/tests/unit/compute/v2/test_service.py +++ b/openstackclient/tests/unit/compute/v2/test_service.py @@ -89,7 +89,7 @@ class TestServiceDelete(TestService): delete_mock_result = [None, exceptions.CommandError] self.service_mock.delete = ( - mock.MagicMock(side_effect=delete_mock_result) + mock.Mock(side_effect=delete_mock_result) ) try: diff --git a/openstackclient/tests/unit/fakes.py b/openstackclient/tests/unit/fakes.py index 786cd6d4..f2598366 100644 --- a/openstackclient/tests/unit/fakes.py +++ b/openstackclient/tests/unit/fakes.py @@ -200,7 +200,7 @@ class FakeResource(object): returned, which looks like a function call. """ for (name, ret) in six.iteritems(methods): - method = mock.MagicMock(return_value=ret) + method = mock.Mock(return_value=ret) setattr(self, name, method) def __repr__(self): diff --git a/openstackclient/tests/unit/identity/v2_0/test_catalog.py b/openstackclient/tests/unit/identity/v2_0/test_catalog.py index c32f9fb8..65af58a2 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_catalog.py +++ b/openstackclient/tests/unit/identity/v2_0/test_catalog.py @@ -25,12 +25,12 @@ class TestCatalog(utils.TestCommand): def setUp(self): super(TestCatalog, self).setUp() - self.sc_mock = mock.MagicMock() + self.sc_mock = mock.Mock() self.sc_mock.service_catalog.catalog.return_value = [ self.service_catalog, ] - self.auth_mock = mock.MagicMock() + self.auth_mock = mock.Mock() self.app.client_manager.session = self.auth_mock self.auth_mock.auth.get_auth_ref.return_value = self.sc_mock diff --git a/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py b/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py index 27306959..87643f12 100644 --- a/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py +++ b/openstackclient/tests/unit/identity/v2_0/test_role_assignment.py @@ -170,7 +170,7 @@ class TestRoleAssignmentList(TestRoleAssignment): def test_role_assignment_list_def_creds(self): - auth_ref = self.app.client_manager.auth_ref = mock.MagicMock() + auth_ref = self.app.client_manager.auth_ref = mock.Mock() auth_ref.project_id.return_value = identity_fakes.project_id auth_ref.user_id.return_value = identity_fakes.user_id diff --git a/openstackclient/tests/unit/identity/v3/fakes.py b/openstackclient/tests/unit/identity/v3/fakes.py index 7b76fa60..75065e65 100644 --- a/openstackclient/tests/unit/identity/v3/fakes.py +++ b/openstackclient/tests/unit/identity/v3/fakes.py @@ -719,7 +719,7 @@ class FakeCredential(object): if credentials is None: credentials = FakeCredential.create_credentials(count) - return mock.MagicMock(side_effect=credentials) + return mock.Mock(side_effect=credentials) class FakeUser(object): @@ -819,7 +819,7 @@ class FakeGroup(object): if groups is None: groups = FakeGroup.create_groups(count) - return mock.MagicMock(side_effect=groups) + return mock.Mock(side_effect=groups) class FakeEndpoint(object): diff --git a/openstackclient/tests/unit/identity/v3/test_catalog.py b/openstackclient/tests/unit/identity/v3/test_catalog.py index 986c05f3..53008e8c 100644 --- a/openstackclient/tests/unit/identity/v3/test_catalog.py +++ b/openstackclient/tests/unit/identity/v3/test_catalog.py @@ -50,12 +50,12 @@ class TestCatalog(utils.TestCommand): def setUp(self): super(TestCatalog, self).setUp() - self.sc_mock = mock.MagicMock() + self.sc_mock = mock.Mock() self.sc_mock.service_catalog.catalog.return_value = [ self.fake_service, ] - self.auth_mock = mock.MagicMock() + self.auth_mock = mock.Mock() self.app.client_manager.session = self.auth_mock self.auth_mock.auth.get_auth_ref.return_value = self.sc_mock diff --git a/openstackclient/tests/unit/identity/v3/test_credential.py b/openstackclient/tests/unit/identity/v3/test_credential.py index fd3ae6b2..ce0fb5ae 100644 --- a/openstackclient/tests/unit/identity/v3/test_credential.py +++ b/openstackclient/tests/unit/identity/v3/test_credential.py @@ -196,7 +196,7 @@ class TestCredentialDelete(TestCredential): delete_mock_result = [None, exceptions.CommandError] self.credentials_mock.delete = ( - mock.MagicMock(side_effect=delete_mock_result) + mock.Mock(side_effect=delete_mock_result) ) try: diff --git a/openstackclient/tests/unit/identity/v3/test_group.py b/openstackclient/tests/unit/identity/v3/test_group.py index d35e98c6..eb50adb5 100644 --- a/openstackclient/tests/unit/identity/v3/test_group.py +++ b/openstackclient/tests/unit/identity/v3/test_group.py @@ -239,7 +239,7 @@ class TestGroupDelete(TestGroup): def test_group_delete_with_domain(self): get_mock_result = [exceptions.CommandError, self.groups[0]] self.groups_mock.get = ( - mock.MagicMock(side_effect=get_mock_result)) + mock.Mock(side_effect=get_mock_result)) arglist = [ '--domain', self.domain.id, @@ -487,7 +487,7 @@ class TestGroupSet(TestGroup): def test_group_set_with_domain(self): get_mock_result = [exceptions.CommandError, self.group] self.groups_mock.get = ( - mock.MagicMock(side_effect=get_mock_result)) + mock.Mock(side_effect=get_mock_result)) arglist = [ '--domain', self.domain.id, @@ -550,7 +550,7 @@ class TestGroupShow(TestGroup): def test_group_show_with_domain(self): get_mock_result = [exceptions.CommandError, self.group] self.groups_mock.get = ( - mock.MagicMock(side_effect=get_mock_result)) + mock.Mock(side_effect=get_mock_result)) arglist = [ '--domain', self.domain.id, diff --git a/openstackclient/tests/unit/identity/v3/test_role_assignment.py b/openstackclient/tests/unit/identity/v3/test_role_assignment.py index 7102a0cd..32fbb7f1 100644 --- a/openstackclient/tests/unit/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/unit/identity/v3/test_role_assignment.py @@ -377,7 +377,7 @@ class TestRoleAssignmentList(TestRoleAssignment): def test_role_assignment_list_def_creds(self): - auth_ref = self.app.client_manager.auth_ref = mock.MagicMock() + auth_ref = self.app.client_manager.auth_ref = mock.Mock() auth_ref.project_id.return_value = identity_fakes.project_id auth_ref.user_id.return_value = identity_fakes.user_id diff --git a/openstackclient/tests/unit/image/v1/test_image.py b/openstackclient/tests/unit/image/v1/test_image.py index c62c1ff9..a6bc80a0 100644 --- a/openstackclient/tests/unit/image/v1/test_image.py +++ b/openstackclient/tests/unit/image/v1/test_image.py @@ -149,7 +149,7 @@ class TestImageCreate(TestImage): @mock.patch('openstackclient.image.v1.image.io.open', name='Open') def test_image_create_file(self, mock_open): - mock_file = mock.MagicMock(name='File') + mock_file = mock.Mock(name='File') mock_open.return_value = mock_file mock_open.read.return_value = image_fakes.image_data mock_exception = { diff --git a/openstackclient/tests/unit/image/v2/fakes.py b/openstackclient/tests/unit/image/v2/fakes.py index 4d9f6458..8e2f587d 100644 --- a/openstackclient/tests/unit/image/v2/fakes.py +++ b/openstackclient/tests/unit/image/v2/fakes.py @@ -245,7 +245,7 @@ class FakeImage(object): if images is None: images = FakeImage.create_images(count) - return mock.MagicMock(side_effect=images) + return mock.Mock(side_effect=images) @staticmethod def get_image_columns(image=None): diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index d33b84c5..91aebf9f 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -33,7 +33,8 @@ QUOTA = { "port": 50, "vip": 10, "member": 10, - "health_monitor": 10, + "healthmonitor": 10, + "l7policy": 5, } @@ -136,7 +137,7 @@ class FakeAddressScope(object): """ if address_scopes is None: address_scopes = FakeAddressScope.create_address_scopes(count) - return mock.MagicMock(side_effect=address_scopes) + return mock.Mock(side_effect=address_scopes) class FakeAvailabilityZone(object): @@ -284,6 +285,7 @@ class FakeNetwork(object): 'id': 'network-id-' + uuid.uuid4().hex, 'name': 'network-name-' + uuid.uuid4().hex, 'status': 'ACTIVE', + 'description': 'network-description-' + uuid.uuid4().hex, 'tenant_id': 'project-id-' + uuid.uuid4().hex, 'admin_state_up': True, 'shared': False, @@ -344,7 +346,7 @@ class FakeNetwork(object): """ if networks is None: networks = FakeNetwork.create_networks(count) - return mock.MagicMock(side_effect=networks) + return mock.Mock(side_effect=networks) class FakeNetworkSegment(object): @@ -491,7 +493,7 @@ class FakePort(object): """ if ports is None: ports = FakePort.create_ports(count) - return mock.MagicMock(side_effect=ports) + return mock.Mock(side_effect=ports) class FakeNetworkAgent(object): @@ -558,7 +560,7 @@ class FakeNetworkAgent(object): """ if agents is None: agents = FakeNetworkAgent.create_network_agents(count) - return mock.MagicMock(side_effect=agents) + return mock.Mock(side_effect=agents) class FakeNetworkRBAC(object): @@ -628,7 +630,7 @@ class FakeNetworkRBAC(object): """ if rbac_policies is None: rbac_policies = FakeNetworkRBAC.create_network_rbacs(count) - return mock.MagicMock(side_effect=rbac_policies) + return mock.Mock(side_effect=rbac_policies) class FakeRouter(object): @@ -706,7 +708,7 @@ class FakeRouter(object): """ if routers is None: routers = FakeRouter.create_routers(count) - return mock.MagicMock(side_effect=routers) + return mock.Mock(side_effect=routers) class FakeSecurityGroup(object): @@ -779,7 +781,7 @@ class FakeSecurityGroup(object): """ if security_groups is None: security_groups = FakeSecurityGroup.create_security_groups(count) - return mock.MagicMock(side_effect=security_groups) + return mock.Mock(side_effect=security_groups) class FakeSecurityGroupRule(object): @@ -858,7 +860,7 @@ class FakeSecurityGroupRule(object): if security_group_rules is None: security_group_rules = ( FakeSecurityGroupRule.create_security_group_rules(count)) - return mock.MagicMock(side_effect=security_group_rules) + return mock.Mock(side_effect=security_group_rules) class FakeSubnet(object): @@ -894,6 +896,7 @@ class FakeSubnet(object): 'segment_id': None, 'service_types': [], 'subnetpool_id': None, + 'description': 'subnet-description-' + uuid.uuid4().hex, } # Overwrite default attributes. @@ -941,7 +944,7 @@ class FakeSubnet(object): """ if subnets is None: subnets = FakeSubnet.create_subnets(count) - return mock.MagicMock(side_effect=subnets) + return mock.Mock(side_effect=subnets) class FakeFloatingIP(object): @@ -1018,7 +1021,7 @@ class FakeFloatingIP(object): """ if floating_ips is None: floating_ips = FakeFloatingIP.create_floating_ips(count) - return mock.MagicMock(side_effect=floating_ips) + return mock.Mock(side_effect=floating_ips) class FakeSubnetPool(object): @@ -1049,6 +1052,7 @@ class FakeSubnetPool(object): 'min_prefixlen': '8', 'default_quota': None, 'ip_version': '4', + 'description': 'subnet-pool-description-' + uuid.uuid4().hex, } # Overwrite default attributes. @@ -1100,4 +1104,4 @@ class FakeSubnetPool(object): """ if subnet_pools is None: subnet_pools = FakeSubnetPool.create_subnet_pools(count) - return mock.MagicMock(side_effect=subnet_pools) + return mock.Mock(side_effect=subnet_pools) diff --git a/openstackclient/tests/unit/network/v2/test_address_scope.py b/openstackclient/tests/unit/network/v2/test_address_scope.py index 6d3f4011..12c3f1d6 100644 --- a/openstackclient/tests/unit/network/v2/test_address_scope.py +++ b/openstackclient/tests/unit/network/v2/test_address_scope.py @@ -216,7 +216,7 @@ class TestDeleteAddressScope(TestAddressScope): find_mock_result = [self._address_scopes[0], exceptions.CommandError] self.network.find_address_scope = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py index a40e48f4..a77fb24b 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip.py @@ -204,7 +204,7 @@ class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): find_mock_result = [self.floating_ips[0], exceptions.CommandError] self.network.find_ip = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: @@ -454,7 +454,7 @@ class TestDeleteFloatingIPCompute(TestFloatingIPCompute): find_mock_result = [self.floating_ips[0], exceptions.CommandError] self.compute.floating_ips.get = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) self.compute.floating_ips.find.side_effect = exceptions.NotFound(None) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 84ead093..1094131e 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -57,6 +57,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'admin_state_up', 'availability_zone_hints', 'availability_zones', + 'description', 'id', 'is_default', 'name', @@ -73,6 +74,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): network._format_admin_state(_network.admin_state_up), utils.format_list(_network.availability_zone_hints), utils.format_list(_network.availability_zones), + _network.description, _network.id, _network.is_default, _network.name, @@ -129,6 +131,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): arglist = [ "--disable", "--share", + "--description", self._network.description, "--project", self.project.name, "--project-domain", self.domain.name, "--availability-zone-hint", "nova", @@ -143,6 +146,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): verifylist = [ ('disable', True), ('share', True), + ('description', self._network.description), ('project', self.project.name), ('project_domain', self.domain.name), ('availability_zone_hints', ["nova"]), @@ -164,6 +168,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'availability_zone_hints': ["nova"], 'name': self._network.name, 'shared': True, + 'description': self._network.description, 'tenant_id': self.project.id, 'is_default': True, 'router:external': True, @@ -216,6 +221,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): 'admin_state_up', 'availability_zone_hints', 'availability_zones', + 'description', 'id', 'is_default', 'name', @@ -232,6 +238,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): network._format_admin_state(_network.admin_state_up), utils.format_list(_network.availability_zone_hints), utils.format_list(_network.availability_zones), + _network.description, _network.id, _network.is_default, _network.name, @@ -532,6 +539,7 @@ class TestSetNetwork(TestNetwork): '--enable', '--name', 'noob', '--share', + '--description', self._network.description, '--external', '--default', '--provider-network-type', 'vlan', @@ -543,6 +551,7 @@ class TestSetNetwork(TestNetwork): verifylist = [ ('network', self._network.name), ('enable', True), + ('description', self._network.description), ('name', 'noob'), ('share', True), ('external', True), @@ -560,6 +569,7 @@ class TestSetNetwork(TestNetwork): attrs = { 'name': 'noob', 'admin_state_up': True, + 'description': self._network.description, 'shared': True, 'router:external': True, 'is_default': True, @@ -624,6 +634,7 @@ class TestShowNetwork(TestNetwork): 'admin_state_up', 'availability_zone_hints', 'availability_zones', + 'description', 'id', 'is_default', 'name', @@ -640,6 +651,7 @@ class TestShowNetwork(TestNetwork): network._format_admin_state(_network.admin_state_up), utils.format_list(_network.availability_zone_hints), utils.format_list(_network.availability_zones), + _network.description, _network.id, _network.is_default, _network.name, diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 2f17f41b..9f5b442a 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -96,7 +96,7 @@ class TestDeleteNetworkAgent(TestNetworkAgent): find_mock_result = [self.network_agents[0], exceptions.CommandError] self.network.get_agent = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: diff --git a/openstackclient/tests/unit/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py index 1cd18a09..c526ae4e 100644 --- a/openstackclient/tests/unit/network/v2/test_network_rbac.py +++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py @@ -259,7 +259,7 @@ class TestDeleteNetworkRBAC(TestNetworkRBAC): find_mock_result = [self.rbac_policies[0], exceptions.CommandError] self.network.find_rbac_policy = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index d5d7f330..271e8160 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -19,6 +19,7 @@ from osc_lib import exceptions from osc_lib import utils from openstackclient.network.v2 import port +from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit.network.v2 import fakes as network_fakes from openstackclient.tests.unit import utils as tests_utils @@ -289,7 +290,7 @@ class TestDeletePort(TestPort): find_mock_result = [self._ports[0], exceptions.CommandError] self.network.find_port = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: @@ -336,7 +337,12 @@ class TestListPort(TestPort): fake_router = network_fakes.FakeRouter.create_one_router({ 'id': 'fake-router-id', }) + fake_network = network_fakes.FakeNetwork.create_one_network({ + 'id': 'fake-network-id', + }) self.network.find_router = mock.Mock(return_value=fake_router) + self.network.find_network = mock.Mock(return_value=fake_network) + self.app.client_manager.compute = mock.Mock() def test_port_list_no_options(self): arglist = [] @@ -369,6 +375,26 @@ class TestListPort(TestPort): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + @mock.patch.object(utils, 'find_resource') + def test_port_list_with_server_option(self, mock_find): + fake_server = compute_fakes.FakeServer.create_one_server() + mock_find.return_value = fake_server + + arglist = [ + '--server', 'fake-server-name', + ] + verifylist = [ + ('server', 'fake-server-name'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.network.ports.assert_called_once_with( + device_id=fake_server.id) + mock_find.assert_called_once_with(mock.ANY, 'fake-server-name') + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + def test_port_list_device_owner_opt(self): arglist = [ '--device-owner', self._ports[0].device_owner, @@ -392,11 +418,13 @@ class TestListPort(TestPort): arglist = [ '--device-owner', self._ports[0].device_owner, '--router', 'fake-router-name', + '--network', 'fake-network-name', ] verifylist = [ ('device_owner', self._ports[0].device_owner), - ('router', 'fake-router-name') + ('router', 'fake-router-name'), + ('network', 'fake-network-name') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -405,7 +433,8 @@ class TestListPort(TestPort): self.network.ports.assert_called_once_with(**{ 'device_owner': self._ports[0].device_owner, - 'device_id': 'fake-router-id' + 'device_id': 'fake-router-id', + 'network_id': 'fake-network-id' }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 26fe655e..5ed969b4 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -40,7 +40,7 @@ class TestAddPortToRouter(TestRouter): def setUp(self): super(TestAddPortToRouter, self).setUp() - self.network.router_add_interface = mock.Mock() + self.network.add_interface_to_router = mock.Mock() self.cmd = router.AddPortToRouter(self.app, self.namespace) self.network.find_router = mock.Mock(return_value=self._router) self.network.find_port = mock.Mock(return_value=self._port) @@ -65,9 +65,8 @@ class TestAddPortToRouter(TestRouter): result = self.cmd.take_action(parsed_args) - self.network.router_add_interface.assert_called_with(self._router, **{ - 'port_id': self._router.port, - }) + self.network.add_interface_to_router.assert_called_with( + self._router, **{'port_id': self._router.port, }) self.assertIsNone(result) @@ -80,7 +79,7 @@ class TestAddSubnetToRouter(TestRouter): def setUp(self): super(TestAddSubnetToRouter, self).setUp() - self.network.router_add_interface = mock.Mock() + self.network.add_interface_to_router = mock.Mock() self.cmd = router.AddSubnetToRouter(self.app, self.namespace) self.network.find_router = mock.Mock(return_value=self._router) self.network.find_subnet = mock.Mock(return_value=self._subnet) @@ -104,7 +103,7 @@ class TestAddSubnetToRouter(TestRouter): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.router_add_interface.assert_called_with( + self.network.add_interface_to_router.assert_called_with( self._router, **{'subnet_id': self._router.subnet}) self.assertIsNone(result) @@ -263,7 +262,7 @@ class TestDeleteRouter(TestRouter): find_mock_result = [self._routers[0], exceptions.CommandError] self.network.find_router = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: @@ -285,6 +284,7 @@ class TestListRouter(TestRouter): # The routers going to be listed up. routers = network_fakes.FakeRouter.create_routers(count=3) + _extensions = network_fakes.FakeExtension.create_one_extension() columns = ( 'ID', @@ -300,6 +300,10 @@ class TestListRouter(TestRouter): 'External gateway info', 'Availability zones' ) + columns_long_no_az = columns + ( + 'Routes', + 'External gateway info', + ) data = [] for r in routers: @@ -322,6 +326,15 @@ class TestListRouter(TestRouter): osc_utils.format_list(r.availability_zones), ) ) + data_long_no_az = [] + for i in range(0, len(routers)): + r = routers[i] + data_long_no_az.append( + data[i] + ( + router._format_routes(r.routes), + router._format_external_gateway_info(r.external_gateway_info), + ) + ) def setUp(self): super(TestListRouter, self).setUp() @@ -330,6 +343,7 @@ class TestListRouter(TestRouter): self.cmd = router.ListRouter(self.app, self.namespace) self.network.routers = mock.Mock(return_value=self.routers) + self.network.find_extension = mock.Mock(return_value=self._extensions) def test_router_list_no_options(self): arglist = [] @@ -365,6 +379,27 @@ class TestListRouter(TestRouter): self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + def test_router_list_long_no_az(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # to mock, that no availability zone + self.network.find_extension = mock.Mock(return_value=None) + + # In base command class Lister in cliff, abstract method take_action() + # returns a tuple containing the column names and an iterable + # containing the data to be listed. + columns, data = self.cmd.take_action(parsed_args) + + self.network.routers.assert_called_once_with() + self.assertEqual(self.columns_long_no_az, columns) + self.assertEqual(self.data_long_no_az, list(data)) + class TestRemovePortFromRouter(TestRouter): '''Remove port from a Router ''' @@ -375,7 +410,7 @@ class TestRemovePortFromRouter(TestRouter): def setUp(self): super(TestRemovePortFromRouter, self).setUp() - self.network.router_remove_interface = mock.Mock() + self.network.remove_interface_from_router = mock.Mock() self.cmd = router.RemovePortFromRouter(self.app, self.namespace) self.network.find_router = mock.Mock(return_value=self._router) self.network.find_port = mock.Mock(return_value=self._port) @@ -400,7 +435,7 @@ class TestRemovePortFromRouter(TestRouter): result = self.cmd.take_action(parsed_args) - self.network.router_remove_interface.assert_called_with( + self.network.remove_interface_from_router.assert_called_with( self._router, **{'port_id': self._router.port}) self.assertIsNone(result) @@ -414,7 +449,7 @@ class TestRemoveSubnetFromRouter(TestRouter): def setUp(self): super(TestRemoveSubnetFromRouter, self).setUp() - self.network.router_remove_interface = mock.Mock() + self.network.remove_interface_from_router = mock.Mock() self.cmd = router.RemoveSubnetFromRouter(self.app, self.namespace) self.network.find_router = mock.Mock(return_value=self._router) self.network.find_subnet = mock.Mock(return_value=self._subnet) @@ -438,7 +473,7 @@ class TestRemoveSubnetFromRouter(TestRouter): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.router_remove_interface.assert_called_with( + self.network.remove_interface_from_router.assert_called_with( self._router, **{'subnet_id': self._router.subnet}) self.assertIsNone(result) diff --git a/openstackclient/tests/unit/network/v2/test_security_group.py b/openstackclient/tests/unit/network/v2/test_security_group.py index 4c5b2972..2615b77a 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group.py +++ b/openstackclient/tests/unit/network/v2/test_security_group.py @@ -279,7 +279,7 @@ class TestDeleteSecurityGroupNetwork(TestSecurityGroupNetwork): find_mock_result = [self._security_groups[0], exceptions.CommandError] self.network.find_security_group = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: @@ -365,7 +365,7 @@ class TestDeleteSecurityGroupCompute(TestSecurityGroupCompute): find_mock_result = [self._security_groups[0], exceptions.CommandError] self.compute.security_groups.get = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) self.compute.security_groups.find.side_effect = ( exceptions.NotFound(None)) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule.py b/openstackclient/tests/unit/network/v2/test_security_group_rule.py index 51e18a65..96d58e5c 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule.py @@ -723,7 +723,7 @@ class TestDeleteSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): find_mock_result = [ self._security_group_rules[0], exceptions.CommandError] self.network.find_security_group_rule = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: @@ -803,7 +803,7 @@ class TestDeleteSecurityGroupRuleCompute(TestSecurityGroupRuleCompute): find_mock_result = [None, exceptions.CommandError] self.compute.security_group_rules.delete = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index e31db469..58506391 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -110,6 +110,7 @@ class TestCreateSubnet(TestSubnet): columns = ( 'allocation_pools', 'cidr', + 'description', 'dns_nameservers', 'enable_dhcp', 'gateway_ip', @@ -129,6 +130,7 @@ class TestCreateSubnet(TestSubnet): data = ( subnet_v2._format_allocation_pools(_subnet.allocation_pools), _subnet.cidr, + _subnet.description, utils.format_list(_subnet.dns_nameservers), _subnet.enable_dhcp, _subnet.gateway_ip, @@ -148,6 +150,7 @@ class TestCreateSubnet(TestSubnet): data_subnet_pool = ( subnet_v2._format_allocation_pools(_subnet_from_pool.allocation_pools), _subnet_from_pool.cidr, + _subnet_from_pool.description, utils.format_list(_subnet_from_pool.dns_nameservers), _subnet_from_pool.enable_dhcp, _subnet_from_pool.gateway_ip, @@ -167,6 +170,7 @@ class TestCreateSubnet(TestSubnet): data_ipv6 = ( subnet_v2._format_allocation_pools(_subnet_ipv6.allocation_pools), _subnet_ipv6.cidr, + _subnet_ipv6.description, utils.format_list(_subnet_ipv6.dns_nameservers), _subnet_ipv6.enable_dhcp, _subnet_ipv6.gateway_ip, @@ -427,6 +431,40 @@ class TestCreateSubnet(TestSubnet): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_create_with_description(self): + # Mock SDK calls for this test. + self.network.create_subnet = mock.Mock(return_value=self._subnet) + self._network.id = self._subnet.network_id + + arglist = [ + "--subnet-range", self._subnet.cidr, + "--network", self._subnet.network_id, + "--description", self._subnet.description, + self._subnet.name, + ] + verifylist = [ + ('name', self._subnet.name), + ('description', self._subnet.description), + ('subnet_range', self._subnet.cidr), + ('network', self._subnet.network_id), + ('ip_version', self._subnet.ip_version), + ('gateway', 'auto'), + + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_subnet.assert_called_once_with(**{ + 'cidr': self._subnet.cidr, + 'ip_version': self._subnet.ip_version, + 'name': self._subnet.name, + 'network_id': self._subnet.network_id, + 'description': self._subnet.description, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteSubnet(TestSubnet): @@ -489,7 +527,7 @@ class TestDeleteSubnet(TestSubnet): find_mock_result = [self._subnets[0], exceptions.CommandError] self.network.find_subnet = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: @@ -646,7 +684,6 @@ class TestListSubnet(TestSubnet): ('service_types', ['network:router_gateway']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) filters = {'service_types': ['network:router_gateway']} @@ -654,6 +691,24 @@ class TestListSubnet(TestSubnet): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_subnet_list_project(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + def test_subnet_list_service_type_multiple(self): arglist = [ '--service-type', 'network:router_gateway', @@ -668,6 +723,97 @@ class TestListSubnet(TestSubnet): columns, data = self.cmd.take_action(parsed_args) filters = {'service_types': ['network:router_gateway', 'network:floatingip_agent_gateway']} + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_list_project_domain(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + '--project-domain', project.domain_id, + ] + verifylist = [ + ('project', project.id), + ('project_domain', project.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_list_network(self): + network = network_fakes.FakeNetwork.create_one_network() + self.network.find_network = mock.Mock(return_value=network) + arglist = [ + '--network', network.id, + ] + verifylist = [ + ('network', network.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'network_id': network.id} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_list_gateway(self): + subnet = network_fakes.FakeSubnet.create_one_subnet() + self.network.find_network = mock.Mock(return_value=subnet) + arglist = [ + '--gateway', subnet.gateway_ip, + ] + verifylist = [ + ('gateway', subnet.gateway_ip), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'gateway_ip': subnet.gateway_ip} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_list_name(self): + subnet = network_fakes.FakeSubnet.create_one_subnet() + self.network.find_network = mock.Mock(return_value=subnet) + arglist = [ + '--name', subnet.name, + ] + verifylist = [ + ('name', subnet.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'name': subnet.name} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_list_subnet_range(self): + subnet = network_fakes.FakeSubnet.create_one_subnet() + self.network.find_network = mock.Mock(return_value=subnet) + arglist = [ + '--subnet-range', subnet.cidr, + ] + verifylist = [ + ('subnet_range', subnet.cidr), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'cidr': subnet.cidr} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -768,6 +914,30 @@ class TestSetSubnet(TestSubnet): _testsubnet, **attrs) self.assertIsNone(result) + def test_set_non_append_options(self): + arglist = [ + "--description", "new_description", + "--dhcp", + "--gateway", self._subnet.gateway_ip, + self._subnet.name, + ] + verifylist = [ + ('description', "new_description"), + ('dhcp', True), + ('gateway', self._subnet.gateway_ip), + ('subnet', self._subnet.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'enable_dhcp': True, + 'gateway_ip': self._subnet.gateway_ip, + 'description': "new_description", + } + self.network.update_subnet.assert_called_with(self._subnet, **attrs) + self.assertIsNone(result) + class TestShowSubnet(TestSubnet): # The subnets to be shown @@ -776,6 +946,7 @@ class TestShowSubnet(TestSubnet): columns = ( 'allocation_pools', 'cidr', + 'description', 'dns_nameservers', 'enable_dhcp', 'gateway_ip', @@ -795,6 +966,7 @@ class TestShowSubnet(TestSubnet): data = ( subnet_v2._format_allocation_pools(_subnet.allocation_pools), _subnet.cidr, + _subnet.description, utils.format_list(_subnet.dns_nameservers), _subnet.enable_dhcp, _subnet.gateway_ip, diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index 0d9494c0..fa6ffff3 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -50,6 +50,7 @@ class TestCreateSubnetPool(TestSubnetPool): 'address_scope_id', 'default_prefixlen', 'default_quota', + 'description', 'id', 'ip_version', 'is_default', @@ -64,6 +65,7 @@ class TestCreateSubnetPool(TestSubnetPool): _subnet_pool.address_scope_id, _subnet_pool.default_prefixlen, _subnet_pool.default_quota, + _subnet_pool.description, _subnet_pool.id, _subnet_pool.ip_version, _subnet_pool.is_default, @@ -245,6 +247,29 @@ class TestCreateSubnetPool(TestSubnetPool): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_create_with_description(self): + arglist = [ + '--pool-prefix', '10.0.10.0/24', + '--description', self._subnet_pool.description, + self._subnet_pool.name, + ] + verifylist = [ + ('prefixes', ['10.0.10.0/24']), + ('description', self._subnet_pool.description), + ('name', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_subnet_pool.assert_called_once_with(**{ + 'name': self._subnet_pool.name, + 'prefixes': ['10.0.10.0/24'], + 'description': self._subnet_pool.description, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteSubnetPool(TestSubnetPool): @@ -310,7 +335,7 @@ class TestDeleteSubnetPool(TestSubnetPool): find_mock_result = [self._subnet_pools[0], exceptions.CommandError] self.network.find_subnet_pool = ( - mock.MagicMock(side_effect=find_mock_result) + mock.Mock(side_effect=find_mock_result) ) try: @@ -400,6 +425,144 @@ class TestListSubnetPool(TestSubnetPool): self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) + def test_subnet_pool_list_no_share(self): + arglist = [ + '--no-share', + ] + verifylist = [ + ('share', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'shared': False} + + self.network.subnet_pools.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_pool_list_share(self): + arglist = [ + '--share', + ] + verifylist = [ + ('share', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'shared': True} + + self.network.subnet_pools.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_pool_list_no_default(self): + arglist = [ + '--no-default', + ] + verifylist = [ + ('default', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'is_default': False} + + self.network.subnet_pools.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_pool_list_default(self): + arglist = [ + '--default', + ] + verifylist = [ + ('default', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'is_default': True} + + self.network.subnet_pools.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_pool_list_project(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.subnet_pools.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_pool_list_project_domain(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + '--project-domain', project.domain_id, + ] + verifylist = [ + ('project', project.id), + ('project_domain', project.domain_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'tenant_id': project.id} + + self.network.subnet_pools.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_pool_list_name(self): + subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + self.network.find_network = mock.Mock(return_value=subnet_pool) + arglist = [ + '--name', subnet_pool.name, + ] + verifylist = [ + ('name', subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'name': subnet_pool.name} + + self.network.subnet_pools.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_subnet_pool_list_address_scope(self): + addr_scope = network_fakes.FakeAddressScope.create_one_address_scope() + self.network.find_address_scope = mock.Mock(return_value=addr_scope) + arglist = [ + '--address-scope', addr_scope.id, + ] + verifylist = [ + ('address_scope', addr_scope.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'address_scope_id': addr_scope.id} + + self.network.subnet_pools.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetSubnetPool(TestSubnetPool): @@ -611,6 +774,26 @@ class TestSetSubnetPool(TestSubnetPool): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) + def test_set_description(self): + arglist = [ + '--description', 'new_description', + self._subnet_pool.name, + ] + verifylist = [ + ('description', "new_description"), + ('subnet_pool', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'description': "new_description", + } + self.network.update_subnet_pool.assert_called_once_with( + self._subnet_pool, **attrs) + self.assertIsNone(result) + class TestShowSubnetPool(TestSubnetPool): @@ -621,6 +804,7 @@ class TestShowSubnetPool(TestSubnetPool): 'address_scope_id', 'default_prefixlen', 'default_quota', + 'description', 'id', 'ip_version', 'is_default', @@ -636,6 +820,7 @@ class TestShowSubnetPool(TestSubnetPool): _subnet_pool.address_scope_id, _subnet_pool.default_prefixlen, _subnet_pool.default_quota, + _subnet_pool.description, _subnet_pool.id, _subnet_pool.ip_version, _subnet_pool.is_default, diff --git a/openstackclient/tests/unit/test_shell.py b/openstackclient/tests/unit/test_shell.py index 87cd7f51..3d91da9b 100644 --- a/openstackclient/tests/unit/test_shell.py +++ b/openstackclient/tests/unit/test_shell.py @@ -413,9 +413,6 @@ class TestShellCli(TestShell): class TestShellArgV(TestShell): """Test the deferred help flag""" - def setUp(self): - super(TestShellArgV, self).setUp() - def test_shell_argv(self): """Test argv decoding diff --git a/openstackclient/tests/unit/volume/v1/fakes.py b/openstackclient/tests/unit/volume/v1/fakes.py index c6fee7d1..33374499 100644 --- a/openstackclient/tests/unit/volume/v1/fakes.py +++ b/openstackclient/tests/unit/volume/v1/fakes.py @@ -13,7 +13,10 @@ # under the License. # +import copy import mock +import random +import uuid from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes @@ -143,9 +146,12 @@ class FakeTransfer(object): """ # Set default attribute transfer_info = { - 'volume_id': 'ce26708d-a7f8-4b4b-9861-4a80256615a7', + 'auth_key': 'key-' + uuid.uuid4().hex, + 'created_at': 'time-' + uuid.uuid4().hex, + 'volume_id': 'volume-id-' + uuid.uuid4().hex, 'name': 'fake_transfer_name', - 'id': '731a7f53-aa92-4fbd-9de3-6f7d729c926b' + 'id': 'id-' + uuid.uuid4().hex, + 'links': 'links-' + uuid.uuid4().hex, } # Overwrite default attributes if there are some attributes set @@ -160,6 +166,43 @@ class FakeTransfer(object): return transfer + @staticmethod + def create_transfers(attrs=None, count=2): + """Create multiple fake transfers. + + :param Dictionary attrs: + A dictionary with all attributes of transfer + :param Integer count: + The number of transfers to be faked + :return: + A list of FakeResource objects + """ + transfers = [] + for n in range(0, count): + transfers.append(FakeTransfer.create_one_transfer(attrs)) + + return transfers + + @staticmethod + def get_transfers(transfers=None, count=2): + """Get an iterable MagicMock object with a list of faked transfers. + + If transfers list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List transfers: + A list of FakeResource objects faking transfers + :param Integer count: + The number of transfers to be faked + :return + An iterable Mock object with side_effect set to a list of faked + transfers + """ + if transfers is None: + transfers = FakeTransfer.create_transfers(count) + + return mock.Mock(side_effect=transfers) + class FakeService(object): """Fake one or more Services.""" @@ -231,7 +274,160 @@ class FakeService(object): if services is None: services = FakeService.create_services(count) - return mock.MagicMock(side_effect=services) + return mock.Mock(side_effect=services) + + +class FakeQos(object): + """Fake one or more Qos specification.""" + + @staticmethod + def create_one_qos(attrs=None): + """Create a fake Qos specification. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with id, name, consumer, etc. + """ + attrs = attrs or {} + + # Set default attributes. + qos_info = { + "id": 'qos-id-' + uuid.uuid4().hex, + "name": 'qos-name-' + uuid.uuid4().hex, + "consumer": 'front-end', + "specs": {"foo": "bar", "iops": "9001"}, + } + + # Overwrite default attributes. + qos_info.update(attrs) + + qos = fakes.FakeResource( + info=copy.deepcopy(qos_info), + loaded=True) + return qos + + @staticmethod + def create_qoses(attrs=None, count=2): + """Create multiple fake Qos specifications. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of Qos specifications to fake + :return: + A list of FakeResource objects faking the Qos specifications + """ + qoses = [] + for i in range(0, count): + qos = FakeQos.create_one_qos(attrs) + qoses.append(qos) + + return qoses + + @staticmethod + def get_qoses(qoses=None, count=2): + """Get an iterable MagicMock object with a list of faked qoses. + + If qoses list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List volumes: + A list of FakeResource objects faking qoses + :param Integer count: + The number of qoses to be faked + :return + An iterable Mock object with side_effect set to a list of faked + qoses + """ + if qoses is None: + qoses = FakeQos.create_qoses(count) + + return mock.Mock(side_effect=qoses) + + +class FakeVolume(object): + """Fake one or more volumes.""" + + @staticmethod + def create_one_volume(attrs=None): + """Create a fake volume. + + :param Dictionary attrs: + A dictionary with all attributes of volume + :return: + A FakeResource object with id, name, status, etc. + """ + attrs = attrs or {} + + # Set default attribute + volume_info = { + 'id': 'volume-id' + uuid.uuid4().hex, + 'display_name': 'volume-name' + uuid.uuid4().hex, + 'display_description': 'description' + uuid.uuid4().hex, + 'status': 'available', + 'size': 10, + 'volume_type': + random.choice(['fake_lvmdriver-1', 'fake_lvmdriver-2']), + 'bootable': 'true', + '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': 'snapshot-id-' + uuid.uuid4().hex, + 'availability_zone': 'zone' + uuid.uuid4().hex, + 'attachments': [{ + 'device': '/dev/' + uuid.uuid4().hex, + 'server_id': uuid.uuid4().hex, + }, ], + 'created_at': 'time-' + 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=None, 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.Mock(side_effect=volumes) class FakeImagev1Client(object): @@ -278,3 +474,77 @@ class TestVolumev1(utils.TestCommand): endpoint=fakes.AUTH_URL, token=fakes.AUTH_TOKEN, ) + + +class FakeType(object): + """Fake one or more type.""" + + @staticmethod + def create_one_type(attrs=None, methods=None): + """Create a fake type. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object with id, name, description, etc. + """ + attrs = attrs or {} + methods = methods or {} + + # Set default attributes. + type_info = { + "id": 'type-id-' + uuid.uuid4().hex, + "name": 'type-name-' + uuid.uuid4().hex, + "description": 'type-description-' + uuid.uuid4().hex, + "extra_specs": {"foo": "bar"}, + "is_public": True, + } + + # Overwrite default attributes. + type_info.update(attrs) + + volume_type = fakes.FakeResource( + info=copy.deepcopy(type_info), + methods=methods, + loaded=True) + return volume_type + + @staticmethod + def create_types(attrs=None, count=2): + """Create multiple fake types. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of types to fake + :return: + A list of FakeResource objects faking the types + """ + volume_types = [] + for i in range(0, count): + volume_type = FakeType.create_one_type(attrs) + volume_types.append(volume_type) + + return volume_types + + @staticmethod + def get_types(types=None, count=2): + """Get an iterable MagicMock object with a list of faked types. + + If types list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List types: + A list of FakeResource objects faking types + :param Integer count: + The number of types to be faked + :return + An iterable Mock object with side_effect set to a list of faked + types + """ + if types is None: + types = FakeType.create_types(count) + + return mock.Mock(side_effect=types) diff --git a/openstackclient/tests/unit/volume/v1/test_qos_specs.py b/openstackclient/tests/unit/volume/v1/test_qos_specs.py index 7b87ccb3..81680ab4 100644 --- a/openstackclient/tests/unit/volume/v1/test_qos_specs.py +++ b/openstackclient/tests/unit/volume/v1/test_qos_specs.py @@ -14,7 +14,10 @@ # import copy +import mock +from mock import call +from osc_lib import exceptions from osc_lib import utils from openstackclient.tests.unit import fakes @@ -188,62 +191,106 @@ class TestQosCreate(TestQos): class TestQosDelete(TestQos): + qos_specs = volume_fakes.FakeQos.create_qoses(count=2) + def setUp(self): super(TestQosDelete, self).setUp() - self.qos_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.QOS), - loaded=True, - ) - + self.qos_mock.get = ( + volume_fakes.FakeQos.get_qoses(self.qos_specs)) # Get the command object to test self.cmd = qos_specs.DeleteQos(self.app, None) def test_qos_delete_with_id(self): arglist = [ - volume_fakes.qos_id + self.qos_specs[0].id ] verifylist = [ - ('qos_specs', [volume_fakes.qos_id]) + ('qos_specs', [self.qos_specs[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, False) + self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, False) self.assertIsNone(result) def test_qos_delete_with_name(self): arglist = [ - volume_fakes.qos_name + self.qos_specs[0].name ] verifylist = [ - ('qos_specs', [volume_fakes.qos_name]) + ('qos_specs', [self.qos_specs[0].name]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, False) + self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, False) self.assertIsNone(result) def test_qos_delete_with_force(self): arglist = [ '--force', - volume_fakes.qos_id + self.qos_specs[0].id ] verifylist = [ ('force', True), - ('qos_specs', [volume_fakes.qos_id]) + ('qos_specs', [self.qos_specs[0].id]) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.qos_mock.delete.assert_called_with(volume_fakes.qos_id, True) + self.qos_mock.delete.assert_called_with(self.qos_specs[0].id, True) self.assertIsNone(result) + def test_delete_multiple_qoses(self): + arglist = [] + for q in self.qos_specs: + arglist.append(q.id) + verifylist = [ + ('qos_specs', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for q in self.qos_specs: + calls.append(call(q.id, False)) + self.qos_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_qoses_with_exception(self): + arglist = [ + self.qos_specs[0].id, + 'unexist_qos', + ] + verifylist = [ + ('qos_specs', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.qos_specs[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual( + '1 of 2 QoS specifications failed to delete.', str(e)) + + find_mock.assert_any_call(self.qos_mock, self.qos_specs[0].id) + find_mock.assert_any_call(self.qos_mock, 'unexist_qos') + + self.assertEqual(2, find_mock.call_count) + self.qos_mock.delete.assert_called_once_with( + self.qos_specs[0].id, False + ) + class TestQosDisassociate(TestQos): diff --git a/openstackclient/tests/unit/volume/v1/test_transfer_request.py b/openstackclient/tests/unit/volume/v1/test_transfer_request.py index f7980c34..e89c5056 100644 --- a/openstackclient/tests/unit/volume/v1/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v1/test_transfer_request.py @@ -12,6 +12,11 @@ # under the License. # +import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils from openstackclient.tests.unit.volume.v1 import fakes as transfer_fakes from openstackclient.volume.v1 import volume_transfer_request @@ -26,6 +31,155 @@ class TestTransfer(transfer_fakes.TestVolumev1): self.transfer_mock = self.app.client_manager.volume.transfers self.transfer_mock.reset_mock() + # Get a shortcut to the VolumeManager Mock + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + + +class TestTransferCreate(TestTransfer): + + volume = transfer_fakes.FakeVolume.create_one_volume() + + columns = ( + 'auth_key', + 'created_at', + 'id', + 'name', + 'volume_id', + ) + + def setUp(self): + super(TestTransferCreate, self).setUp() + + self.volume_transfer = transfer_fakes.FakeTransfer.create_one_transfer( + attrs={'volume_id': self.volume.id}) + self.data = ( + self.volume_transfer.auth_key, + self.volume_transfer.created_at, + self.volume_transfer.id, + self.volume_transfer.name, + self.volume_transfer.volume_id, + ) + + self.transfer_mock.create.return_value = self.volume_transfer + self.volumes_mock.get.return_value = self.volume + + # Get the command object to test + self.cmd = volume_transfer_request.CreateTransferRequest( + self.app, None) + + def test_transfer_create_without_name(self): + arglist = [ + self.volume.id, + ] + verifylist = [ + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.create.assert_called_once_with( + self.volume.id, None) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_transfer_create_with_name(self): + arglist = [ + '--name', self.volume_transfer.name, + self.volume.id, + ] + verifylist = [ + ('name', self.volume_transfer.name), + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.create.assert_called_once_with( + self.volume.id, self.volume_transfer.name,) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestTransferDelete(TestTransfer): + + volume_transfers = transfer_fakes.FakeTransfer.create_transfers(count=2) + + def setUp(self): + super(TestTransferDelete, self).setUp() + + self.transfer_mock.get = ( + transfer_fakes.FakeTransfer.get_transfers(self.volume_transfers)) + self.transfer_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume_transfer_request.DeleteTransferRequest( + self.app, None) + + def test_transfer_delete(self): + arglist = [ + self.volume_transfers[0].id + ] + verifylist = [ + ("transfer_request", [self.volume_transfers[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.transfer_mock.delete.assert_called_with( + self.volume_transfers[0].id) + self.assertIsNone(result) + + def test_delete_multiple_transfers(self): + arglist = [] + for v in self.volume_transfers: + arglist.append(v.id) + verifylist = [ + ('transfer_request', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for v in self.volume_transfers: + calls.append(call(v.id)) + self.transfer_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_transfers_with_exception(self): + arglist = [ + self.volume_transfers[0].id, + 'unexist_transfer', + ] + verifylist = [ + ('transfer_request', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.volume_transfers[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 volume transfer requests failed ' + 'to delete.', str(e)) + + find_mock.assert_any_call( + self.transfer_mock, self.volume_transfers[0].id) + find_mock.assert_any_call(self.transfer_mock, 'unexist_transfer') + + self.assertEqual(2, find_mock.call_count) + self.transfer_mock.delete.assert_called_once_with( + self.volume_transfers[0].id, + ) + class TestTransferList(TestTransfer): @@ -38,7 +192,7 @@ class TestTransferList(TestTransfer): self.transfer_mock.list.return_value = [self.volume_transfers] # Get the command object to test - self.cmd = volume_transfer_request.ListTransferRequests(self.app, None) + self.cmd = volume_transfer_request.ListTransferRequest(self.app, None) def test_transfer_list_without_argument(self): arglist = [] diff --git a/openstackclient/tests/unit/volume/v1/test_type.py b/openstackclient/tests/unit/volume/v1/test_type.py new file mode 100644 index 00000000..35016dc6 --- /dev/null +++ b/openstackclient/tests/unit/volume/v1/test_type.py @@ -0,0 +1,347 @@ +# +# 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 mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.tests.unit import utils as tests_utils +from openstackclient.tests.unit.volume.v1 import fakes as volume_fakes +from openstackclient.volume.v1 import volume_type + + +class TestType(volume_fakes.TestVolumev1): + + def setUp(self): + super(TestType, self).setUp() + + self.types_mock = self.app.client_manager.volume.volume_types + self.types_mock.reset_mock() + + +class TestTypeCreate(TestType): + + columns = ( + 'description', + 'id', + 'is_public', + 'name', + ) + + def setUp(self): + super(TestTypeCreate, self).setUp() + + self.new_volume_type = volume_fakes.FakeType.create_one_type( + methods={'set_keys': {'myprop': 'myvalue'}} + ) + self.data = ( + self.new_volume_type.description, + self.new_volume_type.id, + True, + self.new_volume_type.name, + ) + + self.types_mock.create.return_value = self.new_volume_type + # Get the command object to test + self.cmd = volume_type.CreateVolumeType(self.app, None) + + def test_type_create(self): + arglist = [ + self.new_volume_type.name, + ] + verifylist = [ + ("name", self.new_volume_type.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.create.assert_called_with( + self.new_volume_type.name, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestTypeDelete(TestType): + + volume_types = volume_fakes.FakeType.create_types(count=2) + + def setUp(self): + super(TestTypeDelete, self).setUp() + + self.types_mock.get = volume_fakes.FakeType.get_types( + self.volume_types) + self.types_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume_type.DeleteVolumeType(self.app, None) + + def test_type_delete(self): + arglist = [ + self.volume_types[0].id + ] + verifylist = [ + ("volume_types", [self.volume_types[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.types_mock.delete.assert_called_with(self.volume_types[0]) + self.assertIsNone(result) + + def test_delete_multiple_types(self): + arglist = [] + for t in self.volume_types: + arglist.append(t.id) + verifylist = [ + ('volume_types', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for t in self.volume_types: + calls.append(call(t)) + self.types_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_types_with_exception(self): + arglist = [ + self.volume_types[0].id, + 'unexist_type', + ] + verifylist = [ + ('volume_types', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.volume_types[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 volume types failed to delete.', + str(e)) + + find_mock.assert_any_call( + self.types_mock, self.volume_types[0].id) + find_mock.assert_any_call(self.types_mock, 'unexist_type') + + self.assertEqual(2, find_mock.call_count) + self.types_mock.delete.assert_called_once_with( + self.volume_types[0] + ) + + +class TestTypeList(TestType): + + volume_types = volume_fakes.FakeType.create_types() + + columns = ( + "ID", + "Name" + ) + columns_long = ( + "ID", + "Name", + "Properties" + ) + + data = [] + for t in volume_types: + data.append(( + t.id, + t.name, + )) + data_long = [] + for t in volume_types: + data_long.append(( + t.id, + t.name, + utils.format_dict(t.extra_specs), + )) + + def setUp(self): + super(TestTypeList, self).setUp() + + self.types_mock.list.return_value = self.volume_types + # get the command to test + self.cmd = volume_type.ListVolumeType(self.app, None) + + def test_type_list_without_options(self): + arglist = [] + verifylist = [ + ("long", False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.list.assert_called_once_with() + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_type_list_with_options(self): + arglist = [ + "--long", + ] + verifylist = [ + ("long", True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.list.assert_called_once_with() + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data_long, list(data)) + + +class TestTypeSet(TestType): + + volume_type = volume_fakes.FakeType.create_one_type( + methods={'set_keys': None}) + + def setUp(self): + super(TestTypeSet, self).setUp() + + self.types_mock.get.return_value = self.volume_type + + # Get the command object to test + self.cmd = volume_type.SetVolumeType(self.app, None) + + def test_type_set_nothing(self): + arglist = [ + self.volume_type.id, + ] + verifylist = [ + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + + def test_type_set_property(self): + arglist = [ + '--property', 'myprop=myvalue', + self.volume_type.id, + ] + verifylist = [ + ('property', {'myprop': 'myvalue'}), + ('volume_type', self.volume_type.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volume_type.set_keys.assert_called_once_with( + {'myprop': 'myvalue'}) + self.assertIsNone(result) + + +class TestTypeShow(TestType): + + columns = ( + 'description', + 'id', + 'is_public', + 'name', + 'properties', + ) + + def setUp(self): + super(TestTypeShow, self).setUp() + + self.volume_type = volume_fakes.FakeType.create_one_type() + self.data = ( + self.volume_type.description, + self.volume_type.id, + True, + self.volume_type.name, + utils.format_dict(self.volume_type.extra_specs) + ) + + self.types_mock.get.return_value = self.volume_type + + # Get the command object to test + self.cmd = volume_type.ShowVolumeType(self.app, None) + + def test_type_show(self): + arglist = [ + self.volume_type.id + ] + verifylist = [ + ("volume_type", self.volume_type.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.types_mock.get.assert_called_with(self.volume_type.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestTypeUnset(TestType): + + volume_type = volume_fakes.FakeType.create_one_type( + methods={'unset_keys': None}) + + def setUp(self): + super(TestTypeUnset, self).setUp() + + self.types_mock.get.return_value = self.volume_type + + # Get the command object to test + self.cmd = volume_type.UnsetVolumeType(self.app, None) + + def test_type_unset(self): + arglist = [ + '--property', 'property', + '--property', 'multi_property', + self.volume_type.id, + ] + verifylist = [ + ('property', ['property', 'multi_property']), + ('volume_type', self.volume_type.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.volume_type.unset_keys.assert_called_once_with( + ['property', 'multi_property']) + self.assertIsNone(result) + + def test_type_unset_failed_with_missing_volume_type_argument(self): + arglist = [ + '--property', 'property', + '--property', 'multi_property', + ] + verifylist = [ + ('property', ['property', 'multi_property']), + ] + + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) diff --git a/openstackclient/tests/unit/volume/v1/test_volume.py b/openstackclient/tests/unit/volume/v1/test_volume.py index f90566fd..895f1f87 100644 --- a/openstackclient/tests/unit/volume/v1/test_volume.py +++ b/openstackclient/tests/unit/volume/v1/test_volume.py @@ -13,8 +13,13 @@ # under the License. # +import argparse import copy import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes @@ -43,6 +48,14 @@ class TestVolume(volume_fakes.TestVolumev1): 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 + # TODO(dtroyer): The volume create tests are incomplete, only the minimal # options and the options that require additional processing @@ -54,48 +67,50 @@ class TestVolumeCreate(TestVolume): user = identity_fakes.FakeUser.create_one_user() columns = ( - 'attach_status', + 'attachments', 'availability_zone', + 'bootable', + 'created_at', 'display_description', 'display_name', 'id', 'properties', 'size', + 'snapshot_id', 'status', 'type', ) - datalist = ( - 'detached', - volume_fakes.volume_zone, - volume_fakes.volume_description, - volume_fakes.volume_name, - volume_fakes.volume_id, - volume_fakes.volume_metadata_str, - volume_fakes.volume_size, - volume_fakes.volume_status, - volume_fakes.volume_type, - ) def setUp(self): super(TestVolumeCreate, self).setUp() - - self.volumes_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True, + self.new_volume = volume_fakes.FakeVolume.create_one_volume() + self.datalist = ( + self.new_volume.attachments, + self.new_volume.availability_zone, + self.new_volume.bootable, + self.new_volume.created_at, + self.new_volume.display_description, + self.new_volume.display_name, + self.new_volume.id, + utils.format_dict(self.new_volume.metadata), + self.new_volume.size, + self.new_volume.snapshot_id, + self.new_volume.status, + self.new_volume.volume_type, ) + self.volumes_mock.create.return_value = self.new_volume # Get the command object to test self.cmd = volume.CreateVolume(self.app, None) def test_volume_create_min_options(self): arglist = [ - '--size', str(volume_fakes.volume_size), - volume_fakes.volume_name, + '--size', str(self.new_volume.size), + self.new_volume.display_name, ] verifylist = [ - ('size', volume_fakes.volume_size), - ('name', volume_fakes.volume_name), + ('size', self.new_volume.size), + ('name', self.new_volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -110,10 +125,10 @@ class TestVolumeCreate(TestVolume): # project_id=, availability_zone=, # metadata=, imageRef=) self.volumes_mock.create.assert_called_with( - volume_fakes.volume_size, + self.new_volume.size, None, None, - volume_fakes.volume_name, + self.new_volume.display_name, None, None, None, @@ -127,18 +142,18 @@ class TestVolumeCreate(TestVolume): def test_volume_create_options(self): arglist = [ - '--size', str(volume_fakes.volume_size), - '--description', volume_fakes.volume_description, - '--type', volume_fakes.volume_type, - '--availability-zone', volume_fakes.volume_zone, - volume_fakes.volume_name, + '--size', str(self.new_volume.size), + '--description', self.new_volume.display_description, + '--type', self.new_volume.volume_type, + '--availability-zone', self.new_volume.availability_zone, + self.new_volume.display_name, ] verifylist = [ - ('size', volume_fakes.volume_size), - ('description', volume_fakes.volume_description), - ('type', volume_fakes.volume_type), - ('availability_zone', volume_fakes.volume_zone), - ('name', volume_fakes.volume_name), + ('size', self.new_volume.size), + ('description', self.new_volume.display_description), + ('type', self.new_volume.volume_type), + ('availability_zone', self.new_volume.availability_zone), + ('name', self.new_volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -153,15 +168,15 @@ class TestVolumeCreate(TestVolume): # project_id=, availability_zone=, # metadata=, imageRef=) self.volumes_mock.create.assert_called_with( - volume_fakes.volume_size, + self.new_volume.size, None, None, - volume_fakes.volume_name, - volume_fakes.volume_description, - volume_fakes.volume_type, + self.new_volume.display_name, + self.new_volume.display_description, + self.new_volume.volume_type, None, None, - volume_fakes.volume_zone, + self.new_volume.availability_zone, None, None, ) @@ -176,16 +191,16 @@ class TestVolumeCreate(TestVolume): self.users_mock.get.return_value = self.user arglist = [ - '--size', str(volume_fakes.volume_size), + '--size', str(self.new_volume.size), '--project', self.project.id, '--user', self.user.id, - volume_fakes.volume_name, + self.new_volume.display_name, ] verifylist = [ - ('size', volume_fakes.volume_size), + ('size', self.new_volume.size), ('project', self.project.id), ('user', self.user.id), - ('name', volume_fakes.volume_name), + ('name', self.new_volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -200,10 +215,10 @@ class TestVolumeCreate(TestVolume): # project_id=, availability_zone=, # metadata=, imageRef=) self.volumes_mock.create.assert_called_with( - volume_fakes.volume_size, + self.new_volume.size, None, None, - volume_fakes.volume_name, + self.new_volume.display_name, None, None, self.user.id, @@ -223,16 +238,16 @@ class TestVolumeCreate(TestVolume): self.users_mock.get.return_value = self.user arglist = [ - '--size', str(volume_fakes.volume_size), + '--size', str(self.new_volume.size), '--project', self.project.name, '--user', self.user.name, - volume_fakes.volume_name, + self.new_volume.display_name, ] verifylist = [ - ('size', volume_fakes.volume_size), + ('size', self.new_volume.size), ('project', self.project.name), ('user', self.user.name), - ('name', volume_fakes.volume_name), + ('name', self.new_volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -247,10 +262,10 @@ class TestVolumeCreate(TestVolume): # project_id=, availability_zone=, # metadata=, imageRef=) self.volumes_mock.create.assert_called_with( - volume_fakes.volume_size, + self.new_volume.size, None, None, - volume_fakes.volume_name, + self.new_volume.display_name, None, None, self.user.id, @@ -267,13 +282,13 @@ class TestVolumeCreate(TestVolume): arglist = [ '--property', 'Alpha=a', '--property', 'Beta=b', - '--size', str(volume_fakes.volume_size), - volume_fakes.volume_name, + '--size', str(self.new_volume.size), + self.new_volume.display_name, ] verifylist = [ ('property', {'Alpha': 'a', 'Beta': 'b'}), - ('size', volume_fakes.volume_size), - ('name', volume_fakes.volume_name), + ('size', self.new_volume.size), + ('name', self.new_volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -288,10 +303,10 @@ class TestVolumeCreate(TestVolume): # project_id=, availability_zone=, # metadata=, imageRef=) self.volumes_mock.create.assert_called_with( - volume_fakes.volume_size, + self.new_volume.size, None, None, - volume_fakes.volume_name, + self.new_volume.display_name, None, None, None, @@ -313,13 +328,13 @@ class TestVolumeCreate(TestVolume): arglist = [ '--image', volume_fakes.image_id, - '--size', str(volume_fakes.volume_size), - volume_fakes.volume_name, + '--size', str(self.new_volume.size), + self.new_volume.display_name, ] verifylist = [ ('image', volume_fakes.image_id), - ('size', volume_fakes.volume_size), - ('name', volume_fakes.volume_name), + ('size', self.new_volume.size), + ('name', self.new_volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -334,10 +349,10 @@ class TestVolumeCreate(TestVolume): # project_id=, availability_zone=, # metadata=, imageRef=) self.volumes_mock.create.assert_called_with( - volume_fakes.volume_size, + self.new_volume.size, None, None, - volume_fakes.volume_name, + self.new_volume.display_name, None, None, None, @@ -359,13 +374,13 @@ class TestVolumeCreate(TestVolume): arglist = [ '--image', volume_fakes.image_name, - '--size', str(volume_fakes.volume_size), - volume_fakes.volume_name, + '--size', str(self.new_volume.size), + self.new_volume.display_name, ] verifylist = [ ('image', volume_fakes.image_name), - ('size', volume_fakes.volume_size), - ('name', volume_fakes.volume_name), + ('size', self.new_volume.size), + ('name', self.new_volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -380,10 +395,10 @@ class TestVolumeCreate(TestVolume): # project_id=, availability_zone=, # metadata=, imageRef=) self.volumes_mock.create.assert_called_with( - volume_fakes.volume_size, + self.new_volume.size, None, None, - volume_fakes.volume_name, + self.new_volume.display_name, None, None, None, @@ -397,8 +412,100 @@ class TestVolumeCreate(TestVolume): self.assertEqual(self.datalist, data) +class TestVolumeDelete(TestVolume): + + def setUp(self): + super(TestVolumeDelete, self).setUp() + + self.volumes_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume.DeleteVolume(self.app, None) + + def test_volume_delete_one_volume(self): + volumes = self.setup_volumes_mock(count=1) + + arglist = [ + volumes[0].id + ] + verifylist = [ + ("force", False), + ("volumes", [volumes[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volumes_mock.delete.assert_called_once_with(volumes[0].id) + self.assertIsNone(result) + + def test_volume_delete_multi_volumes(self): + volumes = self.setup_volumes_mock(count=3) + + arglist = [v.id for v in volumes] + verifylist = [ + ('force', False), + ('volumes', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [call(v.id) for v in volumes] + self.volumes_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_volume_delete_multi_volumes_with_exception(self): + volumes = self.setup_volumes_mock(count=2) + + arglist = [ + volumes[0].id, + 'unexist_volume', + ] + verifylist = [ + ('force', False), + ('volumes', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [volumes[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 volumes failed to delete.', + str(e)) + + find_mock.assert_any_call(self.volumes_mock, volumes[0].id) + find_mock.assert_any_call(self.volumes_mock, 'unexist_volume') + + self.assertEqual(2, find_mock.call_count) + self.volumes_mock.delete.assert_called_once_with(volumes[0].id) + + def test_volume_delete_with_force(self): + volumes = self.setup_volumes_mock(count=1) + + arglist = [ + '--force', + volumes[0].id, + ] + verifylist = [ + ('force', True), + ('volumes', [volumes[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volumes_mock.force_delete.assert_called_once_with(volumes[0].id) + self.assertIsNone(result) + + class TestVolumeList(TestVolume): + _volume = volume_fakes.FakeVolume.create_one_volume() columns = ( 'ID', 'Display Name', @@ -406,26 +513,23 @@ class TestVolumeList(TestVolume): 'Size', 'Attached to', ) + server = _volume.attachments[0]['server_id'] + device = _volume.attachments[0]['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, - '', + _volume.id, + _volume.display_name, + _volume.status, + _volume.size, + msg, ), ) def setUp(self): super(TestVolumeList, self).setUp() - self.volumes_mock.list.return_value = [ - fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True, - ), - ] + self.volumes_mock.list.return_value = [self._volume] # Get the command object to test self.cmd = volume.ListVolume(self.app, None) @@ -437,6 +541,7 @@ class TestVolumeList(TestVolume): ('all_projects', False), ('name', None), ('status', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -447,13 +552,14 @@ class TestVolumeList(TestVolume): def test_volume_list_name(self): arglist = [ - '--name', volume_fakes.volume_name, + '--name', self._volume.display_name, ] verifylist = [ ('long', False), ('all_projects', False), - ('name', volume_fakes.volume_name), + ('name', self._volume.display_name), ('status', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -463,13 +569,14 @@ class TestVolumeList(TestVolume): def test_volume_list_status(self): arglist = [ - '--status', volume_fakes.volume_status, + '--status', self._volume.status, ] verifylist = [ ('long', False), ('all_projects', False), ('name', None), - ('status', volume_fakes.volume_status), + ('status', self._volume.status), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -486,6 +593,7 @@ class TestVolumeList(TestVolume): ('all_projects', True), ('name', None), ('status', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -502,6 +610,7 @@ class TestVolumeList(TestVolume): ('all_projects', False), ('name', None), ('status', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -521,47 +630,76 @@ class TestVolumeList(TestVolume): 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._volume.id, + self._volume.display_name, + self._volume.status, + self._volume.size, + self._volume.volume_type, + self._volume.bootable, + self.msg, + utils.format_dict(self._volume.metadata), ), ) self.assertEqual(datalist, tuple(data)) + def test_volume_list_with_limit(self): + arglist = [ + '--limit', '2', + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', None), + ('limit', 2), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.volumes_mock.list.assert_called_once_with( + limit=2, + search_opts={ + 'status': None, + 'display_name': None, + 'all_tenants': False, } + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_volume_list_negative_limit(self): + arglist = [ + "--limit", "-2", + ] + verifylist = [ + ("limit", -2), + ] + self.assertRaises(argparse.ArgumentTypeError, self.check_parser, + self.cmd, arglist, verifylist) + class TestVolumeSet(TestVolume): + _volume = volume_fakes.FakeVolume.create_one_volume() + def setUp(self): super(TestVolumeSet, self).setUp() - self.volumes_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True, - ) + self.volumes_mock.get.return_value = self._volume - self.volumes_mock.update.return_value = fakes.FakeResource( - None, - copy.deepcopy(volume_fakes.VOLUME), - loaded=True, - ) + self.volumes_mock.update.return_value = self._volume # Get the command object to test self.cmd = volume.SetVolume(self.app, None) def test_volume_set_no_options(self): arglist = [ - volume_fakes.volume_name, + self._volume.display_name, ] verifylist = [ ('name', None), ('description', None), ('size', None), ('property', None), - ('volume', volume_fakes.volume_name), + ('volume', self._volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -571,14 +709,14 @@ class TestVolumeSet(TestVolume): def test_volume_set_name(self): arglist = [ '--name', 'qwerty', - volume_fakes.volume_name, + self._volume.display_name, ] verifylist = [ ('name', 'qwerty'), ('description', None), ('size', None), ('property', None), - ('volume', volume_fakes.volume_name), + ('volume', self._volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -589,7 +727,7 @@ class TestVolumeSet(TestVolume): 'display_name': 'qwerty', } self.volumes_mock.update.assert_called_with( - volume_fakes.volume_id, + self._volume.id, **kwargs ) self.assertIsNone(result) @@ -597,14 +735,14 @@ class TestVolumeSet(TestVolume): def test_volume_set_description(self): arglist = [ '--description', 'new desc', - volume_fakes.volume_name, + self._volume.display_name, ] verifylist = [ ('name', None), ('description', 'new desc'), ('size', None), ('property', None), - ('volume', volume_fakes.volume_name), + ('volume', self._volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -615,7 +753,7 @@ class TestVolumeSet(TestVolume): 'display_description': 'new desc', } self.volumes_mock.update.assert_called_with( - volume_fakes.volume_id, + self._volume.id, **kwargs ) self.assertIsNone(result) @@ -623,14 +761,14 @@ class TestVolumeSet(TestVolume): def test_volume_set_size(self): arglist = [ '--size', '130', - volume_fakes.volume_name, + self._volume.display_name, ] verifylist = [ ('name', None), ('description', None), ('size', 130), ('property', None), - ('volume', volume_fakes.volume_name), + ('volume', self._volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -639,23 +777,24 @@ class TestVolumeSet(TestVolume): # Set expected values size = 130 self.volumes_mock.extend.assert_called_with( - volume_fakes.volume_id, + self._volume.id, size ) self.assertIsNone(result) @mock.patch.object(volume.LOG, 'error') def test_volume_set_size_smaller(self, mock_log_error): + self._volume.status = 'available' arglist = [ - '--size', '100', - volume_fakes.volume_name, + '--size', '1', + self._volume.display_name, ] verifylist = [ ('name', None), ('description', None), - ('size', 100), + ('size', 1), ('property', None), - ('volume', volume_fakes.volume_name), + ('volume', self._volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -663,22 +802,22 @@ class TestVolumeSet(TestVolume): mock_log_error.assert_called_with("New size must be greater " "than %s GB", - volume_fakes.volume_size) + self._volume.size) self.assertIsNone(result) @mock.patch.object(volume.LOG, 'error') def test_volume_set_size_not_available(self, mock_log_error): - self.volumes_mock.get.return_value.status = 'error' + self._volume.status = 'error' arglist = [ '--size', '130', - volume_fakes.volume_name, + self._volume.display_name, ] verifylist = [ ('name', None), ('description', None), ('size', 130), ('property', None), - ('volume', volume_fakes.volume_name), + ('volume', self._volume.display_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -692,14 +831,16 @@ class TestVolumeSet(TestVolume): def test_volume_set_property(self): arglist = [ '--property', 'myprop=myvalue', - volume_fakes.volume_name, + self._volume.display_name, ] verifylist = [ ('name', None), ('description', None), ('size', None), ('property', {'myprop': 'myvalue'}), - ('volume', volume_fakes.volume_name), + ('volume', self._volume.display_name), + ('bootable', False), + ('non_bootable', False) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -710,7 +851,131 @@ class TestVolumeSet(TestVolume): 'myprop': 'myvalue' } self.volumes_mock.set_metadata.assert_called_with( - volume_fakes.volume_id, + self._volume.id, metadata ) self.assertIsNone(result) + + def test_volume_set_bootable(self): + arglist = [ + ['--bootable', self._volume.id], + ['--non-bootable', self._volume.id] + ] + verifylist = [ + [ + ('bootable', True), + ('non_bootable', False), + ('volume', self._volume.id) + ], + [ + ('bootable', False), + ('non_bootable', True), + ('volume', self._volume.id) + ] + ] + for index in range(len(arglist)): + parsed_args = self.check_parser( + self.cmd, arglist[index], verifylist[index]) + + self.cmd.take_action(parsed_args) + self.volumes_mock.set_bootable.assert_called_with( + self._volume.id, verifylist[index][0][1]) + + +class TestVolumeShow(TestVolume): + + columns = ( + 'attachments', + 'availability_zone', + 'bootable', + 'created_at', + 'display_description', + 'display_name', + 'id', + 'properties', + 'size', + 'snapshot_id', + 'status', + 'type', + ) + + def setUp(self): + super(TestVolumeShow, self).setUp() + self._volume = volume_fakes.FakeVolume.create_one_volume() + self.datalist = ( + self._volume.attachments, + self._volume.availability_zone, + self._volume.bootable, + self._volume.created_at, + self._volume.display_description, + self._volume.display_name, + self._volume.id, + utils.format_dict(self._volume.metadata), + self._volume.size, + self._volume.snapshot_id, + self._volume.status, + self._volume.volume_type, + ) + self.volumes_mock.get.return_value = self._volume + # Get the command object to test + self.cmd = volume.ShowVolume(self.app, None) + + def test_volume_show(self): + arglist = [ + self._volume.id + ] + verifylist = [ + ("volume", self._volume.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.volumes_mock.get.assert_called_with(self._volume.id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, data) + + +class TestVolumeUnset(TestVolume): + + _volume = volume_fakes.FakeVolume.create_one_volume() + + def setUp(self): + super(TestVolumeUnset, self).setUp() + + self.volumes_mock.get.return_value = self._volume + + self.volumes_mock.delete_metadata.return_value = None + # Get the command object to test + self.cmd = volume.UnsetVolume(self.app, None) + + def test_volume_unset_no_options(self): + arglist = [ + self._volume.display_name, + ] + verifylist = [ + ('property', None), + ('volume', self._volume.display_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + def test_volume_unset_property(self): + arglist = [ + '--property', 'myprop', + self._volume.display_name, + ] + verifylist = [ + ('property', ['myprop']), + ('volume', self._volume.display_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.volumes_mock.delete_metadata.assert_called_with( + self._volume.id, ['myprop'] + ) + self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index a958c468..8d7ac831 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -25,6 +25,19 @@ from openstackclient.tests.unit.image.v2 import fakes as image_fakes from openstackclient.tests.unit import utils +QUOTA = { + "gigabytes": 1000, + "volumes": 11, + "snapshots": 10, + "backups": 10, + "backup_gigabytes": 1000, + "per_volume_gigabytes": -1, + "gigabytes_volume_type_backend": -1, + "volumes_volume_type_backend": -1, + "snapshots_volume_type_backend": -1, +} + + class FakeTransfer(object): """Fake one or more Transfer.""" @@ -39,9 +52,12 @@ class FakeTransfer(object): """ # Set default attribute transfer_info = { - 'volume_id': 'ce26708d-a7f8-4b4b-9861-4a80256615a7', + 'auth_key': 'key-' + uuid.uuid4().hex, + 'created_at': 'time-' + uuid.uuid4().hex, + 'volume_id': 'volume-id-' + uuid.uuid4().hex, 'name': 'fake_transfer_name', - 'id': '731a7f53-aa92-4fbd-9de3-6f7d729c926b' + 'id': 'id-' + uuid.uuid4().hex, + 'links': 'links-' + uuid.uuid4().hex, } # Overwrite default attributes if there are some attributes set @@ -56,6 +72,43 @@ class FakeTransfer(object): return transfer + @staticmethod + def create_transfers(attrs=None, count=2): + """Create multiple fake transfers. + + :param Dictionary attrs: + A dictionary with all attributes of transfer + :param Integer count: + The number of transfers to be faked + :return: + A list of FakeResource objects + """ + transfers = [] + for n in range(0, count): + transfers.append(FakeTransfer.create_one_transfer(attrs)) + + return transfers + + @staticmethod + def get_transfers(transfers=None, count=2): + """Get an iterable MagicMock object with a list of faked transfers. + + If transfers list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List transfers: + A list of FakeResource objects faking transfers + :param Integer count: + The number of transfers to be faked + :return + An iterable Mock object with side_effect set to a list of faked + transfers + """ + if transfers is None: + transfers = FakeTransfer.create_transfers(count) + + return mock.Mock(side_effect=transfers) + class FakeTypeAccess(object): """Fake one or more volume type access.""" @@ -167,6 +220,10 @@ class FakeVolumeClient(object): self.transfers.resource_class = fakes.FakeResource(None, {}) self.services = mock.Mock() self.services.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.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -274,7 +331,7 @@ class FakeVolume(object): if volumes is None: volumes = FakeVolume.create_volumes(count) - return mock.MagicMock(side_effect=volumes) + return mock.Mock(side_effect=volumes) @staticmethod def get_volume_columns(volume=None): @@ -435,7 +492,7 @@ class FakeBackup(object): if backups is None: backups = FakeBackup.create_backups(count) - return mock.MagicMock(side_effect=backups) + return mock.Mock(side_effect=backups) class FakeExtension(object): @@ -566,7 +623,7 @@ class FakeQos(object): if qoses is None: qoses = FakeQos.create_qoses(count) - return mock.MagicMock(side_effect=qoses) + return mock.Mock(side_effect=qoses) class FakeSnapshot(object): @@ -639,7 +696,7 @@ class FakeSnapshot(object): if snapshots is None: snapshots = FakeSnapshot.create_snapshots(count) - return mock.MagicMock(side_effect=snapshots) + return mock.Mock(side_effect=snapshots) class FakeType(object): diff --git a/openstackclient/tests/unit/volume/v2/test_snapshot.py b/openstackclient/tests/unit/volume/v2/test_snapshot.py index 333d8d72..d355662d 100644 --- a/openstackclient/tests/unit/volume/v2/test_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_snapshot.py @@ -376,6 +376,55 @@ class TestSnapshotSet(TestSnapshot): self.snapshot.id, "error") self.assertIsNone(result) + def test_volume_set_state_failed(self): + self.snapshots_mock.reset_state.side_effect = exceptions.CommandError() + arglist = [ + '--state', 'error', + self.snapshot.id + ] + verifylist = [ + ('state', 'error'), + ('snapshot', self.snapshot.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('One or more of the set operations failed', + str(e)) + self.snapshots_mock.reset_state.assert_called_once_with( + self.snapshot.id, 'error') + + def test_volume_set_name_and_state_failed(self): + self.snapshots_mock.reset_state.side_effect = exceptions.CommandError() + arglist = [ + '--state', 'error', + "--name", "new_snapshot", + self.snapshot.id + ] + verifylist = [ + ('state', 'error'), + ("name", "new_snapshot"), + ('snapshot', self.snapshot.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('One or more of the set operations failed', + str(e)) + kwargs = { + "name": "new_snapshot", + } + self.snapshots_mock.update.assert_called_once_with( + self.snapshot.id, **kwargs) + self.snapshots_mock.reset_state.assert_called_once_with( + self.snapshot.id, 'error') + class TestSnapshotShow(TestSnapshot): diff --git a/openstackclient/tests/unit/volume/v2/test_transfer_request.py b/openstackclient/tests/unit/volume/v2/test_transfer_request.py index 32108c02..b4f89089 100644 --- a/openstackclient/tests/unit/volume/v2/test_transfer_request.py +++ b/openstackclient/tests/unit/volume/v2/test_transfer_request.py @@ -12,6 +12,11 @@ # under the License. # +import mock +from mock import call + +from osc_lib import exceptions +from osc_lib import utils from openstackclient.tests.unit.volume.v2 import fakes as transfer_fakes from openstackclient.volume.v2 import volume_transfer_request @@ -26,6 +31,155 @@ class TestTransfer(transfer_fakes.TestVolume): self.transfer_mock = self.app.client_manager.volume.transfers self.transfer_mock.reset_mock() + # Get a shortcut to the VolumeManager Mock + self.volumes_mock = self.app.client_manager.volume.volumes + self.volumes_mock.reset_mock() + + +class TestTransferCreate(TestTransfer): + + volume = transfer_fakes.FakeVolume.create_one_volume() + + columns = ( + 'auth_key', + 'created_at', + 'id', + 'name', + 'volume_id', + ) + + def setUp(self): + super(TestTransferCreate, self).setUp() + + self.volume_transfer = transfer_fakes.FakeTransfer.create_one_transfer( + attrs={'volume_id': self.volume.id}) + self.data = ( + self.volume_transfer.auth_key, + self.volume_transfer.created_at, + self.volume_transfer.id, + self.volume_transfer.name, + self.volume_transfer.volume_id, + ) + + self.transfer_mock.create.return_value = self.volume_transfer + self.volumes_mock.get.return_value = self.volume + + # Get the command object to test + self.cmd = volume_transfer_request.CreateTransferRequest( + self.app, None) + + def test_transfer_create_without_name(self): + arglist = [ + self.volume.id, + ] + verifylist = [ + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.create.assert_called_once_with( + self.volume.id, None) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_transfer_create_with_name(self): + arglist = [ + '--name', self.volume_transfer.name, + self.volume.id, + ] + verifylist = [ + ('name', self.volume_transfer.name), + ('volume', self.volume.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.transfer_mock.create.assert_called_once_with( + self.volume.id, self.volume_transfer.name,) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + +class TestTransferDelete(TestTransfer): + + volume_transfers = transfer_fakes.FakeTransfer.create_transfers(count=2) + + def setUp(self): + super(TestTransferDelete, self).setUp() + + self.transfer_mock.get = ( + transfer_fakes.FakeTransfer.get_transfers(self.volume_transfers)) + self.transfer_mock.delete.return_value = None + + # Get the command object to mock + self.cmd = volume_transfer_request.DeleteTransferRequest( + self.app, None) + + def test_transfer_delete(self): + arglist = [ + self.volume_transfers[0].id + ] + verifylist = [ + ("transfer_request", [self.volume_transfers[0].id]) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.transfer_mock.delete.assert_called_with( + self.volume_transfers[0].id) + self.assertIsNone(result) + + def test_delete_multiple_transfers(self): + arglist = [] + for v in self.volume_transfers: + arglist.append(v.id) + verifylist = [ + ('transfer_request', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + calls = [] + for v in self.volume_transfers: + calls.append(call(v.id)) + self.transfer_mock.delete.assert_has_calls(calls) + self.assertIsNone(result) + + def test_delete_multiple_transfers_with_exception(self): + arglist = [ + self.volume_transfers[0].id, + 'unexist_transfer', + ] + verifylist = [ + ('transfer_request', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self.volume_transfers[0], exceptions.CommandError] + with mock.patch.object(utils, 'find_resource', + side_effect=find_mock_result) as find_mock: + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 volume transfer requests failed ' + 'to delete.', str(e)) + + find_mock.assert_any_call( + self.transfer_mock, self.volume_transfers[0].id) + find_mock.assert_any_call(self.transfer_mock, 'unexist_transfer') + + self.assertEqual(2, find_mock.call_count) + self.transfer_mock.delete.assert_called_once_with( + self.volume_transfers[0].id, + ) + class TestTransferList(TestTransfer): @@ -38,7 +192,7 @@ class TestTransferList(TestTransfer): self.transfer_mock.list.return_value = [self.volume_transfers] # Get the command object to test - self.cmd = volume_transfer_request.ListTransferRequests(self.app, None) + self.cmd = volume_transfer_request.ListTransferRequest(self.app, None) def test_transfer_list_without_argument(self): arglist = [] diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index 66f8f74d..5bdde9de 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -12,6 +12,7 @@ # under the License. # +import argparse import mock from mock import call @@ -553,6 +554,8 @@ class TestVolumeList(TestVolume): ('all_projects', False), ('name', None), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -581,6 +584,8 @@ class TestVolumeList(TestVolume): ('long', False), ('all_projects', False), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -611,6 +616,8 @@ class TestVolumeList(TestVolume): ('long', False), ('all_projects', False), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -639,6 +646,8 @@ class TestVolumeList(TestVolume): ('long', False), ('all_projects', False), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -668,6 +677,8 @@ class TestVolumeList(TestVolume): ('long', False), ('all_projects', False), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -696,6 +707,8 @@ class TestVolumeList(TestVolume): ('all_projects', False), ('name', self.mock_volume.name), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -724,6 +737,8 @@ class TestVolumeList(TestVolume): ('all_projects', False), ('name', None), ('status', self.mock_volume.status), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -752,6 +767,8 @@ class TestVolumeList(TestVolume): ('all_projects', True), ('name', None), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -780,6 +797,8 @@ class TestVolumeList(TestVolume): ('all_projects', False), ('name', None), ('status', None), + ('marker', None), + ('limit', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -813,6 +832,58 @@ class TestVolumeList(TestVolume): ), ) self.assertEqual(datalist, tuple(data)) + def test_volume_list_with_marker_and_limit(self): + arglist = [ + "--marker", self.mock_volume.id, + "--limit", "2", + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', None), + ('marker', self.mock_volume.id), + ('limit', 2), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + + server = self.mock_volume.attachments[0]['server_id'] + device = self.mock_volume.attachments[0]['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + self.mock_volume.id, + self.mock_volume.name, + self.mock_volume.status, + self.mock_volume.size, + msg, + ), ) + + self.volumes_mock.list.assert_called_once_with( + marker=self.mock_volume.id, + limit=2, + search_opts={ + 'status': None, + 'project_id': None, + 'user_id': None, + 'display_name': None, + 'all_tenants': False, } + ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_negative_limit(self): + arglist = [ + "--limit", "-2", + ] + verifylist = [ + ("limit", -2), + ] + self.assertRaises(argparse.ArgumentTypeError, self.check_parser, + self.cmd, arglist, verifylist) + class TestVolumeSet(TestVolume): @@ -834,6 +905,8 @@ class TestVolumeSet(TestVolume): verifylist = [ ('image_property', {'Alpha': 'a', 'Beta': 'b'}), ('volume', self.new_volume.id), + ('bootable', False), + ('non_bootable', False) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -881,6 +954,31 @@ class TestVolumeSet(TestVolume): self.volumes_mock.reset_state.assert_called_with( self.new_volume.id, 'error') + def test_volume_set_bootable(self): + arglist = [ + ['--bootable', self.new_volume.id], + ['--non-bootable', self.new_volume.id] + ] + verifylist = [ + [ + ('bootable', True), + ('non_bootable', False), + ('volume', self.new_volume.id) + ], + [ + ('bootable', False), + ('non_bootable', True), + ('volume', self.new_volume.id) + ] + ] + for index in range(len(arglist)): + parsed_args = self.check_parser( + self.cmd, arglist[index], verifylist[index]) + + self.cmd.take_action(parsed_args) + self.volumes_mock.set_bootable.assert_called_with( + self.new_volume.id, verifylist[index][0][1]) + class TestVolumeShow(TestVolume): @@ -964,3 +1062,30 @@ class TestVolumeUnset(TestVolume): self.volumes_mock.delete_image_metadata.assert_called_with( self.new_volume.id, parsed_args_unset.image_property) + + def test_volume_unset_image_property_fail(self): + self.volumes_mock.delete_image_metadata.side_effect = ( + exceptions.CommandError()) + arglist = [ + '--image-property', 'Alpha', + '--property', 'Beta', + self.new_volume.id, + ] + verifylist = [ + ('image_property', ['Alpha']), + ('property', ['Beta']), + ('volume', self.new_volume.id), + ] + parsed_args = self.check_parser( + self.cmd_unset, arglist, verifylist) + + try: + self.cmd_unset.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('One or more of the unset operations failed', + str(e)) + self.volumes_mock.delete_image_metadata.assert_called_with( + self.new_volume.id, parsed_args.image_property) + self.volumes_mock.delete_metadata.assert_called_with( + self.new_volume.id, parsed_args.property) diff --git a/openstackclient/tests/volume/v1/test_service.py b/openstackclient/tests/volume/v1/test_service.py deleted file mode 100644 index 273a1156..00000000 --- a/openstackclient/tests/volume/v1/test_service.py +++ /dev/null @@ -1,286 +0,0 @@ -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. -# - -from osc_lib import exceptions - -from openstackclient.tests.volume.v1 import fakes as service_fakes -from openstackclient.volume.v1 import service - - -class TestService(service_fakes.TestVolumev1): - - def setUp(self): - super(TestService, self).setUp() - - # Get a shortcut to the ServiceManager Mock - self.service_mock = self.app.client_manager.volume.services - self.service_mock.reset_mock() - - -class TestServiceList(TestService): - - # The service to be listed - services = service_fakes.FakeService.create_one_service() - - def setUp(self): - super(TestServiceList, self).setUp() - - self.service_mock.list.return_value = [self.services] - - # Get the command object to test - self.cmd = service.ListService(self.app, None) - - def test_service_list(self): - arglist = [ - '--host', self.services.host, - '--service', self.services.binary, - ] - verifylist = [ - ('host', self.services.host), - ('service', self.services.binary), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - expected_columns = [ - 'Binary', - 'Host', - 'Zone', - 'Status', - 'State', - 'Updated At', - ] - - # confirming if all expected columns are present in the result. - self.assertEqual(expected_columns, columns) - - datalist = (( - self.services.binary, - self.services.host, - self.services.zone, - self.services.status, - self.services.state, - self.services.updated_at, - ), ) - - # confirming if all expected values are present in the result. - self.assertEqual(datalist, tuple(data)) - - # checking if proper call was made to list services - self.service_mock.list.assert_called_with( - self.services.host, - self.services.binary, - ) - - # checking if prohibited columns are present in output - self.assertNotIn("Disabled Reason", columns) - self.assertNotIn(self.services.disabled_reason, - tuple(data)) - - def test_service_list_with_long_option(self): - arglist = [ - '--host', self.services.host, - '--service', self.services.binary, - '--long' - ] - verifylist = [ - ('host', self.services.host), - ('service', self.services.binary), - ('long', True) - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # In base command class Lister in cliff, abstract method take_action() - # returns a tuple containing the column names and an iterable - # containing the data to be listed. - columns, data = self.cmd.take_action(parsed_args) - - expected_columns = [ - 'Binary', - 'Host', - 'Zone', - 'Status', - 'State', - 'Updated At', - 'Disabled Reason' - ] - - # confirming if all expected columns are present in the result. - self.assertEqual(expected_columns, columns) - - datalist = (( - self.services.binary, - self.services.host, - self.services.zone, - self.services.status, - self.services.state, - self.services.updated_at, - self.services.disabled_reason, - ), ) - - # confirming if all expected values are present in the result. - self.assertEqual(datalist, tuple(data)) - - self.service_mock.list.assert_called_with( - self.services.host, - self.services.binary, - ) - - -class TestServiceSet(TestService): - - service = service_fakes.FakeService.create_one_service() - - def setUp(self): - super(TestServiceSet, self).setUp() - - self.service_mock.enable.return_value = self.service - self.service_mock.disable.return_value = self.service - self.service_mock.disable_log_reason.return_value = self.service - - self.cmd = service.SetService(self.app, None) - - def test_service_set_nothing(self): - arglist = [ - self.service.host, - self.service.binary, - ] - verifylist = [ - ('host', self.service.host), - ('service', self.service.binary), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - self.service_mock.enable.assert_not_called() - self.service_mock.disable.assert_not_called() - self.service_mock.disable_log_reason.assert_not_called() - self.assertIsNone(result) - - def test_service_set_enable(self): - arglist = [ - '--enable', - self.service.host, - self.service.binary, - ] - verifylist = [ - ('enable', True), - ('host', self.service.host), - ('service', self.service.binary), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.service_mock.enable.assert_called_with( - self.service.host, - self.service.binary - ) - self.service_mock.disable.assert_not_called() - self.service_mock.disable_log_reason.assert_not_called() - self.assertIsNone(result) - - def test_service_set_disable(self): - arglist = [ - '--disable', - self.service.host, - self.service.binary, - ] - verifylist = [ - ('disable', True), - ('host', self.service.host), - ('service', self.service.binary), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.service_mock.disable.assert_called_with( - self.service.host, - self.service.binary - ) - self.service_mock.enable.assert_not_called() - self.service_mock.disable_log_reason.assert_not_called() - self.assertIsNone(result) - - def test_service_set_disable_with_reason(self): - reason = 'earthquake' - arglist = [ - '--disable', - '--disable-reason', reason, - self.service.host, - self.service.binary, - ] - verifylist = [ - ('disable', True), - ('disable_reason', reason), - ('host', self.service.host), - ('service', self.service.binary), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - result = self.cmd.take_action(parsed_args) - - self.service_mock.disable_log_reason.assert_called_with( - self.service.host, - self.service.binary, - reason - ) - self.assertIsNone(result) - - def test_service_set_only_with_disable_reason(self): - reason = 'earthquake' - arglist = [ - '--disable-reason', reason, - self.service.host, - self.service.binary, - ] - verifylist = [ - ('disable_reason', reason), - ('host', self.service.host), - ('service', self.service.binary), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - try: - self.cmd.take_action(parsed_args) - self.fail("CommandError should be raised.") - except exceptions.CommandError as e: - self.assertEqual("Cannot specify option --disable-reason without " - "--disable specified.", str(e)) - - def test_service_set_enable_with_disable_reason(self): - reason = 'earthquake' - arglist = [ - '--enable', - '--disable-reason', reason, - self.service.host, - self.service.binary, - ] - verifylist = [ - ('enable', True), - ('disable_reason', reason), - ('host', self.service.host), - ('service', self.service.binary), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - try: - self.cmd.take_action(parsed_args) - self.fail("CommandError should be raised.") - except exceptions.CommandError as e: - self.assertEqual("Cannot specify option --disable-reason without " - "--disable specified.", str(e)) diff --git a/openstackclient/volume/v1/backup.py b/openstackclient/volume/v1/backup.py index 539ed369..c9d0ca0d 100644 --- a/openstackclient/volume/v1/backup.py +++ b/openstackclient/volume/v1/backup.py @@ -19,12 +19,16 @@ import copy import logging from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateVolumeBackup(command.ShowOne): """Create new volume backup""" @@ -100,10 +104,24 @@ class DeleteVolumeBackup(command.Command): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for backup in parsed_args.backups: - backup_id = utils.find_resource(volume_client.backups, - backup).id - volume_client.backups.delete(backup_id) + result = 0 + + for i in parsed_args.backups: + try: + backup_id = utils.find_resource( + volume_client.backups, i).id + volume_client.backups.delete(backup_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete backup with " + "name or ID '%(backup)s': %(e)s"), + {'backup': i, 'e': e}) + + if result > 0: + total = len(parsed_args.backups) + msg = (_("%(result)s of %(total)s backups failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class DeleteBackup(DeleteVolumeBackup): diff --git a/openstackclient/volume/v1/qos_specs.py b/openstackclient/volume/v1/qos_specs.py index c5850871..b982c0e6 100644 --- a/openstackclient/volume/v1/qos_specs.py +++ b/openstackclient/volume/v1/qos_specs.py @@ -15,14 +15,20 @@ """Volume v1 QoS action implementations""" +import logging + from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class AssociateQos(command.Command): """Associate a QoS specification to a volume type""" @@ -113,9 +119,23 @@ class DeleteQos(command.Command): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for qos in parsed_args.qos_specs: - qos_spec = utils.find_resource(volume_client.qos_specs, qos) - volume_client.qos_specs.delete(qos_spec.id, parsed_args.force) + result = 0 + + for i in parsed_args.qos_specs: + try: + qos_spec = utils.find_resource(volume_client.qos_specs, i) + volume_client.qos_specs.delete(qos_spec.id, parsed_args.force) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete QoS specification with " + "name or ID '%(qos)s': %(e)s"), + {'qos': i, 'e': e}) + + if result > 0: + total = len(parsed_args.qos_specs) + msg = (_("%(result)s of %(total)s QoS specifications failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class DisassociateQos(command.Command): diff --git a/openstackclient/volume/v1/snapshot.py b/openstackclient/volume/v1/snapshot.py index bb3a1fc3..c4d113a3 100644 --- a/openstackclient/volume/v1/snapshot.py +++ b/openstackclient/volume/v1/snapshot.py @@ -16,15 +16,20 @@ """Volume v1 Snapshot action implementations""" import copy +import logging from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six from openstackclient.i18n import _ +LOG = logging.getLogger(__name__) + + class CreateSnapshot(command.ShowOne): """Create new snapshot""" @@ -88,10 +93,24 @@ class DeleteSnapshot(command.Command): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - for snapshot in parsed_args.snapshots: - snapshot_id = utils.find_resource(volume_client.volume_snapshots, - snapshot).id - volume_client.volume_snapshots.delete(snapshot_id) + result = 0 + + for i in parsed_args.snapshots: + try: + snapshot_id = utils.find_resource( + volume_client.volume_snapshots, i).id + volume_client.volume_snapshots.delete(snapshot_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete snapshot with " + "name or ID '%(snapshot)s': %(e)s"), + {'snapshot': i, 'e': e}) + + if result > 0: + total = len(parsed_args.snapshots) + msg = (_("%(result)s of %(total)s snapshots failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListSnapshot(command.Lister): @@ -199,17 +218,31 @@ class SetSnapshot(command.Command): snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) + result = 0 if parsed_args.property: - volume_client.volume_snapshots.set_metadata(snapshot.id, - parsed_args.property) + try: + volume_client.volume_snapshots.set_metadata( + snapshot.id, parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set snapshot property: %s"), e) + result += 1 kwargs = {} if parsed_args.name: kwargs['display_name'] = parsed_args.name if parsed_args.description: kwargs['display_description'] = parsed_args.description - - snapshot.update(**kwargs) + if kwargs: + try: + snapshot.update(**kwargs) + except Exception as e: + LOG.error(_("Failed to update snapshot display name " + "or display description: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "set operations failed")) class ShowSnapshot(command.ShowOne): diff --git a/openstackclient/volume/v1/volume.py b/openstackclient/volume/v1/volume.py index 820673bb..89fa2014 100644 --- a/openstackclient/volume/v1/volume.py +++ b/openstackclient/volume/v1/volume.py @@ -20,6 +20,7 @@ import logging from osc_lib.cli import parseractions from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils import six @@ -184,13 +185,27 @@ class DeleteVolume(command.Command): def take_action(self, parsed_args): volume_client = self.app.client_manager.volume - 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) + result = 0 + + for i in parsed_args.volumes: + try: + volume_obj = utils.find_resource( + volume_client.volumes, i) + if parsed_args.force: + volume_client.volumes.force_delete(volume_obj.id) + else: + volume_client.volumes.delete(volume_obj.id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume with " + "name or ID '%(volume)s': %(e)s"), + {'volume': i, 'e': e}) + + if result > 0: + total = len(parsed_args.volumes) + msg = (_("%(result)s of %(total)s volumes failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListVolume(command.Lister): @@ -220,6 +235,13 @@ class ListVolume(command.Lister): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '--limit', + type=int, + action=parseractions.NonNegativeAction, + metavar='<limit>', + help=_('Maximum number of volumes to display'), + ) return parser def take_action(self, parsed_args): @@ -295,7 +317,10 @@ class ListVolume(command.Lister): 'status': parsed_args.status, } - data = volume_client.volumes.list(search_opts=search_opts) + data = volume_client.volumes.list( + search_opts=search_opts, + limit=parsed_args.limit, + ) return (column_headers, (utils.get_item_properties( @@ -338,6 +363,17 @@ class SetVolume(command.Command): help=_('Set a property on this volume ' '(repeat option to set multiple properties)'), ) + bootable_group = parser.add_mutually_exclusive_group() + bootable_group.add_argument( + "--bootable", + action="store_true", + help=_("Mark volume as bootable") + ) + bootable_group.add_argument( + "--non-bootable", + action="store_true", + help=_("Mark volume as non-bootable") + ) return parser def take_action(self, parsed_args): @@ -357,7 +393,12 @@ class SetVolume(command.Command): if parsed_args.property: volume_client.volumes.set_metadata(volume.id, parsed_args.property) - + if parsed_args.bootable or parsed_args.non_bootable: + try: + volume_client.volumes.set_bootable( + volume.id, parsed_args.bootable) + except Exception as e: + LOG.error(_("Failed to set volume bootable property: %s"), e) kwargs = {} if parsed_args.name: kwargs['display_name'] = parsed_args.name @@ -411,10 +452,8 @@ class UnsetVolume(command.Command): '--property', metavar='<key>', action='append', - default=[], help=_('Remove a property from volume ' '(repeat option to remove multiple properties)'), - required=True, ) return parser diff --git a/openstackclient/volume/v1/volume_transfer_request.py b/openstackclient/volume/v1/volume_transfer_request.py index 5d8ff683..a985f8e5 100644 --- a/openstackclient/volume/v1/volume_transfer_request.py +++ b/openstackclient/volume/v1/volume_transfer_request.py @@ -12,19 +12,90 @@ # under the License. # -"""Volume v2 transfer action implementations""" +"""Volume v1 transfer action implementations""" + +import logging from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils +import six from openstackclient.i18n import _ -class ListTransferRequests(command.Lister): +LOG = logging.getLogger(__name__) + + +class CreateTransferRequest(command.ShowOne): + """Create volume transfer request.""" + + def get_parser(self, prog_name): + parser = super(CreateTransferRequest, self).get_parser(prog_name) + parser.add_argument( + '--name', + metavar="<name>", + help=_('New transfer request name (default to None)') + ) + parser.add_argument( + 'volume', + metavar="<volume>", + help=_('Volume to transfer (name or ID)') + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume_id = utils.find_resource( + volume_client.volumes, parsed_args.volume).id + volume_transfer_request = volume_client.transfers.create( + volume_id, parsed_args.name, + ) + volume_transfer_request._info.pop("links", None) + + return zip(*sorted(six.iteritems(volume_transfer_request._info))) + + +class DeleteTransferRequest(command.Command): + """Delete volume transfer request(s).""" + + def get_parser(self, prog_name): + parser = super(DeleteTransferRequest, self).get_parser(prog_name) + parser.add_argument( + 'transfer_request', + metavar="<transfer-request>", + nargs="+", + help=_('Volume transfer request(s) to delete (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result = 0 + + for t in parsed_args.transfer_request: + try: + transfer_request_id = utils.find_resource( + volume_client.transfers, t).id + volume_client.transfers.delete(transfer_request_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume transfer request " + "with name or ID '%(transfer)s': %(e)s") + % {'transfer': t, 'e': e}) + + if result > 0: + total = len(parsed_args.transfer_request) + msg = (_("%(result)s of %(total)s volume transfer requests failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListTransferRequest(command.Lister): """Lists all volume transfer requests.""" def get_parser(self, prog_name): - parser = super(ListTransferRequests, self).get_parser(prog_name) + parser = super(ListTransferRequest, self).get_parser(prog_name) parser.add_argument( '--all-projects', dest='all_projects', diff --git a/openstackclient/volume/v1/volume_type.py b/openstackclient/volume/v1/volume_type.py index 3fe4fa05..625b34dc 100644 --- a/openstackclient/volume/v1/volume_type.py +++ b/openstackclient/volume/v1/volume_type.py @@ -55,10 +55,9 @@ class CreateVolumeType(command.ShowOne): if parsed_args.property: result = volume_type.set_keys(parsed_args.property) volume_type._info.update({'properties': utils.format_dict(result)}) + volume_type._info.pop("os-volume-type-access:is_public", None) - info = {} - info.update(volume_type._info) - return zip(*sorted(six.iteritems(info))) + return zip(*sorted(six.iteritems(volume_type._info))) class DeleteVolumeType(command.Command): @@ -171,6 +170,7 @@ class ShowVolumeType(command.ShowOne): volume_client.volume_types, parsed_args.volume_type) properties = utils.format_dict(volume_type._info.pop('extra_specs')) volume_type._info.update({'properties': properties}) + volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) diff --git a/openstackclient/volume/v2/snapshot.py b/openstackclient/volume/v2/snapshot.py index 8304a5eb..0d826551 100644 --- a/openstackclient/volume/v2/snapshot.py +++ b/openstackclient/volume/v2/snapshot.py @@ -240,19 +240,40 @@ class SetSnapshot(command.Command): snapshot = utils.find_resource(volume_client.volume_snapshots, parsed_args.snapshot) + result = 0 + if parsed_args.property: + try: + volume_client.volume_snapshots.set_metadata( + snapshot.id, parsed_args.property) + except Exception as e: + LOG.error(_("Failed to set snapshot property: %s"), e) + result += 1 + + if parsed_args.state: + try: + volume_client.volume_snapshots.reset_state( + snapshot.id, parsed_args.state) + except Exception as e: + LOG.error(_("Failed to set snapshot state: %s"), e) + result += 1 + kwargs = {} if parsed_args.name: kwargs['name'] = parsed_args.name if parsed_args.description: kwargs['description'] = parsed_args.description + if kwargs: + try: + volume_client.volume_snapshots.update( + snapshot.id, **kwargs) + except Exception as e: + LOG.error(_("Failed to update snapshot name " + "or description: %s"), e) + result += 1 - if parsed_args.property: - volume_client.volume_snapshots.set_metadata(snapshot.id, - parsed_args.property) - if parsed_args.state: - volume_client.volume_snapshots.reset_state(snapshot.id, - parsed_args.state) - volume_client.volume_snapshots.update(snapshot.id, **kwargs) + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "set operations failed")) class ShowSnapshot(command.ShowOne): diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index bd201e00..0805b2be 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -246,6 +246,18 @@ class ListVolume(command.Lister): default=False, help=_('List additional fields in output'), ) + parser.add_argument( + '--marker', + metavar='<marker>', + help=_('The last volume ID of the previous page'), + ) + parser.add_argument( + '--limit', + type=int, + action=parseractions.NonNegativeAction, + metavar='<limit>', + help=_('Maximum number of volumes to display'), + ) return parser def take_action(self, parsed_args): @@ -328,7 +340,11 @@ class ListVolume(command.Lister): 'status': parsed_args.status, } - data = volume_client.volumes.list(search_opts=search_opts) + data = volume_client.volumes.list( + search_opts=search_opts, + marker=parsed_args.marker, + limit=parsed_args.limit, + ) return (column_headers, (utils.get_item_properties( @@ -388,6 +404,17 @@ class SetVolume(command.Command): '"deleting", "in-use", "attaching", "detaching", ' '"error_deleting" or "maintenance")'), ) + bootable_group = parser.add_mutually_exclusive_group() + bootable_group.add_argument( + "--bootable", + action="store_true", + help=_("Mark volume as bootable") + ) + bootable_group.add_argument( + "--non-bootable", + action="store_true", + help=_("Mark volume as non-bootable") + ) return parser def take_action(self, parsed_args): @@ -430,6 +457,13 @@ class SetVolume(command.Command): except Exception as e: LOG.error(_("Failed to set volume state: %s"), e) result += 1 + if parsed_args.bootable or parsed_args.non_bootable: + try: + volume_client.volumes.set_bootable( + volume.id, parsed_args.bootable) + except Exception as e: + LOG.error(_("Failed to set volume bootable property: %s"), e) + result += 1 kwargs = {} if parsed_args.name: @@ -456,7 +490,7 @@ class ShowVolume(command.ShowOne): parser = super(ShowVolume, self).get_parser(prog_name) parser.add_argument( 'volume', - metavar="<volume-id>", + metavar="<volume>", help=_("Volume to display (name or ID)") ) return parser @@ -511,9 +545,23 @@ class UnsetVolume(command.Command): volume = utils.find_resource( volume_client.volumes, parsed_args.volume) + result = 0 if parsed_args.property: - volume_client.volumes.delete_metadata( - volume.id, parsed_args.property) + try: + volume_client.volumes.delete_metadata( + volume.id, parsed_args.property) + except Exception as e: + LOG.error(_("Failed to unset volume property: %s"), e) + result += 1 + if parsed_args.image_property: - volume_client.volumes.delete_image_metadata( - volume.id, parsed_args.image_property) + try: + volume_client.volumes.delete_image_metadata( + volume.id, parsed_args.image_property) + except Exception as e: + LOG.error(_("Failed to unset image property: %s"), e) + result += 1 + + if result > 0: + raise exceptions.CommandError(_("One or more of the " + "unset operations failed")) diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index 5d8ff683..8e79807c 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -14,17 +14,88 @@ """Volume v2 transfer action implementations""" +import logging + from osc_lib.command import command +from osc_lib import exceptions from osc_lib import utils +import six from openstackclient.i18n import _ -class ListTransferRequests(command.Lister): +LOG = logging.getLogger(__name__) + + +class CreateTransferRequest(command.ShowOne): + """Create volume transfer request.""" + + def get_parser(self, prog_name): + parser = super(CreateTransferRequest, self).get_parser(prog_name) + parser.add_argument( + '--name', + metavar="<name>", + help=_('New transfer request name (default to None)'), + ) + parser.add_argument( + 'volume', + metavar="<volume>", + help=_('Volume to transfer (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + volume_id = utils.find_resource( + volume_client.volumes, parsed_args.volume).id + volume_transfer_request = volume_client.transfers.create( + volume_id, parsed_args.name, + ) + volume_transfer_request._info.pop("links", None) + + return zip(*sorted(six.iteritems(volume_transfer_request._info))) + + +class DeleteTransferRequest(command.Command): + """Delete volume transfer request(s).""" + + def get_parser(self, prog_name): + parser = super(DeleteTransferRequest, self).get_parser(prog_name) + parser.add_argument( + 'transfer_request', + metavar="<transfer-request>", + nargs="+", + help=_('Volume transfer request(s) to delete (name or ID)'), + ) + return parser + + def take_action(self, parsed_args): + volume_client = self.app.client_manager.volume + result = 0 + + for t in parsed_args.transfer_request: + try: + transfer_request_id = utils.find_resource( + volume_client.transfers, t).id + volume_client.transfers.delete(transfer_request_id) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete volume transfer request " + "with name or ID '%(transfer)s': %(e)s") + % {'transfer': t, 'e': e}) + + if result > 0: + total = len(parsed_args.transfer_request) + msg = (_("%(result)s of %(total)s volume transfer requests failed" + " to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListTransferRequest(command.Lister): """Lists all volume transfer requests.""" def get_parser(self, prog_name): - parser = super(ListTransferRequests, self).get_parser(prog_name) + parser = super(ListTransferRequest, self).get_parser(prog_name) parser.add_argument( '--all-projects', dest='all_projects', diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index e42fffe0..80a1f21b 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -110,6 +110,7 @@ class CreateVolumeType(command.ShowOne): if parsed_args.property: result = volume_type.set_keys(parsed_args.property) volume_type._info.update({'properties': utils.format_dict(result)}) + volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) @@ -318,6 +319,7 @@ class ShowVolumeType(command.ShowOne): '%(type)s: %(e)s') LOG.error(msg % {'type': volume_type.id, 'e': e}) volume_type._info.update({'access_project_ids': access_project_ids}) + volume_type._info.pop("os-volume-type-access:is_public", None) return zip(*sorted(six.iteritems(volume_type._info))) |
