diff options
Diffstat (limited to 'openstackclient')
32 files changed, 1691 insertions, 188 deletions
diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 8b0fb921..9c2b320c 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -195,6 +195,18 @@ class ClientManager(object): not self._auth_params.get('user_domain_name')): self._auth_params['user_domain_id'] = default_domain + # NOTE(hieulq): If USER_DOMAIN_NAME, USER_DOMAIN_ID, PROJECT_DOMAIN_ID + # or PROJECT_DOMAIN_NAME is present and API_VERSION is 2.0, then + # ignore all domain related configs. + if (self._api_version.get('identity') == '2.0' and + self.auth_plugin_name.endswith('password')): + domain_props = ['project_domain_name', 'project_domain_id', + 'user_domain_name', 'user_domain_id'] + for prop in domain_props: + if self._auth_params.pop(prop, None) is not None: + LOG.warning("Ignoring domain related configs " + + prop + " because identity API version is 2.0") + # For compatibility until all clients can be updated if 'project_name' in self._auth_params: self._project_name = self._auth_params['project_name'] diff --git a/openstackclient/common/parseractions.py b/openstackclient/common/parseractions.py index c30058c8..77798f90 100644 --- a/openstackclient/common/parseractions.py +++ b/openstackclient/common/parseractions.py @@ -155,9 +155,8 @@ class NonNegativeAction(argparse.Action): """ def __call__(self, parser, namespace, values, option_string=None): - try: - assert(int(values) >= 0) + if int(values) >= 0: setattr(namespace, self.dest, values) - except Exception: + else: msg = "%s expected a non-negative integer" % (str(option_string)) raise argparse.ArgumentTypeError(msg) diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index b3d4c3b6..b497a44d 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -145,7 +145,8 @@ class ShowQuota(command.ShowOne): parser.add_argument( 'project', metavar='<project/class>', - help='Show this project or class (name/ID)', + nargs='?', + help='Show quotas for this project or class (name or ID)', ) type_group = parser.add_mutually_exclusive_group() type_group.add_argument( @@ -164,12 +165,22 @@ class ShowQuota(command.ShowOne): ) return parser + def _get_project(self, parsed_args): + if parsed_args.project is not None: + identity_client = self.app.client_manager.identity + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ).id + elif self.app.client_manager.auth_ref: + # Get the project from the current auth + project = self.app.client_manager.auth_ref.project_id + else: + project = None + return project + def get_compute_volume_quota(self, client, parsed_args): - identity_client = self.app.client_manager.identity - project = utils.find_resource( - identity_client.projects, - parsed_args.project, - ).id + project = self._get_project(parsed_args) try: if parsed_args.quota_class: @@ -189,11 +200,7 @@ class ShowQuota(command.ShowOne): if parsed_args.quota_class or parsed_args.default: return {} if self.app.client_manager.is_network_endpoint_enabled(): - identity_client = self.app.client_manager.identity - project = utils.find_resource( - identity_client.projects, - parsed_args.project, - ).id + project = self._get_project(parsed_args) return self.app.client_manager.network.get_quota(project) else: return {} diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index 29e0e9d4..4b918369 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -18,10 +18,29 @@ import six from openstackclient.common import command +from openstackclient.common import exceptions from openstackclient.common import parseractions from openstackclient.common import utils +def _find_flavor(compute_client, flavor): + try: + return compute_client.flavors.get(flavor) + except Exception as ex: + if type(ex).__name__ == 'NotFound': + pass + else: + raise + try: + return compute_client.flavors.find(name=flavor, is_public=None) + except Exception as ex: + if type(ex).__name__ == 'NotFound': + msg = "No flavor with a name or ID of '%s' exists." % flavor + raise exceptions.CommandError(msg) + else: + raise + + class CreateFlavor(command.ShowOne): """Create new flavor""" @@ -132,8 +151,7 @@ class DeleteFlavor(command.Command): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - flavor = utils.find_resource(compute_client.flavors, - parsed_args.flavor) + flavor = _find_flavor(compute_client, parsed_args.flavor) compute_client.flavors.delete(flavor.id) @@ -239,7 +257,7 @@ class SetFlavor(command.Command): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - flavor = compute_client.flavors.find(name=parsed_args.flavor) + flavor = _find_flavor(compute_client, parsed_args.flavor) flavor.set_keys(parsed_args.property) @@ -257,8 +275,7 @@ class ShowFlavor(command.ShowOne): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - resource_flavor = utils.find_resource(compute_client.flavors, - parsed_args.flavor) + resource_flavor = _find_flavor(compute_client, parsed_args.flavor) flavor = resource_flavor._info.copy() flavor.pop("links", None) @@ -289,5 +306,5 @@ class UnsetFlavor(command.Command): def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - flavor = compute_client.flavors.find(name=parsed_args.flavor) + flavor = _find_flavor(compute_client, parsed_args.flavor) flavor.unset_keys(parsed_args.property) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index bf9f0985..8c533158 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -155,6 +155,11 @@ def _prep_server_detail(compute_client, server): if 'tenant_id' in info: info['project_id'] = info.pop('tenant_id') + # Map power state num to meanful string + if 'OS-EXT-STS:power_state' in info: + info['OS-EXT-STS:power_state'] = _format_servers_list_power_state( + info['OS-EXT-STS:power_state']) + # Remove values that are long and not too useful info.pop('links', None) diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py new file mode 100644 index 00000000..eb5745f5 --- /dev/null +++ b/openstackclient/compute/v2/server_group.py @@ -0,0 +1,182 @@ +# Copyright 2016 Huawei, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Compute v2 Server Group action implementations""" + +from openstackclient.common import command +from openstackclient.common import exceptions +from openstackclient.common import utils + + +_formatters = { + 'policies': utils.format_list, + 'members': utils.format_list, +} + + +def _get_columns(info): + columns = list(info.keys()) + if 'metadata' in columns: + # NOTE(RuiChen): The metadata of server group is always empty since API + # compatible, so hide it in order to avoid confusion. + columns.remove('metadata') + return tuple(sorted(columns)) + + +class CreateServerGroup(command.ShowOne): + """Create a new server group.""" + + def get_parser(self, prog_name): + parser = super(CreateServerGroup, self).get_parser(prog_name) + parser.add_argument( + 'name', + metavar='<name>', + help='New server group name', + ) + parser.add_argument( + '--policy', + metavar='<policy>', + action='append', + required=True, + help='Add a policy to <name> ' + '(repeat option to add multiple policies)', + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + info = {} + server_group = compute_client.server_groups.create( + name=parsed_args.name, + policies=parsed_args.policy) + info.update(server_group._info) + + columns = _get_columns(info) + data = utils.get_dict_properties(info, columns, + formatters=_formatters) + return columns, data + + +class DeleteServerGroup(command.Command): + """Delete an existing server group.""" + + def get_parser(self, prog_name): + parser = super(DeleteServerGroup, self).get_parser(prog_name) + parser.add_argument( + 'server_group', + metavar='<server-group>', + nargs='+', + help='server group(s) to delete (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + result = 0 + for group in parsed_args.server_group: + try: + group_obj = utils.find_resource(compute_client.server_groups, + group) + compute_client.server_groups.delete(group_obj.id) + # Catch all exceptions in order to avoid to block the next deleting + except Exception as e: + result += 1 + self.app.log.error(e) + + if result > 0: + total = len(parsed_args.server_group) + msg = "%s of %s server groups failed to delete." % (result, total) + raise exceptions.CommandError(msg) + + +class ListServerGroup(command.Lister): + """List all server groups.""" + + def get_parser(self, prog_name): + parser = super(ListServerGroup, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action='store_true', + default=False, + help='Display information from all projects (admin only)', + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + data = compute_client.server_groups.list(parsed_args.all_projects) + + if parsed_args.long: + column_headers = ( + 'ID', + 'Name', + 'Policies', + 'Members', + 'Project Id', + 'User Id', + ) + columns = ( + 'ID', + 'Name', + 'Policies', + 'Members', + 'Project Id', + 'User Id', + ) + else: + column_headers = columns = ( + 'ID', + 'Name', + 'Policies', + ) + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={ + 'Policies': utils.format_list, + 'Members': utils.format_list, + } + ) for s in data)) + + +class ShowServerGroup(command.ShowOne): + """Display server group details.""" + + def get_parser(self, prog_name): + parser = super(ShowServerGroup, self).get_parser(prog_name) + parser.add_argument( + 'server_group', + metavar='<server-group>', + help='server group to display (name or ID)', + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + group = utils.find_resource(compute_client.server_groups, + parsed_args.server_group) + info = {} + info.update(group._info) + columns = _get_columns(info) + data = utils.get_dict_properties(info, columns, + formatters=_formatters) + return columns, data diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index 355583cc..f43ada56 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -82,7 +82,7 @@ class DeleteService(command.Command): parser.add_argument( 'service', metavar='<service>', - help='Service to delete (type or ID)', + help='Service to delete (type, name or ID)', ) return parser diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index fbfce4d3..9b6161fd 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -249,7 +249,8 @@ class CreatePort(command.ShowOne): parser.add_argument( 'name', metavar='<name>', - help='Name of this port') + help=_("Name of this port") + ) # TODO(singhj): Add support for extended options: # qos,security groups,dhcp, address pairs return parser diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 56630a23..a32ab5ea 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -34,11 +34,20 @@ def _format_external_gateway_info(info): return '' +def _format_routes(routes): + # Map the route keys to match --route option. + for route in routes: + if 'nexthop' in route: + route['gateway'] = route.pop('nexthop') + return utils.format_list_of_dicts(routes) + + _formatters = { 'admin_state_up': _format_admin_state, 'external_gateway_info': _format_external_gateway_info, 'availability_zones': utils.format_list, 'availability_zone_hints': utils.format_list, + 'routes': _format_routes, } @@ -67,11 +76,6 @@ def _get_attrs(client_manager, parsed_args): and parsed_args.availability_zone_hints is not None): attrs['availability_zone_hints'] = parsed_args.availability_zone_hints - if 'clear_routes' in parsed_args and parsed_args.clear_routes: - attrs['routes'] = [] - elif 'routes' in parsed_args and parsed_args.routes is not None: - attrs['routes'] = parsed_args.routes - # "router set" command doesn't support setting project. if 'project' in parsed_args and parsed_args.project is not None: identity_client = client_manager.identity @@ -393,7 +397,19 @@ class SetRouter(command.Command): client = self.app.client_manager.network obj = client.find_router(parsed_args.router, ignore_missing=False) + # Get the common attributes. attrs = _get_attrs(self.app.client_manager, parsed_args) + + # Get the route attributes. + if parsed_args.clear_routes: + attrs['routes'] = [] + elif parsed_args.routes is not None: + # Map the route keys and append to the current routes. + # The REST API will handle route validation and duplicates. + for route in parsed_args.routes: + route['nexthop'] = route.pop('gateway') + attrs['routes'] = obj.routes + parsed_args.routes + if attrs == {}: msg = "Nothing specified to be set" raise exceptions.CommandError(msg) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index 715e6620..fb441cbf 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -14,8 +14,6 @@ """Subnet action implementations""" import copy -from json.encoder import JSONEncoder - from openstackclient.common import command from openstackclient.common import exceptions from openstackclient.common import parseractions @@ -31,10 +29,8 @@ def _format_allocation_pools(data): def _format_host_routes(data): - try: - return '\n'.join([JSONEncoder().encode(route) for route in data]) - except (TypeError, KeyError): - return '' + # Map the host route keys to match --host-route option. + return utils.format_list_of_dicts(convert_entries_to_gateway(data)) _formatters = { @@ -89,8 +85,9 @@ def convert_entries_to_nexthop(entries): # Change 'gateway' entry to 'nexthop' changed_entries = copy.deepcopy(entries) for entry in changed_entries: - entry['nexthop'] = entry['gateway'] - del entry['gateway'] + if 'gateway' in entry: + entry['nexthop'] = entry['gateway'] + del entry['gateway'] return changed_entries @@ -99,8 +96,9 @@ def convert_entries_to_gateway(entries): # Change 'nexthop' entry to 'gateway' changed_entries = copy.deepcopy(entries) for entry in changed_entries: - entry['gateway'] = entry['nexthop'] - del entry['nexthop'] + if 'nexthop' in entry: + entry['gateway'] = entry['nexthop'] + del entry['nexthop'] return changed_entries diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 482b5ecf..f1174dda 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -55,6 +55,16 @@ def _get_attrs(client_manager, parsed_args): if 'no_address_scope' in parsed_args and parsed_args.no_address_scope: attrs['address_scope_id'] = None + if parsed_args.default: + attrs['is_default'] = True + if parsed_args.no_default: + attrs['is_default'] = False + + if 'share' in parsed_args and parsed_args.share: + attrs['shared'] = True + if 'no_share' in parsed_args and parsed_args.no_share: + attrs['shared'] = False + # "subnet pool set" command doesn't support setting project. if 'project' in parsed_args and parsed_args.project is not None: identity_client = client_manager.identity @@ -68,18 +78,20 @@ def _get_attrs(client_manager, parsed_args): return attrs -def _add_prefix_options(parser): +def _add_prefix_options(parser, for_create=False): parser.add_argument( '--pool-prefix', metavar='<pool-prefix>', dest='prefixes', action='append', + required=for_create, help=_("Set subnet pool prefixes (in CIDR notation) " "(repeat option to set multiple prefixes)") ) parser.add_argument( '--default-prefix-length', metavar='<default-prefix-length>', + type=int, action=parseractions.NonNegativeAction, help=_("Set subnet pool default prefix length") ) @@ -87,16 +99,32 @@ def _add_prefix_options(parser): '--min-prefix-length', metavar='<min-prefix-length>', action=parseractions.NonNegativeAction, + type=int, help=_("Set subnet pool minimum prefix length") ) parser.add_argument( '--max-prefix-length', metavar='<max-prefix-length>', + type=int, action=parseractions.NonNegativeAction, help=_("Set subnet pool maximum prefix length") ) +def _add_default_options(parser): + default_group = parser.add_mutually_exclusive_group() + default_group.add_argument( + '--default', + action='store_true', + help=_("Set this as a default subnet pool"), + ) + default_group.add_argument( + '--no-default', + action='store_true', + help=_("Set this as a non-default subnet pool"), + ) + + class CreateSubnetPool(command.ShowOne): """Create subnet pool""" @@ -107,7 +135,7 @@ class CreateSubnetPool(command.ShowOne): metavar='<name>', help=_("Name of the new subnet pool") ) - _add_prefix_options(parser) + _add_prefix_options(parser, for_create=True) parser.add_argument( '--project', metavar='<project>', @@ -121,6 +149,18 @@ class CreateSubnetPool(command.ShowOne): "(name or ID), prefixes must be unique across address " "scopes") ) + _add_default_options(parser) + shared_group = parser.add_mutually_exclusive_group() + shared_group.add_argument( + '--share', + action='store_true', + help=_("Set this subnet pool as shared"), + ) + shared_group.add_argument( + '--no-share', + action='store_true', + help=_("Set this subnet pool as not shared"), + ) return parser def take_action(self, parsed_args): @@ -176,6 +216,8 @@ class ListSubnetPool(command.Lister): 'Prefixes', 'Default Prefix Length', 'Address Scope', + 'Default Subnet Pool', + 'Shared', ) columns = ( 'id', @@ -183,6 +225,8 @@ class ListSubnetPool(command.Lister): 'prefixes', 'default_prefixlen', 'address_scope_id', + 'is_default', + 'shared', ) else: headers = ( @@ -232,6 +276,8 @@ class SetSubnetPool(command.Command): action='store_true', help=_("Remove address scope associated with the subnet pool") ) + _add_default_options(parser) + return parser def take_action(self, parsed_args): diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py index edf29c9b..ba7ee469 100644 --- a/openstackclient/tests/common/test_quota.py +++ b/openstackclient/tests/common/test_quota.py @@ -59,6 +59,7 @@ class TestQuota(compute_fakes.TestComputev2): self.service_catalog_mock = \ self.app.client_manager.auth_ref.service_catalog self.service_catalog_mock.reset_mock() + self.app.client_manager.auth_ref.project_id = identity_fakes.project_id class TestQuotaSet(TestQuota): @@ -304,3 +305,13 @@ class TestQuotaShow(TestQuota): identity_fakes.project_id) self.volume_quotas_class_mock.get.assert_called_with( identity_fakes.project_id) + + 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( + identity_fakes.project_id) + self.network.get_quota.assert_called_with(identity_fakes.project_id) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index 7f39bad0..b3f3fb49 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -100,8 +100,7 @@ class FakeAggregate(object): :return: A FakeResource object, with id and other attributes """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attribute aggregate_info = { @@ -177,6 +176,9 @@ class FakeComputev2Client(object): self.hosts = mock.Mock() self.hosts.resource_class = fakes.FakeResource(None, {}) + self.server_groups = mock.Mock() + self.server_groups.resource_class = fakes.FakeResource(None, {}) + self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] @@ -217,7 +219,7 @@ class FakeHypervisor(object): """Fake one or more hypervisor.""" @staticmethod - def create_one_hypervisor(attrs={}): + def create_one_hypervisor(attrs=None): """Create a fake hypervisor. :param Dictionary attrs: @@ -225,6 +227,8 @@ class FakeHypervisor(object): :return: A FakeResource object, with id, hypervisor_hostname, and so on """ + attrs = attrs or {} + # Set default attributes. hypervisor_info = { 'id': 'hypervisor-id-' + uuid.uuid4().hex, @@ -263,7 +267,7 @@ class FakeHypervisor(object): return hypervisor @staticmethod - def create_hypervisors(attrs={}, count=2): + def create_hypervisors(attrs=None, count=2): """Create multiple fake hypervisors. :param Dictionary attrs: @@ -284,7 +288,7 @@ class FakeHypervisorStats(object): """Fake one or more hypervisor stats.""" @staticmethod - def create_one_hypervisor_stats(attrs={}): + def create_one_hypervisor_stats(attrs=None): """Create a fake hypervisor stats. :param Dictionary attrs: @@ -292,6 +296,8 @@ class FakeHypervisorStats(object): :return: A FakeResource object, with id, hypervisor_hostname, and so on """ + attrs = attrs or {} + # Set default attributes. stats_info = { 'count': 2, @@ -319,7 +325,7 @@ class FakeHypervisorStats(object): return hypervisor_stats @staticmethod - def create_hypervisors_stats(attrs={}, count=2): + def create_hypervisors_stats(attrs=None, count=2): """Create multiple fake hypervisors stats. :param Dictionary attrs: @@ -349,8 +355,7 @@ class FakeSecurityGroup(object): :return: A FakeResource object, with id, name, etc. """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. security_group_attrs = { @@ -400,8 +405,7 @@ class FakeSecurityGroupRule(object): :return: A FakeResource object, with id, etc. """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. security_group_rule_attrs = { @@ -445,7 +449,7 @@ class FakeServer(object): """Fake one or more compute servers.""" @staticmethod - def create_one_server(attrs={}, methods={}): + def create_one_server(attrs=None, methods=None): """Create a fake server. :param Dictionary attrs: @@ -455,6 +459,9 @@ class FakeServer(object): :return: A FakeResource object, with id, name, metadata """ + attrs = attrs or {} + methods = methods or {} + # Set default attributes. server_info = { 'id': 'server-id-' + uuid.uuid4().hex, @@ -465,7 +472,8 @@ class FakeServer(object): }, 'flavor': { 'id': 'flavor-id-' + uuid.uuid4().hex, - } + }, + 'OS-EXT-STS:power_state': 1, } # Overwrite default attributes. @@ -477,7 +485,7 @@ class FakeServer(object): return server @staticmethod - def create_servers(attrs={}, methods={}, count=2): + def create_servers(attrs=None, methods=None, count=2): """Create multiple fake servers. :param Dictionary attrs: @@ -527,8 +535,7 @@ class FakeFlavor(object): :return: A FakeResource object, with id, name, ram, vcpus, properties """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. flavor_info = { @@ -537,8 +544,8 @@ class FakeFlavor(object): 'ram': 8192, 'vcpus': 4, 'disk': 128, - 'swap': '', - 'rxtx_factor': '1.0', + 'swap': 0, + 'rxtx_factor': 1.0, 'OS-FLV-DISABLED:disabled': False, 'os-flavor-access:is_public': True, 'OS-FLV-EXT-DATA:ephemeral': 0, @@ -566,7 +573,7 @@ class FakeFlavor(object): return flavor @staticmethod - def create_flavors(attrs={}, count=2): + def create_flavors(attrs=None, count=2): """Create multiple fake flavors. :param Dictionary attrs: @@ -614,10 +621,9 @@ class FakeKeypair(object): :return: A FakeResource """ - # Set default attributes. - if attrs is None: - attrs = {} + attrs = attrs or {} + # Set default attributes. keypair_info = { 'name': 'keypair-name-' + uuid.uuid4().hex, 'fingerprint': 'dummy', @@ -658,7 +664,7 @@ class FakeAvailabilityZone(object): """Fake one or more compute availability zones (AZs).""" @staticmethod - def create_one_availability_zone(attrs={}): + def create_one_availability_zone(attrs=None): """Create a fake AZ. :param Dictionary attrs: @@ -666,6 +672,8 @@ class FakeAvailabilityZone(object): :return: A FakeResource object with zoneName, zoneState, etc. """ + attrs = attrs or {} + # Set default attributes. host_name = uuid.uuid4().hex service_name = uuid.uuid4().hex @@ -689,7 +697,7 @@ class FakeAvailabilityZone(object): return availability_zone @staticmethod - def create_availability_zones(attrs={}, count=2): + def create_availability_zones(attrs=None, count=2): """Create multiple fake AZs. :param Dictionary attrs: @@ -712,7 +720,7 @@ class FakeFloatingIP(object): """Fake one or more floating ip.""" @staticmethod - def create_one_floating_ip(attrs={}): + def create_one_floating_ip(attrs=None): """Create a fake floating ip. :param Dictionary attrs: @@ -720,6 +728,8 @@ class FakeFloatingIP(object): :return: A FakeResource object, with id, ip, and so on """ + attrs = attrs or {} + # Set default attributes. floating_ip_attrs = { 'id': 'floating-ip-id-' + uuid.uuid4().hex, @@ -739,7 +749,7 @@ class FakeFloatingIP(object): return floating_ip @staticmethod - def create_floating_ips(attrs={}, count=2): + def create_floating_ips(attrs=None, count=2): """Create multiple fake floating ips. :param Dictionary attrs: @@ -778,7 +788,7 @@ class FakeNetwork(object): """Fake one or more networks.""" @staticmethod - def create_one_network(attrs={}): + def create_one_network(attrs=None): """Create a fake network. :param Dictionary attrs: @@ -786,6 +796,8 @@ class FakeNetwork(object): :return: A FakeResource object, with id, label, cidr and so on """ + attrs = attrs or {} + # Set default attributes. network_attrs = { 'bridge': 'br100', @@ -831,7 +843,7 @@ class FakeNetwork(object): return network @staticmethod - def create_networks(attrs={}, count=2): + def create_networks(attrs=None, count=2): """Create multiple fake networks. :param Dictionary attrs: @@ -860,8 +872,7 @@ class FakeHost(object): :return: A FakeResource object, with id and other attributes """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. host_info = { @@ -899,3 +910,34 @@ class FakeHost(object): info=copy.deepcopy(host_info), loaded=True) return host + + +class FakeServerGroup(object): + """Fake one server group""" + + @staticmethod + def create_one_server_group(attrs=None): + """Create a fake server group + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object, with id and other attributes + """ + if attrs is None: + attrs = {} + + server_group_info = { + 'id': 'server-group-id-' + uuid.uuid4().hex, + 'members': [], + 'metadata': {}, + 'name': 'server-group-name-' + uuid.uuid4().hex, + 'policies': [], + 'project_id': 'server-group-project-id-' + uuid.uuid4().hex, + 'user_id': 'server-group-user-id-' + uuid.uuid4().hex, + } + server_group_info.update(attrs) + server_group = fakes.FakeResource( + info=copy.deepcopy(server_group_info), + loaded=True) + return server_group diff --git a/openstackclient/tests/compute/v2/test_flavor.py b/openstackclient/tests/compute/v2/test_flavor.py index 03ca8807..6f507b16 100644 --- a/openstackclient/tests/compute/v2/test_flavor.py +++ b/openstackclient/tests/compute/v2/test_flavor.py @@ -30,6 +30,166 @@ class TestFlavor(compute_fakes.TestComputev2): self.flavors_mock.reset_mock() +class TestFlavorCreate(TestFlavor): + + flavor = compute_fakes.FakeFlavor.create_one_flavor( + attrs={'links': 'flavor-links'}) + + columns = ( + 'OS-FLV-DISABLED:disabled', + 'OS-FLV-EXT-DATA:ephemeral', + 'disk', + 'id', + 'name', + 'os-flavor-access:is_public', + 'ram', + 'rxtx_factor', + 'swap', + 'vcpus', + ) + data = ( + flavor.disabled, + flavor.ephemeral, + flavor.disk, + flavor.id, + flavor.name, + flavor.is_public, + flavor.ram, + flavor.rxtx_factor, + flavor.swap, + flavor.vcpus, + ) + + def setUp(self): + super(TestFlavorCreate, self).setUp() + + self.flavors_mock.create.return_value = self.flavor + self.cmd = flavor.CreateFlavor(self.app, None) + + def test_flavor_create_default_options(self): + + arglist = [ + self.flavor.name + ] + verifylist = [ + ('name', self.flavor.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + default_args = ( + self.flavor.name, + 256, + 1, + 0, + 'auto', + 0, + 0, + 1.0, + True + ) + columns, data = self.cmd.take_action(parsed_args) + self.flavors_mock.create.assert_called_once_with(*default_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_flavor_create_all_options(self): + + arglist = [ + self.flavor.name, + '--id', self.flavor.id, + '--ram', str(self.flavor.ram), + '--disk', str(self.flavor.disk), + '--ephemeral', str(self.flavor.ephemeral), + '--swap', str(self.flavor.swap), + '--vcpus', str(self.flavor.vcpus), + '--rxtx-factor', str(self.flavor.rxtx_factor), + '--public', + ] + verifylist = [ + ('name', self.flavor.name), + ('id', self.flavor.id), + ('ram', self.flavor.ram), + ('disk', self.flavor.disk), + ('ephemeral', self.flavor.ephemeral), + ('swap', self.flavor.swap), + ('vcpus', self.flavor.vcpus), + ('rxtx_factor', self.flavor.rxtx_factor), + ('public', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + args = ( + self.flavor.name, + self.flavor.ram, + self.flavor.vcpus, + self.flavor.disk, + self.flavor.id, + self.flavor.ephemeral, + self.flavor.swap, + self.flavor.rxtx_factor, + self.flavor.is_public, + ) + columns, data = self.cmd.take_action(parsed_args) + self.flavors_mock.create.assert_called_once_with(*args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_flavor_create_other_options(self): + + self.flavor.is_public = False + arglist = [ + self.flavor.name, + '--id', self.flavor.id, + '--ram', str(self.flavor.ram), + '--disk', str(self.flavor.disk), + '--ephemeral', str(self.flavor.ephemeral), + '--swap', str(self.flavor.swap), + '--vcpus', str(self.flavor.vcpus), + '--rxtx-factor', str(self.flavor.rxtx_factor), + '--private', + ] + verifylist = [ + ('name', self.flavor.name), + ('id', self.flavor.id), + ('ram', self.flavor.ram), + ('disk', self.flavor.disk), + ('ephemeral', self.flavor.ephemeral), + ('swap', self.flavor.swap), + ('vcpus', self.flavor.vcpus), + ('rxtx_factor', self.flavor.rxtx_factor), + ('public', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + args = ( + self.flavor.name, + self.flavor.ram, + self.flavor.vcpus, + self.flavor.disk, + self.flavor.id, + self.flavor.ephemeral, + self.flavor.swap, + self.flavor.rxtx_factor, + self.flavor.is_public, + ) + columns, data = self.cmd.take_action(parsed_args) + self.flavors_mock.create.assert_called_once_with(*args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_flavor_create_no_options(self): + arglist = [] + verifylist = None + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) + + class TestFlavorDelete(TestFlavor): flavor = compute_fakes.FakeFlavor.create_one_flavor() @@ -273,7 +433,7 @@ class TestFlavorSet(TestFlavor): super(TestFlavorSet, self).setUp() self.flavors_mock.find.return_value = self.flavor - + self.flavors_mock.get.side_effect = exceptions.NotFound(None) self.cmd = flavor.SetFlavor(self.app, None) def test_flavor_set(self): @@ -288,8 +448,8 @@ class TestFlavorSet(TestFlavor): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - - self.flavors_mock.find.assert_called_with(name='baremetal') + self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, + is_public=None) self.assertIsNone(result) @@ -329,9 +489,9 @@ class TestFlavorShow(TestFlavor): def setUp(self): super(TestFlavorShow, self).setUp() - # Return value of utils.find_resource() - self.flavors_mock.get.return_value = self.flavor - + # Return value of _find_resource() + self.flavors_mock.find.return_value = self.flavor + self.flavors_mock.get.side_effect = exceptions.NotFound(None) self.cmd = flavor.ShowFlavor(self.app, None) def test_show_no_options(self): @@ -367,7 +527,7 @@ class TestFlavorUnset(TestFlavor): super(TestFlavorUnset, self).setUp() self.flavors_mock.find.return_value = self.flavor - + self.flavors_mock.get.side_effect = exceptions.NotFound(None) self.cmd = flavor.UnsetFlavor(self.app, None) def test_flavor_unset(self): @@ -382,6 +542,6 @@ class TestFlavorUnset(TestFlavor): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - - self.flavors_mock.find.assert_called_with(name='baremetal') + self.flavors_mock.find.assert_called_with(name=parsed_args.flavor, + is_public=None) self.assertIsNone(result) diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 17681672..903d0bff 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -89,6 +89,7 @@ class TestServer(compute_fakes.TestComputev2): class TestServerCreate(TestServer): columns = ( + 'OS-EXT-STS:power_state', 'addresses', 'flavor', 'id', @@ -100,6 +101,8 @@ class TestServerCreate(TestServer): def datalist(self): datalist = ( + server._format_servers_list_power_state( + getattr(self.new_server, 'OS-EXT-STS:power_state')), '', self.flavor.name + ' (' + self.new_server.flavor.get('id') + ')', self.new_server.id, @@ -146,7 +149,6 @@ class TestServerCreate(TestServer): ('server_name', self.new_server.name), ] - # Missing required args should bail here self.assertRaises(utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -1476,6 +1478,8 @@ class TestServerGeneral(TestServer): 'image': u'%s (%s)' % (_image.name, _image.id), 'project_id': u'tenant-id-xxx', 'properties': '', + 'OS-EXT-STS:power_state': server._format_servers_list_power_state( + getattr(_server, 'OS-EXT-STS:power_state')), } # Call _prep_server_detail(). diff --git a/openstackclient/tests/compute/v2/test_server_group.py b/openstackclient/tests/compute/v2/test_server_group.py new file mode 100644 index 00000000..70ff23f9 --- /dev/null +++ b/openstackclient/tests/compute/v2/test_server_group.py @@ -0,0 +1,283 @@ +# Copyright 2016 Huawei, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import mock + +from openstackclient.common import exceptions +from openstackclient.common import utils +from openstackclient.compute.v2 import server_group +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import utils as tests_utils + + +class TestServerGroup(compute_fakes.TestComputev2): + + fake_server_group = compute_fakes.FakeServerGroup.create_one_server_group() + + columns = ( + 'id', + 'members', + 'name', + 'policies', + 'project_id', + 'user_id', + ) + + data = ( + fake_server_group.id, + utils.format_list(fake_server_group.members), + fake_server_group.name, + utils.format_list(fake_server_group.policies), + fake_server_group.project_id, + fake_server_group.user_id, + ) + + def setUp(self): + super(TestServerGroup, self).setUp() + + # Get a shortcut to the ServerGroupsManager Mock + self.server_groups_mock = self.app.client_manager.compute.server_groups + self.server_groups_mock.reset_mock() + + +class TestServerGroupCreate(TestServerGroup): + + def setUp(self): + super(TestServerGroupCreate, self).setUp() + + self.server_groups_mock.create.return_value = self.fake_server_group + self.cmd = server_group.CreateServerGroup(self.app, None) + + def test_server_group_create(self): + arglist = [ + '--policy', 'affinity', + 'affinity_group', + ] + verifylist = [ + ('policy', ['affinity']), + ('name', 'affinity_group'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.create.assert_called_once_with( + name=parsed_args.name, + policies=parsed_args.policy, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_server_group_create_with_multiple_policies(self): + arglist = [ + '--policy', 'affinity', + '--policy', 'soft-affinity', + 'affinity_group', + ] + verifylist = [ + ('policy', ['affinity', 'soft-affinity']), + ('name', 'affinity_group'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.create.assert_called_once_with( + name=parsed_args.name, + policies=parsed_args.policy, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_server_group_create_no_policy(self): + arglist = [ + 'affinity_group', + ] + verifylist = None + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) + + +class TestServerGroupDelete(TestServerGroup): + + def setUp(self): + super(TestServerGroupDelete, self).setUp() + + self.server_groups_mock.get.return_value = self.fake_server_group + self.cmd = server_group.DeleteServerGroup(self.app, None) + + def test_server_group_delete(self): + arglist = [ + 'affinity_group', + ] + verifylist = [ + ('server_group', ['affinity_group']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.server_groups_mock.get.assert_called_once_with('affinity_group') + self.server_groups_mock.delete.assert_called_once_with( + self.fake_server_group.id + ) + self.assertIsNone(result) + + def test_server_group_multiple_delete(self): + arglist = [ + 'affinity_group', + 'anti_affinity_group' + ] + verifylist = [ + ('server_group', ['affinity_group', 'anti_affinity_group']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.server_groups_mock.get.assert_any_call('affinity_group') + self.server_groups_mock.get.assert_any_call('anti_affinity_group') + self.server_groups_mock.delete.assert_called_with( + self.fake_server_group.id + ) + self.assertEqual(2, self.server_groups_mock.get.call_count) + self.assertEqual(2, self.server_groups_mock.delete.call_count) + self.assertIsNone(result) + + def test_server_group_delete_no_input(self): + arglist = [] + verifylist = None + self.assertRaises(tests_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist) + + def test_server_group_multiple_delete_with_exception(self): + arglist = [ + 'affinity_group', + 'anti_affinity_group' + ] + verifylist = [ + ('server_group', ['affinity_group', 'anti_affinity_group']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + find_mock_result = [self.fake_server_group, 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 server groups failed to delete.', + str(e)) + + find_mock.assert_any_call(self.server_groups_mock, + 'affinity_group') + find_mock.assert_any_call(self.server_groups_mock, + 'anti_affinity_group') + + self.assertEqual(2, find_mock.call_count) + self.server_groups_mock.delete.assert_called_once_with( + self.fake_server_group.id + ) + + +class TestServerGroupList(TestServerGroup): + + list_columns = ( + 'ID', + 'Name', + 'Policies', + ) + + list_columns_long = ( + 'ID', + 'Name', + 'Policies', + 'Members', + 'Project Id', + 'User Id', + ) + + list_data = (( + TestServerGroup.fake_server_group.id, + TestServerGroup.fake_server_group.name, + utils.format_list(TestServerGroup.fake_server_group.policies), + ),) + + list_data_long = (( + TestServerGroup.fake_server_group.id, + TestServerGroup.fake_server_group.name, + utils.format_list(TestServerGroup.fake_server_group.policies), + utils.format_list(TestServerGroup.fake_server_group.members), + TestServerGroup.fake_server_group.project_id, + TestServerGroup.fake_server_group.user_id, + ),) + + def setUp(self): + super(TestServerGroupList, self).setUp() + + self.server_groups_mock.list.return_value = [self.fake_server_group] + self.cmd = server_group.ListServerGroup(self.app, None) + + def test_server_group_list(self): + arglist = [] + verifylist = [ + ('all_projects', False), + ('long', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.list.assert_called_once_with(False) + + self.assertEqual(self.list_columns, columns) + self.assertEqual(self.list_data, tuple(data)) + + def test_server_group_list_with_all_projects_and_long(self): + arglist = [ + '--all-projects', + '--long', + ] + verifylist = [ + ('all_projects', True), + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.server_groups_mock.list.assert_called_once_with(True) + + self.assertEqual(self.list_columns_long, columns) + self.assertEqual(self.list_data_long, tuple(data)) + + +class TestServerGroupShow(TestServerGroup): + + def setUp(self): + super(TestServerGroupShow, self).setUp() + + self.server_groups_mock.get.return_value = self.fake_server_group + self.cmd = server_group.ShowServerGroup(self.app, None) + + def test_server_group_show(self): + arglist = [ + 'affinity_group', + ] + verifylist = [ + ('server_group', 'affinity_group'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index f0cebb06..46f983dc 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -142,7 +142,7 @@ class FakeModule(object): class FakeResource(object): - def __init__(self, manager=None, info={}, loaded=False, methods={}): + def __init__(self, manager=None, info=None, loaded=False, methods=None): """Set attributes and methods for a resource. :param manager: @@ -154,6 +154,9 @@ class FakeResource(object): :param Dictionary methods: A dictionary with all methods """ + info = info or {} + methods = methods or {} + self.__name__ = type(self).__name__ self.manager = manager self._info = info @@ -189,9 +192,12 @@ class FakeResource(object): class FakeResponse(requests.Response): - def __init__(self, headers={}, status_code=200, data=None, encoding=None): + def __init__(self, headers=None, status_code=200, + data=None, encoding=None): super(FakeResponse, self).__init__() + headers = headers or {} + self.status_code = status_code self.headers.update(headers) diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index 3555d2d4..f90d846d 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -181,7 +181,7 @@ class FakeImage(object): """ @staticmethod - def create_one_image(attrs={}): + def create_one_image(attrs=None): """Create a fake image. :param Dictionary attrs: @@ -190,6 +190,8 @@ class FakeImage(object): A FakeResource object with id, name, owner, protected, visibility and tags attrs """ + attrs = attrs or {} + # Set default attribute image_info = { 'id': 'image-id' + uuid.uuid4().hex, @@ -210,7 +212,7 @@ class FakeImage(object): return image @staticmethod - def create_images(attrs={}, count=2): + def create_images(attrs=None, count=2): """Create multiple fake images. :param Dictionary attrs: diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 413ec7d2..7c4604bd 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -83,8 +83,7 @@ class FakeAddressScope(object): :return: A FakeResource object with name, id, etc. """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. address_scope_attrs = { @@ -130,7 +129,7 @@ class FakeAvailabilityZone(object): """Fake one or more network availability zones (AZs).""" @staticmethod - def create_one_availability_zone(attrs={}): + def create_one_availability_zone(attrs=None): """Create a fake AZ. :param Dictionary attrs: @@ -138,6 +137,8 @@ class FakeAvailabilityZone(object): :return: A FakeResource object with name, state, etc. """ + attrs = attrs or {} + # Set default attributes. availability_zone = { 'name': uuid.uuid4().hex, @@ -154,7 +155,7 @@ class FakeAvailabilityZone(object): return availability_zone @staticmethod - def create_availability_zones(attrs={}, count=2): + def create_availability_zones(attrs=None, count=2): """Create multiple fake AZs. :param Dictionary attrs: @@ -177,7 +178,7 @@ class FakeNetwork(object): """Fake one or more networks.""" @staticmethod - def create_one_network(attrs={}): + def create_one_network(attrs=None): """Create a fake network. :param Dictionary attrs: @@ -186,6 +187,8 @@ class FakeNetwork(object): A FakeResource object, with id, name, admin_state_up, router_external, status, subnets, tenant_id """ + attrs = attrs or {} + # Set default attributes. network_attrs = { 'id': 'network-id-' + uuid.uuid4().hex, @@ -214,7 +217,7 @@ class FakeNetwork(object): return network @staticmethod - def create_networks(attrs={}, count=2): + def create_networks(attrs=None, count=2): """Create multiple fake networks. :param Dictionary attrs: @@ -254,16 +257,16 @@ class FakePort(object): """Fake one or more ports.""" @staticmethod - def create_one_port(attrs={}): + def create_one_port(attrs=None): """Create a fake port. :param Dictionary attrs: A dictionary with all attributes - :param Dictionary methods: - A dictionary with all methods :return: A FakeResource object, with id, name, etc. """ + attrs = attrs or {} + # Set default attributes. port_attrs = { 'admin_state_up': True, @@ -306,7 +309,7 @@ class FakePort(object): return port @staticmethod - def create_ports(attrs={}, count=2): + def create_ports(attrs=None, count=2): """Create multiple fake ports. :param Dictionary attrs: @@ -346,7 +349,7 @@ class FakeRouter(object): """Fake one or more routers.""" @staticmethod - def create_one_router(attrs={}): + def create_one_router(attrs=None): """Create a fake router. :param Dictionary attrs: @@ -355,6 +358,8 @@ class FakeRouter(object): A FakeResource object, with id, name, admin_state_up, status, tenant_id """ + attrs = attrs or {} + # Set default attributes. router_attrs = { 'id': 'router-id-' + uuid.uuid4().hex, @@ -382,7 +387,7 @@ class FakeRouter(object): return router @staticmethod - def create_routers(attrs={}, count=2): + def create_routers(attrs=None, count=2): """Create multiple fake routers. :param Dictionary attrs: @@ -430,8 +435,7 @@ class FakeSecurityGroup(object): :return: A FakeResource object, with id, name, etc. """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. security_group_attrs = { @@ -485,8 +489,7 @@ class FakeSecurityGroupRule(object): :return: A FakeResource object, with id, etc. """ - if attrs is None: - attrs = {} + attrs = attrs or {} # Set default attributes. security_group_rule_attrs = { @@ -537,7 +540,7 @@ class FakeSubnet(object): """Fake one or more subnets.""" @staticmethod - def create_one_subnet(attrs={}): + def create_one_subnet(attrs=None): """Create a fake subnet. :param Dictionary attrs: @@ -545,6 +548,8 @@ class FakeSubnet(object): :return: A FakeResource object faking the subnet """ + attrs = attrs or {} + # Set default attributes. project_id = 'project-id-' + uuid.uuid4().hex subnet_attrs = { @@ -569,13 +574,14 @@ class FakeSubnet(object): subnet = fakes.FakeResource(info=copy.deepcopy(subnet_attrs), loaded=True) + # Set attributes with special mappings in OpenStack SDK. subnet.project_id = subnet_attrs['tenant_id'] return subnet @staticmethod - def create_subnets(attrs={}, count=2): + def create_subnets(attrs=None, count=2): """Create multiple fake subnets. :param Dictionary attrs: @@ -596,7 +602,7 @@ class FakeFloatingIP(object): """Fake one or more floating ip.""" @staticmethod - def create_one_floating_ip(attrs={}): + def create_one_floating_ip(attrs=None): """Create a fake floating ip. :param Dictionary attrs: @@ -604,6 +610,8 @@ class FakeFloatingIP(object): :return: A FakeResource object, with id, ip, and so on """ + attrs = attrs or {} + # Set default attributes. floating_ip_attrs = { 'id': 'floating-ip-id-' + uuid.uuid4().hex, @@ -632,7 +640,7 @@ class FakeFloatingIP(object): return floating_ip @staticmethod - def create_floating_ips(attrs={}, count=2): + def create_floating_ips(attrs=None, count=2): """Create multiple fake floating ips. :param Dictionary attrs: @@ -671,7 +679,7 @@ class FakeSubnetPool(object): """Fake one or more subnet pools.""" @staticmethod - def create_one_subnet_pool(attrs={}): + def create_one_subnet_pool(attrs=None): """Create a fake subnet pool. :param Dictionary attrs: @@ -679,6 +687,8 @@ class FakeSubnetPool(object): :return: A FakeResource object faking the subnet pool """ + attrs = attrs or {} + # Set default attributes. subnet_pool_attrs = { 'id': 'subnet-pool-id-' + uuid.uuid4().hex, @@ -709,7 +719,7 @@ class FakeSubnetPool(object): return subnet_pool @staticmethod - def create_subnet_pools(attrs={}, count=2): + def create_subnet_pools(attrs=None, count=2): """Create multiple fake subnet pools. :param Dictionary attrs: diff --git a/openstackclient/tests/network/v2/test_floating_ip.py b/openstackclient/tests/network/v2/test_floating_ip.py index 3e261fb5..f9ccfe1c 100644 --- a/openstackclient/tests/network/v2/test_floating_ip.py +++ b/openstackclient/tests/network/v2/test_floating_ip.py @@ -88,7 +88,6 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -314,7 +313,6 @@ class TestCreateFloatingIPCompute(TestFloatingIPCompute): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index a1b0aec9..1269b0a1 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -112,7 +112,6 @@ class TestCreateNetworkIdentityV3(TestNetwork): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -599,7 +598,6 @@ class TestShowNetwork(TestNetwork): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -617,8 +615,8 @@ class TestShowNetwork(TestNetwork): self.network.find_network.assert_called_once_with( self._network.name, ignore_missing=False) - self.assertEqual(tuple(self.columns), columns) - self.assertEqual(list(self.data), list(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) # Tests for Nova network @@ -916,7 +914,6 @@ class TestShowNetworkCompute(TestNetworkCompute): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -931,5 +928,5 @@ class TestShowNetworkCompute(TestNetworkCompute): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.assertEqual(self.columns, tuple(columns)) + self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/openstackclient/tests/network/v2/test_router.py b/openstackclient/tests/network/v2/test_router.py index e8041498..655e86c9 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -47,7 +47,6 @@ class TestAddPortToRouter(TestRouter): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -88,7 +87,6 @@ class TestAddSubnetToRouter(TestRouter): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -138,7 +136,7 @@ class TestCreateRouter(TestRouter): new_router.id, new_router.name, new_router.tenant_id, - new_router.routes, + router._format_routes(new_router.routes), new_router.status, ) @@ -154,7 +152,6 @@ class TestCreateRouter(TestRouter): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -268,7 +265,7 @@ class TestListRouter(TestRouter): r = routers[i] data_long.append( data[i] + ( - r.routes, + router._format_routes(r.routes), router._format_external_gateway_info(r.external_gateway_info), osc_utils.format_list(r.availability_zones), ) @@ -335,7 +332,6 @@ class TestRemovePortFromRouter(TestRouter): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -375,7 +371,6 @@ class TestRemoveSubnetFromRouter(TestRouter): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -399,7 +394,10 @@ class TestRemoveSubnetFromRouter(TestRouter): class TestSetRouter(TestRouter): # The router to set. - _router = network_fakes.FakeRouter.create_one_router() + _default_route = {'destination': '10.20.20.0/24', 'nexthop': '10.20.30.1'} + _router = network_fakes.FakeRouter.create_one_router( + attrs={'routes': [_default_route]} + ) def setUp(self): super(TestSetRouter, self).setUp() @@ -472,7 +470,6 @@ class TestSetRouter(TestRouter): ('distributed', False), ] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -491,8 +488,8 @@ class TestSetRouter(TestRouter): result = self.cmd.take_action(parsed_args) attrs = { - 'routes': [{'destination': '10.20.30.0/24', - 'gateway': '10.20.30.1'}], + 'routes': self._router.routes + [{'destination': '10.20.30.0/24', + 'nexthop': '10.20.30.1'}], } self.network.update_router.assert_called_once_with( self._router, **attrs) @@ -531,7 +528,6 @@ class TestSetRouter(TestRouter): ('clear_routes', True), ] - # Argument parse failing should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -572,7 +568,7 @@ class TestShowRouter(TestRouter): _router.id, _router.name, _router.tenant_id, - _router.routes, + router._format_routes(_router.routes), _router.status, ) @@ -588,7 +584,6 @@ class TestShowRouter(TestRouter): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -605,5 +600,5 @@ class TestShowRouter(TestRouter): self.network.find_router.assert_called_once_with( self._router.name, ignore_missing=False) - self.assertEqual(tuple(self.columns), columns) + self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py index dd6a3d41..213367a4 100644 --- a/openstackclient/tests/network/v2/test_security_group.py +++ b/openstackclient/tests/network/v2/test_security_group.py @@ -114,7 +114,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): 'description': self._security_group.name, 'name': self._security_group.name, }) - self.assertEqual(tuple(self.columns), columns) + self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) def test_create_all_options(self): @@ -139,7 +139,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): 'name': self._security_group.name, 'tenant_id': identity_fakes.project_id, }) - self.assertEqual(tuple(self.columns), columns) + self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -296,28 +296,30 @@ class TestDeleteSecurityGroupCompute(TestSecurityGroupCompute): class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): # The security group to be listed. - _security_group = \ - network_fakes.FakeSecurityGroup.create_one_security_group() + _security_groups = \ + network_fakes.FakeSecurityGroup.create_security_groups(count=3) - expected_columns = ( + columns = ( 'ID', 'Name', 'Description', 'Project', ) - expected_data = (( - _security_group.id, - _security_group.name, - _security_group.description, - _security_group.tenant_id, - ),) + data = [] + for grp in _security_groups: + data.append(( + grp.id, + grp.name, + grp.description, + grp.tenant_id, + )) def setUp(self): super(TestListSecurityGroupNetwork, self).setUp() self.network.security_groups = mock.Mock( - return_value=[self._security_group]) + return_value=self._security_groups) # Get the command object to test self.cmd = security_group.ListSecurityGroup(self.app, self.namespace) @@ -332,8 +334,8 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): columns, data = self.cmd.take_action(parsed_args) self.network.security_groups.assert_called_once_with() - self.assertEqual(self.expected_columns, columns) - self.assertEqual(self.expected_data, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) def test_security_group_list_all_projects(self): arglist = [ @@ -347,45 +349,49 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): columns, data = self.cmd.take_action(parsed_args) self.network.security_groups.assert_called_once_with() - self.assertEqual(self.expected_columns, columns) - self.assertEqual(self.expected_data, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) class TestListSecurityGroupCompute(TestSecurityGroupCompute): # The security group to be listed. - _security_group = \ - compute_fakes.FakeSecurityGroup.create_one_security_group() + _security_groups = \ + compute_fakes.FakeSecurityGroup.create_security_groups(count=3) - expected_columns = ( + columns = ( 'ID', 'Name', 'Description', ) - expected_columns_all_projects = ( + columns_all_projects = ( 'ID', 'Name', 'Description', 'Project', ) - expected_data = (( - _security_group.id, - _security_group.name, - _security_group.description, - ),) - expected_data_all_projects = (( - _security_group.id, - _security_group.name, - _security_group.description, - _security_group.tenant_id, - ),) + data = [] + for grp in _security_groups: + data.append(( + grp.id, + grp.name, + grp.description, + )) + data_all_projects = [] + for grp in _security_groups: + data_all_projects.append(( + grp.id, + grp.name, + grp.description, + grp.tenant_id, + )) def setUp(self): super(TestListSecurityGroupCompute, self).setUp() self.app.client_manager.network_endpoint_enabled = False - self.compute.security_groups.list.return_value = [self._security_group] + self.compute.security_groups.list.return_value = self._security_groups # Get the command object to test self.cmd = security_group.ListSecurityGroup(self.app, None) @@ -401,8 +407,8 @@ class TestListSecurityGroupCompute(TestSecurityGroupCompute): kwargs = {'search_opts': {'all_tenants': False}} self.compute.security_groups.list.assert_called_once_with(**kwargs) - self.assertEqual(self.expected_columns, columns) - self.assertEqual(self.expected_data, tuple(data)) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) def test_security_group_list_all_projects(self): arglist = [ @@ -417,8 +423,8 @@ class TestListSecurityGroupCompute(TestSecurityGroupCompute): kwargs = {'search_opts': {'all_tenants': True}} self.compute.security_groups.list.assert_called_once_with(**kwargs) - self.assertEqual(self.expected_columns_all_projects, columns) - self.assertEqual(self.expected_data_all_projects, tuple(data)) + self.assertEqual(self.columns_all_projects, columns) + self.assertEqual(self.data_all_projects, list(data)) class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork): diff --git a/openstackclient/tests/network/v2/test_security_group_rule.py b/openstackclient/tests/network/v2/test_security_group_rule.py index df7414aa..bd903f9e 100644 --- a/openstackclient/tests/network/v2/test_security_group_rule.py +++ b/openstackclient/tests/network/v2/test_security_group_rule.py @@ -174,7 +174,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, }) - self.assertEqual(tuple(self.expected_columns), columns) + self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) def test_create_source_group(self): @@ -209,7 +209,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'remote_group_id': self._security_group_rule.remote_group_id, 'security_group_id': self._security_group.id, }) - self.assertEqual(tuple(self.expected_columns), columns) + self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) def test_create_source_ip(self): @@ -240,7 +240,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, }) - self.assertEqual(tuple(self.expected_columns), columns) + self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) def test_create_network_options(self): @@ -282,7 +282,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'security_group_id': self._security_group.id, 'tenant_id': identity_fakes.project_id, }) - self.assertEqual(tuple(self.expected_columns), columns) + self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) @@ -816,7 +816,7 @@ class TestShowSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): self.network.find_security_group_rule.assert_called_once_with( self._security_group_rule.id, ignore_missing=False) - self.assertEqual(tuple(self.columns), columns) + self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) diff --git a/openstackclient/tests/network/v2/test_subnet.py b/openstackclient/tests/network/v2/test_subnet.py index ede37416..1923286d 100644 --- a/openstackclient/tests/network/v2/test_subnet.py +++ b/openstackclient/tests/network/v2/test_subnet.py @@ -627,4 +627,4 @@ class TestShowSubnet(TestSubnet): self._subnet.name, ignore_missing=False) self.assertEqual(self.columns, columns) - self.assertEqual(list(self.data), list(data)) + self.assertEqual(self.data, data) diff --git a/openstackclient/tests/network/v2/test_subnet_pool.py b/openstackclient/tests/network/v2/test_subnet_pool.py index cbb32fc3..de12c9e9 100644 --- a/openstackclient/tests/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/network/v2/test_subnet_pool.py @@ -109,7 +109,17 @@ class TestCreateSubnetPool(TestSubnetPool): arglist = [] verifylist = [] - # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_create_no_pool_prefix(self): + """Make sure --pool-prefix is a required argument""" + arglist = [ + self._subnet_pool.name, + ] + verifylist = [ + ('name', self._subnet_pool.name), + ] self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) @@ -138,23 +148,26 @@ class TestCreateSubnetPool(TestSubnetPool): '--default-prefix-length', self._subnet_pool.default_prefixlen, '--max-prefix-length', self._subnet_pool.max_prefixlen, '--min-prefix-length', self._subnet_pool.min_prefixlen, + '--pool-prefix', '10.0.10.0/24', self._subnet_pool.name, ] verifylist = [ - ('default_prefix_length', self._subnet_pool.default_prefixlen), - ('max_prefix_length', self._subnet_pool.max_prefixlen), - ('min_prefix_length', self._subnet_pool.min_prefixlen), + ('default_prefix_length', + int(self._subnet_pool.default_prefixlen)), + ('max_prefix_length', int(self._subnet_pool.max_prefixlen)), + ('min_prefix_length', int(self._subnet_pool.min_prefixlen)), ('name', self._subnet_pool.name), + ('prefixes', ['10.0.10.0/24']), ] 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(**{ - 'default_prefixlen': self._subnet_pool.default_prefixlen, - 'max_prefixlen': self._subnet_pool.max_prefixlen, - 'min_prefixlen': self._subnet_pool.min_prefixlen, - 'prefixes': [], + 'default_prefixlen': int(self._subnet_pool.default_prefixlen), + 'max_prefixlen': int(self._subnet_pool.max_prefixlen), + 'min_prefixlen': int(self._subnet_pool.min_prefixlen), + 'prefixes': ['10.0.10.0/24'], 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) @@ -221,6 +234,32 @@ class TestCreateSubnetPool(TestSubnetPool): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_create_default_and_shared_options(self): + arglist = [ + '--pool-prefix', '10.0.10.0/24', + '--default', + '--share', + self._subnet_pool.name, + ] + verifylist = [ + ('prefixes', ['10.0.10.0/24']), + ('default', True), + ('share', True), + ('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(**{ + 'is_default': True, + 'name': self._subnet_pool.name, + 'prefixes': ['10.0.10.0/24'], + 'shared': True, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteSubnetPool(TestSubnetPool): @@ -267,6 +306,8 @@ class TestListSubnetPool(TestSubnetPool): columns_long = columns + ( 'Default Prefix Length', 'Address Scope', + 'Default Subnet Pool', + 'Shared', ) data = [] @@ -285,6 +326,8 @@ class TestListSubnetPool(TestSubnetPool): utils.format_list(pool.prefixes), pool.default_prefixlen, pool.address_scope_id, + pool.is_default, + pool.shared, )) def setUp(self): @@ -354,8 +397,8 @@ class TestSetSubnetPool(TestSubnetPool): ] verifylist = [ ('name', 'noob'), - ('default_prefix_length', '8'), - ('min_prefix_length', '8'), + ('default_prefix_length', 8), + ('min_prefix_length', 8), ('subnet_pool', self._subnet_pool.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -364,8 +407,8 @@ class TestSetSubnetPool(TestSubnetPool): attrs = { 'name': 'noob', - 'default_prefixlen': '8', - 'min_prefixlen': '8', + 'default_prefixlen': 8, + 'min_prefixlen': 8, } self.network.update_subnet_pool.assert_called_once_with( self._subnet_pool, **attrs) @@ -380,7 +423,7 @@ class TestSetSubnetPool(TestSubnetPool): ] verifylist = [ ('prefixes', ['10.0.1.0/24', '10.0.2.0/24']), - ('max_prefix_length', '16'), + ('max_prefix_length', 16), ('subnet_pool', self._subnet_pool.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -391,7 +434,7 @@ class TestSetSubnetPool(TestSubnetPool): prefixes.extend(self._subnet_pool.prefixes) attrs = { 'prefixes': prefixes, - 'max_prefixlen': '16', + 'max_prefixlen': 16, } self.network.update_subnet_pool.assert_called_once_with( self._subnet_pool, **attrs) @@ -474,6 +517,62 @@ class TestSetSubnetPool(TestSubnetPool): self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) + def test_set_default(self): + arglist = [ + '--default', + self._subnet_pool.name, + ] + verifylist = [ + ('default', True), + ('subnet_pool', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'is_default': True + } + self.network.update_subnet_pool.assert_called_once_with( + self._subnet_pool, **attrs) + self.assertIsNone(result) + + def test_set_no_default(self): + arglist = [ + '--no-default', + self._subnet_pool.name, + ] + verifylist = [ + ('no_default', True), + ('subnet_pool', self._subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'is_default': False, + } + self.network.update_subnet_pool.assert_called_once_with( + self._subnet_pool, **attrs) + self.assertIsNone(result) + + def test_set_no_default_conflict(self): + arglist = [ + '--default', + '--no-default', + self._subnet_pool.name, + ] + verifylist = [ + ('default', True), + ('no_default', True), + ('subnet_pool', self._subnet_pool.name), + ] + + # Exclusive arguments will conflict here. + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + class TestShowSubnetPool(TestSubnetPool): @@ -524,7 +623,6 @@ class TestShowSubnetPool(TestSubnetPool): arglist = [] verifylist = [] - # Missing required args should bail here self.assertRaises(tests_utils.ParserException, self.check_parser, self.cmd, arglist, verifylist) diff --git a/openstackclient/tests/volume/v1/fakes.py b/openstackclient/tests/volume/v1/fakes.py index 42673efa..d6c46439 100644 --- a/openstackclient/tests/volume/v1/fakes.py +++ b/openstackclient/tests/volume/v1/fakes.py @@ -129,6 +129,97 @@ QOS_WITH_ASSOCIATIONS = { } +class FakeServiceClient(object): + + def __init__(self, **kwargs): + self.services = mock.Mock() + self.services.resource_class = fakes.FakeResource(None, {}) + + +class TestService(utils.TestCommand): + + def setUp(self): + super(TestService, self).setUp() + + self.app.client_manager.volume = FakeServiceClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) + + +class FakeService(object): + """Fake one or more Services.""" + + @staticmethod + def create_one_service(attrs=None): + """Create a fake service. + + :param Dictionary attrs: + A dictionary with all attributes of service + :retrun: + A FakeResource object with host, status, etc. + """ + # Set default attribute + service_info = { + 'host': 'host_test', + 'binary': 'cinder_test', + 'status': 'enabled', + 'disabled_reason': 'LongHoliday-GoldenWeek', + 'zone': 'fake_zone', + 'updated_at': 'fake_date', + 'state': 'fake_state', + } + + # Overwrite default attributes if there are some attributes set + if attrs is None: + attrs = {} + service_info.update(attrs) + + service = fakes.FakeResource( + None, + service_info, + loaded=True) + + return service + + @staticmethod + def create_services(attrs=None, count=2): + """Create multiple fake services. + + :param Dictionary attrs: + A dictionary with all attributes of service + :param Integer count: + The number of services to be faked + :return: + A list of FakeResource objects + """ + services = [] + for n in range(0, count): + services.append(FakeService.create_one_service(attrs)) + + return services + + @staticmethod + def get_services(services=None, count=2): + """Get an iterable MagicMock object with a list of faked services. + + If services list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List services: + A list of FakeResource objects faking services + :param Integer count: + The number of services to be faked + :return + An iterable Mock object with side_effect set to a list of faked + services + """ + if services is None: + services = FakeService.create_services(count) + + return mock.MagicMock(side_effect=services) + + class FakeImagev1Client(object): def __init__(self, **kwargs): diff --git a/openstackclient/tests/volume/v1/test_service.py b/openstackclient/tests/volume/v1/test_service.py new file mode 100644 index 00000000..71684344 --- /dev/null +++ b/openstackclient/tests/volume/v1/test_service.py @@ -0,0 +1,141 @@ +# +# 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 openstackclient.tests.volume.v1 import fakes as service_fakes +from openstackclient.volume.v1 import service + + +class TestService(service_fakes.TestService): + + 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, + ) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 6e631c41..120666a0 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -232,6 +232,97 @@ EXTENSION = { } +class FakeServiceClient(object): + + def __init__(self, **kwargs): + self.services = mock.Mock() + self.services.resource_class = fakes.FakeResource(None, {}) + + +class TestService(utils.TestCommand): + + def setUp(self): + super(TestService, self).setUp() + + self.app.client_manager.volume = FakeServiceClient( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN + ) + + +class FakeService(object): + """Fake one or more Services.""" + + @staticmethod + def create_one_service(attrs=None): + """Create a fake service. + + :param Dictionary attrs: + A dictionary with all attributes of service + :retrun: + A FakeResource object with host, status, etc. + """ + # Set default attribute + service_info = { + 'host': 'host_test', + 'binary': 'cinder_test', + 'status': 'enabled', + 'disabled_reason': 'LongHoliday-GoldenWeek', + 'zone': 'fake_zone', + 'updated_at': 'fake_date', + 'state': 'fake_state', + } + + # Overwrite default attributes if there are some attributes set + if attrs is None: + attrs = {} + service_info.update(attrs) + + service = fakes.FakeResource( + None, + service_info, + loaded=True) + + return service + + @staticmethod + def create_services(attrs=None, count=2): + """Create multiple fake services. + + :param Dictionary attrs: + A dictionary with all attributes of service + :param Integer count: + The number of services to be faked + :return: + A list of FakeResource objects + """ + services = [] + for n in range(0, count): + services.append(FakeService.create_one_service(attrs)) + + return services + + @staticmethod + def get_services(services=None, count=2): + """Get an iterable MagicMock object with a list of faked services. + + If services list is provided, then initialize the Mock object with the + list. Otherwise create one. + + :param List services: + A list of FakeResource objects faking services + :param Integer count: + The number of services to be faked + :return + An iterable Mock object with side_effect set to a list of faked + services + """ + if services is None: + services = FakeService.create_services(count) + + return mock.MagicMock(side_effect=services) + + class FakeVolumeClient(object): def __init__(self, **kwargs): @@ -281,7 +372,7 @@ class FakeVolume(object): """ @staticmethod - def create_one_volume(attrs={}): + def create_one_volume(attrs=None): """Create a fake volume. :param Dictionary attrs: @@ -289,6 +380,8 @@ class FakeVolume(object): :retrun: A FakeResource object with id, name, status, etc. """ + attrs = attrs or {} + # Set default attribute volume_info = { 'id': 'volume-id' + uuid.uuid4().hex, @@ -320,7 +413,7 @@ class FakeVolume(object): return volume @staticmethod - def create_volumes(attrs={}, count=2): + def create_volumes(attrs=None, count=2): """Create multiple fake volumes. :param Dictionary attrs: @@ -361,7 +454,7 @@ class FakeAvailabilityZone(object): """Fake one or more volume availability zones (AZs).""" @staticmethod - def create_one_availability_zone(attrs={}): + def create_one_availability_zone(attrs=None): """Create a fake AZ. :param Dictionary attrs: @@ -369,6 +462,8 @@ class FakeAvailabilityZone(object): :return: A FakeResource object with zoneName, zoneState, etc. """ + attrs = attrs or {} + # Set default attributes. availability_zone = { 'zoneName': uuid.uuid4().hex, @@ -384,7 +479,7 @@ class FakeAvailabilityZone(object): return availability_zone @staticmethod - def create_availability_zones(attrs={}, count=2): + def create_availability_zones(attrs=None, count=2): """Create multiple fake AZs. :param Dictionary attrs: diff --git a/openstackclient/tests/volume/v2/test_service.py b/openstackclient/tests/volume/v2/test_service.py new file mode 100644 index 00000000..ba2e1b32 --- /dev/null +++ b/openstackclient/tests/volume/v2/test_service.py @@ -0,0 +1,141 @@ +# +# 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 openstackclient.tests.volume.v2 import fakes as service_fakes +from openstackclient.volume.v2 import service + + +class TestService(service_fakes.TestService): + + 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, + ) diff --git a/openstackclient/volume/v1/service.py b/openstackclient/volume/v1/service.py new file mode 100644 index 00000000..f26be13e --- /dev/null +++ b/openstackclient/volume/v1/service.py @@ -0,0 +1,70 @@ +# +# 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. +# + +"""Service action implementations""" + +from openstackclient.common import command +from openstackclient.common import utils + + +class ListService(command.Lister): + """List service command""" + + def get_parser(self, prog_name): + parser = super(ListService, self).get_parser(prog_name) + parser.add_argument( + "--host", + metavar="<host>", + help="List services on specified host (name only)") + parser.add_argument( + "--service", + metavar="<service>", + help="List only specified service (name only)") + parser.add_argument( + "--long", + action="store_true", + default=False, + help="List additional fields in output" + ) + return parser + + def take_action(self, parsed_args): + service_client = self.app.client_manager.volume + + if parsed_args.long: + columns = [ + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At", + "Disabled Reason" + ] + else: + columns = [ + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At" + ] + + data = service_client.services.list(parsed_args.host, + parsed_args.service) + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) diff --git a/openstackclient/volume/v2/service.py b/openstackclient/volume/v2/service.py new file mode 100644 index 00000000..f26be13e --- /dev/null +++ b/openstackclient/volume/v2/service.py @@ -0,0 +1,70 @@ +# +# 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. +# + +"""Service action implementations""" + +from openstackclient.common import command +from openstackclient.common import utils + + +class ListService(command.Lister): + """List service command""" + + def get_parser(self, prog_name): + parser = super(ListService, self).get_parser(prog_name) + parser.add_argument( + "--host", + metavar="<host>", + help="List services on specified host (name only)") + parser.add_argument( + "--service", + metavar="<service>", + help="List only specified service (name only)") + parser.add_argument( + "--long", + action="store_true", + default=False, + help="List additional fields in output" + ) + return parser + + def take_action(self, parsed_args): + service_client = self.app.client_manager.volume + + if parsed_args.long: + columns = [ + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At", + "Disabled Reason" + ] + else: + columns = [ + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At" + ] + + data = service_client.services.list(parsed_args.host, + parsed_args.service) + return (columns, + (utils.get_item_properties( + s, columns, + ) for s in data)) |
