diff options
Diffstat (limited to 'openstackclient')
21 files changed, 986 insertions, 175 deletions
diff --git a/openstackclient/api/utils.py b/openstackclient/api/utils.py index b7ff7f23..fa759cd3 100644 --- a/openstackclient/api/utils.py +++ b/openstackclient/api/utils.py @@ -27,11 +27,11 @@ def simple_filter( be changed if any filtering occurs. :param string attr: The name of the attribute to filter. If attr does not exist no - match will succeed and no rows will be retrurned. If attr is + match will succeed and no rows will be returned. If attr is None no filtering will be performed and all rows will be returned. :param sring value: The value to filter. None is considered to be a 'no filter' value. - '' matches agains a Python empty string. + '' matches against a Python empty string. :param string property_field: The name of the data field containing a property dict to filter. If property_field is None, attr is a field name. If property_field diff --git a/openstackclient/common/availability_zone.py b/openstackclient/common/availability_zone.py index fa5aee47..a941418b 100644 --- a/openstackclient/common/availability_zone.py +++ b/openstackclient/common/availability_zone.py @@ -30,6 +30,8 @@ def _xform_common_availability_zone(az, zone_info): if hasattr(az, 'zoneName'): zone_info['zone_name'] = az.zoneName + zone_info['zone_resource'] = '' + def _xform_compute_availability_zone(az, include_extra): result = [] @@ -69,6 +71,18 @@ def _xform_volume_availability_zone(az): return result +def _xform_network_availability_zone(az): + result = [] + zone_info = {} + zone_info['zone_name'] = getattr(az, 'name', '') + zone_info['zone_status'] = getattr(az, 'state', '') + if 'unavailable' == zone_info['zone_status']: + zone_info['zone_status'] = 'not available' + zone_info['zone_resource'] = getattr(az, 'resource', '') + result.append(zone_info) + return result + + class ListAvailabilityZone(command.Lister): """List availability zones and their status""" @@ -80,6 +94,11 @@ class ListAvailabilityZone(command.Lister): default=False, help='List compute availability zones') parser.add_argument( + '--network', + action='store_true', + default=False, + help='List network availability zones') + parser.add_argument( '--volume', action='store_true', default=False, @@ -92,7 +111,7 @@ class ListAvailabilityZone(command.Lister): ) return parser - def get_compute_availability_zones(self, parsed_args): + def _get_compute_availability_zones(self, parsed_args): compute_client = self.app.client_manager.compute try: data = compute_client.availability_zones.list() @@ -108,36 +127,63 @@ class ListAvailabilityZone(command.Lister): result += _xform_compute_availability_zone(zone, parsed_args.long) return result - def get_volume_availability_zones(self, parsed_args): + def _get_volume_availability_zones(self, parsed_args): volume_client = self.app.client_manager.volume + data = [] try: data = volume_client.availability_zones.list() - except Exception: - message = "Availability zones list not supported by " \ - "Block Storage API" - self.log.warning(message) + except Exception as e: + self.log.debug('Volume availability zone exception: ' + str(e)) + if parsed_args.volume: + message = "Availability zones list not supported by " \ + "Block Storage API" + self.log.warning(message) result = [] for zone in data: result += _xform_volume_availability_zone(zone) return result + def _get_network_availability_zones(self, parsed_args): + network_client = self.app.client_manager.network + data = [] + try: + # Verify that the extension exists. + network_client.find_extension('Availability Zone', + ignore_missing=False) + data = network_client.availability_zones() + except Exception as e: + self.log.debug('Network availability zone exception: ' + str(e)) + if parsed_args.network: + message = "Availability zones list not supported by " \ + "Network API" + self.log.warning(message) + + result = [] + for zone in data: + result += _xform_network_availability_zone(zone) + return result + def take_action(self, parsed_args): if parsed_args.long: - columns = ('Zone Name', 'Zone Status', + columns = ('Zone Name', 'Zone Status', 'Zone Resource', 'Host Name', 'Service Name', 'Service Status') else: columns = ('Zone Name', 'Zone Status') # Show everything by default. - show_all = (not parsed_args.compute and not parsed_args.volume) + show_all = (not parsed_args.compute and + not parsed_args.volume and + not parsed_args.network) result = [] if parsed_args.compute or show_all: - result += self.get_compute_availability_zones(parsed_args) + result += self._get_compute_availability_zones(parsed_args) if parsed_args.volume or show_all: - result += self.get_volume_availability_zones(parsed_args) + result += self._get_volume_availability_zones(parsed_args) + if parsed_args.network or show_all: + result += self._get_network_availability_zones(parsed_args) return (columns, (utils.get_dict_properties( diff --git a/openstackclient/common/utils.py b/openstackclient/common/utils.py index 3ae30c8f..4142f830 100644 --- a/openstackclient/common/utils.py +++ b/openstackclient/common/utils.py @@ -26,33 +26,6 @@ from oslo_utils import importutils from openstackclient.common import exceptions -class log_method(object): - - def __init__(self, log=None, level=logging.DEBUG): - self._log = log - self._level = level - - def __call__(self, func): - func_name = func.__name__ - if not self._log: - self._log = logging.getLogger(func.__class__.__name__) - - @six.wraps(func) - def wrapper(*args, **kwargs): - if self._log.isEnabledFor(self._level): - pretty_args = [] - if args: - pretty_args.extend(str(a) for a in args) - if kwargs: - pretty_args.extend( - "%s=%s" % (k, v) for k, v in six.iteritems(kwargs)) - self._log.log(self._level, "%s(%s)", - func_name, ", ".join(pretty_args)) - return func(*args, **kwargs) - - return wrapper - - def find_resource(manager, name_or_id, **kwargs): """Helper for the _find_* methods. @@ -169,6 +142,16 @@ def format_list(data, separator=', '): return separator.join(sorted(data)) +def format_list_of_dicts(data): + """Return a formatted string of key value pairs for each dict + + :param data: a list of dicts + :rtype: a string formatted to key='value' with dicts separated by new line + """ + + return '\n'.join(format_dict(i) for i in data) + + def get_field(item, field): try: if isinstance(item, dict): @@ -331,6 +314,8 @@ def wait_for_status(status_f, def wait_for_delete(manager, res_id, status_field='status', + error_status=['error'], + exception_name=['NotFound'], sleep_time=5, timeout=300, callback=None): @@ -341,6 +326,8 @@ def wait_for_delete(manager, :param status_field: the status attribute in the returned resource object, this is used to check for error states while the resource is being deleted + :param error_status: a list of status strings for error + :param exception_name: a list of exception strings for deleted case :param sleep_time: wait this long between checks (seconds) :param timeout: check until this long (seconds) :param callback: called per sleep cycle, useful to display progress; this @@ -357,12 +344,12 @@ def wait_for_delete(manager, # handle a NotFound exception here without parsing the message res = manager.get(res_id) except Exception as ex: - if type(ex).__name__ == 'NotFound': + if type(ex).__name__ in exception_name: return True raise status = getattr(res, status_field, '').lower() - if status == 'error': + if status in error_status: return False if callback: diff --git a/openstackclient/compute/v2/security_group.py b/openstackclient/compute/v2/security_group.py index b975a505..2a8908d7 100644 --- a/openstackclient/compute/v2/security_group.py +++ b/openstackclient/compute/v2/security_group.py @@ -169,28 +169,6 @@ class CreateSecurityGroupRule(command.ShowOne): return zip(*sorted(six.iteritems(info))) -class DeleteSecurityGroup(command.Command): - """Delete a security group""" - - def get_parser(self, prog_name): - parser = super(DeleteSecurityGroup, self).get_parser(prog_name) - parser.add_argument( - 'group', - metavar='<group>', - help='Security group to delete (name or ID)', - ) - return parser - - def take_action(self, parsed_args): - - compute_client = self.app.client_manager.compute - data = utils.find_resource( - compute_client.security_groups, - parsed_args.group, - ) - compute_client.security_groups.delete(data.id) - - class DeleteSecurityGroupRule(command.Command): """Delete a security group rule""" diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index 39a53195..3f162181 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -431,6 +431,20 @@ class ListImage(command.Lister): "(default: asc), multiple keys and directions can be " "specified separated by comma", ) + parser.add_argument( + "--limit", + metavar="<limit>", + type=int, + help="Maximum number of images to display.", + ) + parser.add_argument( + '--marker', + metavar='<marker>', + default=None, + help="The last image (name or ID) of the previous page. Display " + "list of images after marker. Display all images if not " + "specified." + ) return parser def take_action(self, parsed_args): @@ -443,6 +457,11 @@ class ListImage(command.Lister): kwargs['private'] = True if parsed_args.shared: kwargs['shared'] = True + if parsed_args.limit: + kwargs['limit'] = parsed_args.limit + if parsed_args.marker: + kwargs['marker'] = utils.find_resource(image_client.images, + parsed_args.marker).id if parsed_args.long: columns = ( @@ -474,16 +493,7 @@ class ListImage(command.Lister): column_headers = columns # List of image data received - data = [] - # No pages received yet, so start the page marker at None. - marker = None - while True: - page = image_client.api.image_list(marker=marker, **kwargs) - if not page: - break - data.extend(page) - # Set the marker to the id of the last item we received - marker = page[-1]['id'] + data = image_client.api.image_list(**kwargs) if parsed_args.property: # NOTE(dtroyer): coerce to a list to subscript it in py3 diff --git a/openstackclient/network/common.py b/openstackclient/network/common.py new file mode 100644 index 00000000..c539dd05 --- /dev/null +++ b/openstackclient/network/common.py @@ -0,0 +1,62 @@ +# 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 abc +import six + +from openstackclient.common import command + + +@six.add_metaclass(abc.ABCMeta) +class NetworkAndComputeCommand(command.Command): + """Network and Compute Command""" + + def take_action(self, parsed_args): + if self.app.client_manager.is_network_endpoint_enabled(): + return self.take_action_network(self.app.client_manager.network, + parsed_args) + else: + return self.take_action_compute(self.app.client_manager.compute, + parsed_args) + + def get_parser(self, prog_name): + self.log.debug('get_parser(%s)', prog_name) + parser = super(NetworkAndComputeCommand, self).get_parser(prog_name) + parser = self.update_parser_common(parser) + self.log.debug('common parser: %s', parser) + if self.app.client_manager.is_network_endpoint_enabled(): + return self.update_parser_network(parser) + else: + return self.update_parser_compute(parser) + + def update_parser_common(self, parser): + """Default is no updates to parser.""" + return parser + + def update_parser_network(self, parser): + """Default is no updates to parser.""" + return parser + + def update_parser_compute(self, parser): + """Default is no updates to parser.""" + return parser + + @abc.abstractmethod + def take_action_network(self, client, parsed_args): + """Override to do something useful.""" + pass + + @abc.abstractmethod + def take_action_compute(self, client, parsed_args): + """Override to do something useful.""" + pass diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index fc94fd82..61237219 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -47,6 +47,33 @@ def _get_columns(item): return tuple(sorted(columns)) +def _get_attrs(client_manager, parsed_args): + attrs = {} + if parsed_args.name is not None: + attrs['name'] = str(parsed_args.name) + if parsed_args.admin_state is not None: + attrs['admin_state_up'] = parsed_args.admin_state + if parsed_args.shared is not None: + attrs['shared'] = parsed_args.shared + + # "network set" command doesn't support setting project. + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['tenant_id'] = project_id + + # "network set" command doesn't support setting availability zone hints. + if 'availability_zone_hints' in parsed_args and \ + parsed_args.availability_zone_hints is not None: + attrs['availability_zone_hints'] = parsed_args.availability_zone_hints + + return attrs + + class CreateNetwork(command.ShowOne): """Create new network""" @@ -105,31 +132,14 @@ class CreateNetwork(command.ShowOne): def take_action(self, parsed_args): client = self.app.client_manager.network - body = self.get_body(parsed_args) - obj = client.create_network(**body) + + attrs = _get_attrs(self.app.client_manager, parsed_args) + obj = client.create_network(**attrs) columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters=_formatters) return (columns, data) - def get_body(self, parsed_args): - body = {'name': str(parsed_args.name), - 'admin_state_up': parsed_args.admin_state} - if parsed_args.shared is not None: - body['shared'] = parsed_args.shared - if parsed_args.project is not None: - identity_client = self.app.client_manager.identity - project_id = identity_common.find_project( - identity_client, - parsed_args.project, - parsed_args.project_domain, - ).id - body['tenant_id'] = project_id - if parsed_args.availability_zone_hints is not None: - body['availability_zone_hints'] = \ - parsed_args.availability_zone_hints - - return body - class DeleteNetwork(command.Command): """Delete network(s)""" @@ -271,18 +281,13 @@ class SetNetwork(command.Command): client = self.app.client_manager.network obj = client.find_network(parsed_args.identifier, ignore_missing=False) - if parsed_args.name is not None: - obj.name = str(parsed_args.name) - if parsed_args.admin_state is not None: - obj.admin_state_up = parsed_args.admin_state - if parsed_args.shared is not None: - obj.shared = parsed_args.shared - - if not obj.is_dirty: + attrs = _get_attrs(self.app.client_manager, parsed_args) + if attrs == {}: msg = "Nothing specified to be set" raise exceptions.CommandError(msg) - client.update_network(obj) + client.update_network(obj, **attrs) + return class ShowNetwork(command.ShowOne): diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 0d5b183e..46cb031f 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -14,6 +14,42 @@ """Port action implementations""" from openstackclient.common import command +from openstackclient.common import utils + + +def _format_admin_state(state): + return 'UP' if state else 'DOWN' + + +_formatters = { + 'admin_state_up': _format_admin_state, + 'allowed_address_pairs': utils.format_list_of_dicts, + 'binding_profile': utils.format_dict, + 'binding_vif_details': utils.format_dict, + 'dns_assignment': utils.format_list_of_dicts, + 'extra_dhcp_opts': utils.format_list_of_dicts, + 'fixed_ips': utils.format_list_of_dicts, + 'security_groups': utils.format_list, +} + + +def _get_columns(item): + columns = item.keys() + if 'tenant_id' in columns: + columns.remove('tenant_id') + columns.append('project_id') + binding_columns = [ + 'binding:host_id', + 'binding:profile', + 'binding:vif_details', + 'binding:vif_type', + 'binding:vnic_type', + ] + for binding_column in binding_columns: + if binding_column in columns: + columns.remove(binding_column) + columns.append(binding_column.replace('binding:', 'binding_', 1)) + return sorted(columns) class DeletePort(command.Command): @@ -35,3 +71,23 @@ class DeletePort(command.Command): for port in parsed_args.port: res = client.find_port(port) client.delete_port(res) + + +class ShowPort(command.ShowOne): + """Display port details""" + + def get_parser(self, prog_name): + parser = super(ShowPort, self).get_parser(prog_name) + parser.add_argument( + 'port', + metavar="<port>", + help="Port to display (name or ID)" + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_port(parsed_args.port, ignore_missing=False) + columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters=_formatters) + return (tuple(columns), data) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index 6c8acb63..60db816a 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -35,6 +35,8 @@ def _format_external_gateway_info(info): _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, } @@ -46,6 +48,9 @@ def _get_attrs(client_manager, parsed_args): attrs['admin_state_up'] = parsed_args.admin_state_up if parsed_args.distributed is not None: attrs['distributed'] = parsed_args.distributed + if ('availability_zone_hints' in parsed_args + and parsed_args.availability_zone_hints is not None): + attrs['availability_zone_hints'] = parsed_args.availability_zone_hints # "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 @@ -96,9 +101,19 @@ class CreateRouter(command.ShowOne): ) parser.add_argument( '--project', - metavar='<poroject>', + metavar='<project>', help="Owner's project (name or ID)", ) + parser.add_argument( + '--availability-zone-hint', + metavar='<availability-zone>', + action='append', + dest='availability_zone_hints', + help='Availability Zone in which to create this router ' + '(requires the Router Availability Zone extension, ' + 'this option can be repeated).', + ) + identity_common.add_project_domain_option_to_parser(parser) return parser @@ -176,10 +191,12 @@ class ListRouter(command.Lister): columns = columns + ( 'routes', 'external_gateway_info', + 'availability_zones' ) column_headers = column_headers + ( 'Routes', 'External gateway info', + 'Availability zones' ) data = client.routers() diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py new file mode 100644 index 00000000..4e122f21 --- /dev/null +++ b/openstackclient/network/v2/security_group.py @@ -0,0 +1,40 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Security Group action implementations""" + +from openstackclient.common import utils +from openstackclient.network import common + + +class DeleteSecurityGroup(common.NetworkAndComputeCommand): + """Delete a security group""" + + def update_parser_common(self, parser): + parser.add_argument( + 'group', + metavar='<group>', + help='Security group to delete (name or ID)', + ) + return parser + + def take_action_network(self, client, parsed_args): + obj = client.find_security_group(parsed_args.group) + client.delete_security_group(obj) + + def take_action_compute(self, client, parsed_args): + data = utils.find_resource( + client.security_groups, + parsed_args.group, + ) + client.security_groups.delete(data.id) diff --git a/openstackclient/tests/common/test_availability_zone.py b/openstackclient/tests/common/test_availability_zone.py index 232b56c9..e44455c7 100644 --- a/openstackclient/tests/common/test_availability_zone.py +++ b/openstackclient/tests/common/test_availability_zone.py @@ -11,11 +11,13 @@ # under the License. # +import mock import six from openstackclient.common import availability_zone from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes +from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils from openstackclient.tests.volume.v2 import fakes as volume_fakes @@ -33,6 +35,7 @@ def _build_compute_az_datalist(compute_az, long_datalist=False): datalist += ( compute_az.zoneName, 'available', + '', host, service, 'enabled :-) ' + state['updated_at'], @@ -51,6 +54,23 @@ def _build_volume_az_datalist(volume_az, long_datalist=False): datalist = ( volume_az.zoneName, 'available', + '', '', '', '', + ) + return (datalist,) + + +def _build_network_az_datalist(network_az, long_datalist=False): + datalist = () + if not long_datalist: + datalist = ( + network_az.name, + network_az.state, + ) + else: + datalist = ( + network_az.name, + network_az.state, + network_az.resource, '', '', '', ) return (datalist,) @@ -79,6 +99,16 @@ class TestAvailabilityZone(utils.TestCommand): self.volume_azs_mock = volume_client.availability_zones self.volume_azs_mock.reset_mock() + network_client = network_fakes.FakeNetworkV2Client( + endpoint=fakes.AUTH_URL, + token=fakes.AUTH_TOKEN, + ) + self.app.client_manager.network = network_client + + network_client.availability_zones = mock.Mock() + network_client.find_extension = mock.Mock() + self.network_azs_mock = network_client.availability_zones + class TestAvailabilityZoneList(TestAvailabilityZone): @@ -86,11 +116,14 @@ class TestAvailabilityZoneList(TestAvailabilityZone): compute_fakes.FakeAvailabilityZone.create_availability_zones() volume_azs = \ volume_fakes.FakeAvailabilityZone.create_availability_zones(count=1) + network_azs = \ + network_fakes.FakeAvailabilityZone.create_availability_zones() short_columnslist = ('Zone Name', 'Zone Status') long_columnslist = ( 'Zone Name', 'Zone Status', + 'Zone Resource', 'Host Name', 'Service Name', 'Service Status', @@ -101,6 +134,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone): self.compute_azs_mock.list.return_value = self.compute_azs self.volume_azs_mock.list.return_value = self.volume_azs + self.network_azs_mock.return_value = self.network_azs # Get the command object to test self.cmd = availability_zone.ListAvailabilityZone(self.app, None) @@ -115,6 +149,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone): self.compute_azs_mock.list.assert_called_with() self.volume_azs_mock.list.assert_called_with() + self.network_azs_mock.assert_called_with() self.assertEqual(self.short_columnslist, columns) datalist = () @@ -122,6 +157,8 @@ class TestAvailabilityZoneList(TestAvailabilityZone): datalist += _build_compute_az_datalist(compute_az) for volume_az in self.volume_azs: datalist += _build_volume_az_datalist(volume_az) + for network_az in self.network_azs: + datalist += _build_network_az_datalist(network_az) self.assertEqual(datalist, tuple(data)) def test_availability_zone_list_long(self): @@ -138,6 +175,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone): self.compute_azs_mock.list.assert_called_with() self.volume_azs_mock.list.assert_called_with() + self.network_azs_mock.assert_called_with() self.assertEqual(self.long_columnslist, columns) datalist = () @@ -147,6 +185,9 @@ class TestAvailabilityZoneList(TestAvailabilityZone): for volume_az in self.volume_azs: datalist += _build_volume_az_datalist(volume_az, long_datalist=True) + for network_az in self.network_azs: + datalist += _build_network_az_datalist(network_az, + long_datalist=True) self.assertEqual(datalist, tuple(data)) def test_availability_zone_list_compute(self): @@ -163,6 +204,7 @@ class TestAvailabilityZoneList(TestAvailabilityZone): self.compute_azs_mock.list.assert_called_with() self.volume_azs_mock.list.assert_not_called() + self.network_azs_mock.assert_not_called() self.assertEqual(self.short_columnslist, columns) datalist = () @@ -184,9 +226,32 @@ class TestAvailabilityZoneList(TestAvailabilityZone): self.compute_azs_mock.list.assert_not_called() self.volume_azs_mock.list.assert_called_with() + self.network_azs_mock.assert_not_called() self.assertEqual(self.short_columnslist, columns) datalist = () for volume_az in self.volume_azs: datalist += _build_volume_az_datalist(volume_az) self.assertEqual(datalist, tuple(data)) + + def test_availability_zone_list_network(self): + arglist = [ + '--network', + ] + verifylist = [ + ('network', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.compute_azs_mock.list.assert_not_called() + self.volume_azs_mock.list.assert_not_called() + self.network_azs_mock.assert_called_with() + + self.assertEqual(self.short_columnslist, columns) + datalist = () + for network_az in self.network_azs: + datalist += _build_network_az_datalist(network_az) + self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/common/test_utils.py b/openstackclient/tests/common/test_utils.py index 064ad417..09545079 100644 --- a/openstackclient/tests/common/test_utils.py +++ b/openstackclient/tests/common/test_utils.py @@ -212,6 +212,28 @@ class TestUtils(test_utils.TestCase): self.assertFalse(utils.wait_for_delete(manager, res_id)) self.assertFalse(mock_sleep.called) + @mock.patch.object(time, 'sleep') + def test_wait_for_delete_error_with_overrides(self, mock_sleep): + # Tests that we fail if the resource is my_status=failed + resource = mock.MagicMock(my_status='FAILED') + mock_get = mock.Mock(return_value=resource) + manager = mock.MagicMock(get=mock_get) + res_id = str(uuid.uuid4()) + self.assertFalse(utils.wait_for_delete(manager, res_id, + status_field='my_status', + error_status=['failed'])) + self.assertFalse(mock_sleep.called) + + @mock.patch.object(time, 'sleep') + def test_wait_for_delete_error_with_overrides_exception(self, mock_sleep): + # Tests that we succeed if the resource is specific exception + mock_get = mock.Mock(side_effect=Exception) + manager = mock.MagicMock(get=mock_get) + res_id = str(uuid.uuid4()) + self.assertTrue(utils.wait_for_delete(manager, res_id, + exception_name=['Exception'])) + self.assertFalse(mock_sleep.called) + def test_build_kwargs_dict_value_set(self): self.assertEqual({'arg_bla': 'bla'}, utils.build_kwargs_dict('arg_bla', 'bla')) @@ -348,6 +370,15 @@ class TestFindResource(test_utils.TestCase): self.assertEqual(expected, utils.format_list(['a', 'b', 'c'])) self.assertEqual(expected, utils.format_list(['c', 'b', 'a'])) + def test_format_list_of_dicts(self): + expected = "a='b', c='d'\ne='f'" + sorted_data = [{'a': 'b', 'c': 'd'}, {'e': 'f'}] + unsorted_data = [{'c': 'd', 'a': 'b'}, {'e': 'f'}] + self.assertEqual(expected, utils.format_list_of_dicts(sorted_data)) + self.assertEqual(expected, utils.format_list_of_dicts(unsorted_data)) + self.assertEqual('', utils.format_list_of_dicts([])) + self.assertEqual('', utils.format_list_of_dicts([{}])) + def test_format_list_separator(self): expected = 'a\nb\nc' actual_pre_sorted = utils.format_list(['a', 'b', 'c'], separator='\n') diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index 41abdaab..d399c9ed 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -498,9 +498,7 @@ class TestImageList(TestImage): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - marker=image_fakes.image_id, - ) + self.api_mock.image_list.assert_called_with() self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) @@ -521,7 +519,6 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( public=True, - marker=image_fakes.image_id, ) self.assertEqual(self.columns, columns) @@ -543,7 +540,6 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( private=True, - marker=image_fakes.image_id, ) self.assertEqual(self.columns, columns) @@ -565,7 +561,6 @@ class TestImageList(TestImage): columns, data = self.cmd.take_action(parsed_args) self.api_mock.image_list.assert_called_with( shared=True, - marker=image_fakes.image_id, ) self.assertEqual(self.columns, columns) @@ -582,9 +577,7 @@ class TestImageList(TestImage): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - marker=image_fakes.image_id, - ) + self.api_mock.image_list.assert_called_with() collist = ( 'ID', @@ -630,9 +623,7 @@ class TestImageList(TestImage): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - marker=image_fakes.image_id, - ) + self.api_mock.image_list.assert_called_with() sf_mock.assert_called_with( [image_fakes.IMAGE], attr='a', @@ -655,9 +646,7 @@ class TestImageList(TestImage): # DisplayCommandBase.take_action() returns two tuples columns, data = self.cmd.take_action(parsed_args) - self.api_mock.image_list.assert_called_with( - marker=image_fakes.image_id, - ) + self.api_mock.image_list.assert_called_with() si_mock.assert_called_with( [image_fakes.IMAGE], 'name:asc' @@ -666,6 +655,44 @@ class TestImageList(TestImage): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) + def test_image_list_limit_option(self): + arglist = [ + '--limit', str(1), + ] + verifylist = [ + ('limit', 1), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.api_mock.image_list.assert_called_with( + limit=1, + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(len(self.datalist), len(tuple(data))) + + @mock.patch('openstackclient.common.utils.find_resource') + def test_image_list_marker_option(self, fr_mock): + # tangchen: Since image_fakes.IMAGE is a dict, it cannot offer a .id + # operation. Will fix this by using FakeImage class instead + # of IMAGE dict. + fr_mock.return_value = mock.Mock() + fr_mock.return_value.id = image_fakes.image_id + + arglist = [ + '--marker', image_fakes.image_name, + ] + verifylist = [ + ('marker', image_fakes.image_name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.api_mock.image_list.assert_called_with( + marker=image_fakes.image_id, + ) + class TestRemoveProjectImage(TestImage): diff --git a/openstackclient/tests/network/test_common.py b/openstackclient/tests/network/test_common.py new file mode 100644 index 00000000..a3396b9d --- /dev/null +++ b/openstackclient/tests/network/test_common.py @@ -0,0 +1,103 @@ +# 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 argparse +import mock + +from openstackclient.network import common +from openstackclient.tests import utils + + +class FakeNetworkAndComputeCommand(common.NetworkAndComputeCommand): + def update_parser_common(self, parser): + parser.add_argument( + 'common', + metavar='<common>', + help='Common argument', + ) + return parser + + def update_parser_network(self, parser): + parser.add_argument( + 'network', + metavar='<network>', + help='Network argument', + ) + return parser + + def update_parser_compute(self, parser): + parser.add_argument( + 'compute', + metavar='<compute>', + help='Compute argument', + ) + return parser + + def take_action_network(self, client, parsed_args): + client.network_action(parsed_args) + return 'take_action_network' + + def take_action_compute(self, client, parsed_args): + client.compute_action(parsed_args) + return 'take_action_compute' + + +class TestNetworkAndComputeCommand(utils.TestCommand): + def setUp(self): + super(TestNetworkAndComputeCommand, self).setUp() + + self.namespace = argparse.Namespace() + + # Create network client mocks. + self.app.client_manager.network = mock.Mock() + self.network = self.app.client_manager.network + self.network.network_action = mock.Mock(return_value=None) + + # Create compute client mocks. + self.app.client_manager.compute = mock.Mock() + self.compute = self.app.client_manager.compute + self.compute.compute_action = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = FakeNetworkAndComputeCommand(self.app, self.namespace) + + def test_take_action_network(self): + arglist = [ + 'common', + 'network' + ] + verifylist = [ + ('common', 'common'), + ('network', 'network') + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.network.network_action.assert_called_with(parsed_args) + self.assertEqual('take_action_network', result) + + def test_take_action_compute(self): + arglist = [ + 'common', + 'compute' + ] + verifylist = [ + ('common', 'common'), + ('compute', 'compute') + ] + + self.app.client_manager.network_endpoint_enabled = False + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.compute.compute_action.assert_called_with(parsed_args) + self.assertEqual('take_action_compute', result) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 4c862bd3..516995eb 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -69,6 +69,59 @@ class TestNetworkV2(utils.TestCommand): ) +class FakeAvailabilityZone(object): + """Fake one or more network availability zones (AZs).""" + + @staticmethod + def create_one_availability_zone(attrs={}, methods={}): + """Create a fake AZ. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object with name, state, etc. + """ + # Set default attributes. + availability_zone = { + 'name': uuid.uuid4().hex, + 'state': 'available', + 'resource': 'network', + } + + # Overwrite default attributes. + availability_zone.update(attrs) + + availability_zone = fakes.FakeResource( + info=copy.deepcopy(availability_zone), + methods=methods, + loaded=True) + return availability_zone + + @staticmethod + def create_availability_zones(attrs={}, methods={}, count=2): + """Create multiple fake AZs. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of AZs to fake + :return: + A list of FakeResource objects faking the AZs + """ + availability_zones = [] + for i in range(0, count): + availability_zone = \ + FakeAvailabilityZone.create_one_availability_zone( + attrs, methods) + availability_zones.append(availability_zone) + + return availability_zones + + class FakeNetwork(object): """Fake one or more networks.""" @@ -96,7 +149,6 @@ class FakeNetwork(object): 'subnets': ['a', 'b'], 'provider_network_type': 'vlan', 'router_external': True, - 'is_dirty': True, 'availability_zones': [], 'availability_zone_hints': [], } @@ -172,15 +224,31 @@ class FakePort(object): :param Dictionary methods: A dictionary with all methods :return: - A FakeResource object, with id, name, admin_state_up, - status, tenant_id + A FakeResource object, with id, name, etc. """ + # Set default attributes. port_attrs = { + 'admin_state_up': True, + 'allowed_address_pairs': [{}], + 'binding:host_id': 'binding-host-id-' + uuid.uuid4().hex, + 'binding:profile': {}, + 'binding:vif_details': {}, + 'binding:vif_type': 'ovs', + 'binding:vnic_type': 'normal', + 'device_id': 'device-id-' + uuid.uuid4().hex, + 'device_owner': 'compute:nova', + 'dns_assignment': [{}], + 'dns_name': 'dns-name-' + uuid.uuid4().hex, + 'extra_dhcp_opts': [{}], + 'fixed_ips': [{}], 'id': 'port-id-' + uuid.uuid4().hex, + 'mac_address': 'fa:16:3e:a9:4e:72', 'name': 'port-name-' + uuid.uuid4().hex, + 'network_id': 'network-id-' + uuid.uuid4().hex, + 'port_security_enabled': True, + 'security_groups': [], 'status': 'ACTIVE', - 'admin_state_up': True, 'tenant_id': 'project-id-' + uuid.uuid4().hex, } @@ -188,7 +256,16 @@ class FakePort(object): port_attrs.update(attrs) # Set default methods. - port_methods = {} + port_methods = { + 'keys': ['admin_state_up', 'allowed_address_pairs', + 'binding:host_id', 'binding:profile', + 'binding:vif_details', 'binding:vif_type', + 'binding:vnic_type', 'device_id', 'device_owner', + 'dns_assignment', 'dns_name', 'extra_dhcp_opts', + 'fixed_ips', 'id', 'mac_address', 'name', + 'network_id', 'port_security_enabled', + 'security_groups', 'status', 'tenant_id'], + } # Overwrite default methods. port_methods.update(methods) @@ -196,6 +273,15 @@ class FakePort(object): port = fakes.FakeResource(info=copy.deepcopy(port_attrs), methods=copy.deepcopy(port_methods), loaded=True) + + # Set attributes with special mappings. + port.project_id = port_attrs['tenant_id'] + port.binding_host_id = port_attrs['binding:host_id'] + port.binding_profile = port_attrs['binding:profile'] + port.binding_vif_details = port_attrs['binding:vif_details'] + port.binding_vif_type = port_attrs['binding:vif_type'] + port.binding_vnic_type = port_attrs['binding:vnic_type'] + return port @staticmethod @@ -263,6 +349,8 @@ class FakeRouter(object): 'tenant_id': 'project-id-' + uuid.uuid4().hex, 'routes': [], 'external_gateway_info': {}, + 'availability_zone_hints': [], + 'availability_zones': [], } # Overwrite default attributes. @@ -321,6 +409,84 @@ class FakeRouter(object): return mock.MagicMock(side_effect=routers) +class FakeSecurityGroup(object): + """Fake one or more security groups.""" + + @staticmethod + def create_one_security_group(attrs={}, methods={}): + """Create a fake security group. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :return: + A FakeResource object, with id, name, etc. + """ + # Set default attributes. + security_group_attrs = { + 'id': 'security-group-id-' + uuid.uuid4().hex, + 'name': 'security-group-name-' + uuid.uuid4().hex, + 'description': 'security-group-description-' + uuid.uuid4().hex, + 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'security_group_rules': [], + } + + # Overwrite default attributes. + security_group_attrs.update(attrs) + + # Set default methods. + security_group_methods = {} + + # Overwrite default methods. + security_group_methods.update(methods) + + security_group = fakes.FakeResource( + info=copy.deepcopy(security_group_attrs), + methods=copy.deepcopy(security_group_methods), + loaded=True) + return security_group + + @staticmethod + def create_security_groups(attrs={}, methods={}, count=2): + """Create multiple fake security groups. + + :param Dictionary attrs: + A dictionary with all attributes + :param Dictionary methods: + A dictionary with all methods + :param int count: + The number of security groups to fake + :return: + A list of FakeResource objects faking the security groups + """ + security_groups = [] + for i in range(0, count): + security_groups.append( + FakeRouter.create_one_security_group(attrs, methods)) + + return security_groups + + @staticmethod + def get_security_groups(security_groups=None, count=2): + """Get an iterable MagicMock object with a list of faked security groups. + + If security group list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List security groups: + A list of FakeResource objects faking security groups + :param int count: + The number of security groups to fake + :return: + An iterable Mock object with side_effect set to a list of faked + security groups + """ + if security_groups is None: + security_groups = FakeRouter.create_security_groups(count) + return mock.MagicMock(side_effect=security_groups) + + class FakeSubnet(object): """Fake one or more subnets.""" diff --git a/openstackclient/tests/network/v2/test_network.py b/openstackclient/tests/network/v2/test_network.py index 37cc6674..f96497a4 100644 --- a/openstackclient/tests/network/v2/test_network.py +++ b/openstackclient/tests/network/v2/test_network.py @@ -440,8 +440,6 @@ class TestSetNetwork(TestNetwork): self.cmd = network.SetNetwork(self.app, self.namespace) def test_set_this(self): - self._network.is_dirty = True - arglist = [ self._network.name, '--enable', @@ -458,12 +456,15 @@ class TestSetNetwork(TestNetwork): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.update_network.assert_called_with(self._network) + attrs = { + 'name': 'noob', + 'admin_state_up': True, + 'shared': True, + } + self.network.update_network.assert_called_with(self._network, **attrs) self.assertIsNone(result) def test_set_that(self): - self._network.is_dirty = True - arglist = [ self._network.name, '--disable', @@ -478,12 +479,14 @@ class TestSetNetwork(TestNetwork): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network.update_network.assert_called_with(self._network) + attrs = { + 'admin_state_up': False, + 'shared': False, + } + self.network.update_network.assert_called_with(self._network, **attrs) self.assertIsNone(result) def test_set_nothing(self): - self._network.is_dirty = False - arglist = [self._network.name, ] verifylist = [('identifier', self._network.name), ] diff --git a/openstackclient/tests/network/v2/test_port.py b/openstackclient/tests/network/v2/test_port.py index a1ddefa1..bc246bd8 100644 --- a/openstackclient/tests/network/v2/test_port.py +++ b/openstackclient/tests/network/v2/test_port.py @@ -13,8 +13,10 @@ import mock +from openstackclient.common import utils from openstackclient.network.v2 import port from openstackclient.tests.network.v2 import fakes as network_fakes +from openstackclient.tests import utils as tests_utils class TestPort(network_fakes.TestNetworkV2): @@ -51,3 +53,88 @@ class TestDeletePort(TestPort): result = self.cmd.take_action(parsed_args) self.network.delete_port.assert_called_with(self._port) self.assertIsNone(result) + + +class TestShowPort(TestPort): + + # The port to show. + _port = network_fakes.FakePort.create_one_port() + + columns = ( + 'admin_state_up', + 'allowed_address_pairs', + 'binding_host_id', + 'binding_profile', + 'binding_vif_details', + 'binding_vif_type', + 'binding_vnic_type', + 'device_id', + 'device_owner', + 'dns_assignment', + 'dns_name', + 'extra_dhcp_opts', + 'fixed_ips', + 'id', + 'mac_address', + 'name', + 'network_id', + 'port_security_enabled', + 'project_id', + 'security_groups', + 'status', + ) + + data = ( + port._format_admin_state(_port.admin_state_up), + utils.format_list_of_dicts(_port.allowed_address_pairs), + _port.binding_host_id, + utils.format_dict(_port.binding_profile), + utils.format_dict(_port.binding_vif_details), + _port.binding_vif_type, + _port.binding_vnic_type, + _port.device_id, + _port.device_owner, + utils.format_list_of_dicts(_port.dns_assignment), + _port.dns_name, + utils.format_list_of_dicts(_port.extra_dhcp_opts), + utils.format_list_of_dicts(_port.fixed_ips), + _port.id, + _port.mac_address, + _port.name, + _port.network_id, + _port.port_security_enabled, + _port.project_id, + utils.format_list(_port.security_groups), + _port.status, + ) + + def setUp(self): + super(TestShowPort, self).setUp() + + self.network.find_port = mock.Mock(return_value=self._port) + + # Get the command object to test + self.cmd = port.ShowPort(self.app, self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self._port.name, + ] + verifylist = [ + ('port', self._port.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_port.assert_called_with(self._port.name, + ignore_missing=False) + self.assertEqual(tuple(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 fba6e192..98e9f17a 100644 --- a/openstackclient/tests/network/v2/test_router.py +++ b/openstackclient/tests/network/v2/test_router.py @@ -14,6 +14,7 @@ import mock from openstackclient.common import exceptions +from openstackclient.common import utils as osc_utils from openstackclient.network.v2 import router from openstackclient.tests.network.v2 import fakes as network_fakes from openstackclient.tests import utils as tests_utils @@ -88,6 +89,31 @@ class TestCreateRouter(TestRouter): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def test_create_with_AZ_hints(self): + arglist = [ + self.new_router.name, + '--availability-zone-hint', 'fake-az', + '--availability-zone-hint', 'fake-az2', + ] + verifylist = [ + ('name', self.new_router.name), + ('availability_zone_hints', ['fake-az', 'fake-az2']), + ('admin_state_up', True), + ('distributed', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + self.network.create_router.assert_called_with(**{ + 'admin_state_up': True, + 'name': self.new_router.name, + 'distributed': False, + 'availability_zone_hints': ['fake-az', 'fake-az2'], + }) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + class TestDeleteRouter(TestRouter): @@ -135,6 +161,7 @@ class TestListRouter(TestRouter): columns_long = columns + ( 'Routes', 'External gateway info', + 'Availability zones' ) data = [] @@ -155,6 +182,7 @@ class TestListRouter(TestRouter): data[i] + ( r.routes, router._format_external_gateway_info(r.external_gateway_info), + osc_utils.format_list(r.availability_zones), ) ) diff --git a/openstackclient/tests/network/v2/test_security_group.py b/openstackclient/tests/network/v2/test_security_group.py new file mode 100644 index 00000000..98388ec7 --- /dev/null +++ b/openstackclient/tests/network/v2/test_security_group.py @@ -0,0 +1,99 @@ +# 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.network.v2 import security_group +from openstackclient.tests.network.v2 import fakes as network_fakes + + +class TestSecurityGroup(network_fakes.TestNetworkV2): + + def setUp(self): + super(TestSecurityGroup, self).setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + + # Create compute client mocks. + self.app.client_manager.compute = mock.Mock() + self.compute = self.app.client_manager.compute + self.compute.security_groups = mock.Mock() + + +class TestDeleteSecurityGroupNetwork(TestSecurityGroup): + + # The security group to be deleted. + _security_group = \ + network_fakes.FakeSecurityGroup.create_one_security_group() + + def setUp(self): + super(TestDeleteSecurityGroupNetwork, self).setUp() + + self.network.delete_security_group = mock.Mock(return_value=None) + + self.network.find_security_group = mock.Mock( + return_value=self._security_group) + + # Get the command object to test + self.cmd = security_group.DeleteSecurityGroup(self.app, self.namespace) + + def test_security_group_delete(self): + arglist = [ + self._security_group.name, + ] + verifylist = [ + ('group', self._security_group.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.delete_security_group.assert_called_with( + self._security_group) + self.assertEqual(None, result) + + +class TestDeleteSecurityGroupCompute(TestSecurityGroup): + + # The security group to be deleted. + _security_group = \ + network_fakes.FakeSecurityGroup.create_one_security_group() + + def setUp(self): + super(TestDeleteSecurityGroupCompute, self).setUp() + + self.app.client_manager.network_endpoint_enabled = False + + self.compute.security_groups.delete = mock.Mock(return_value=None) + + self.compute.security_groups.get = mock.Mock( + return_value=self._security_group) + + # Get the command object to test + self.cmd = security_group.DeleteSecurityGroup(self.app, self.namespace) + + def test_security_group_delete(self): + arglist = [ + self._security_group.name, + ] + verifylist = [ + ('group', self._security_group.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.compute.security_groups.delete.assert_called_with( + self._security_group.id) + self.assertEqual(None, result) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index 2e58e58d..2fc5c8ff 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -256,9 +256,10 @@ class FakeVolume(object): 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex}, 'snapshot_id': random.randint(1, 5), 'availability_zone': 'zone' + uuid.uuid4().hex, - 'attachments': { + 'attachments': [{ 'device': '/dev/' + uuid.uuid4().hex, - 'server_id': uuid.uuid4().hex}, + 'server_id': uuid.uuid4().hex, + }, ], } # Overwrite default attributes if there are some attributes set diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 4c583abd..76c7a27a 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -362,6 +362,47 @@ 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 = [ + ("volumes", [volumes[0].id]) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + self.volumes_mock.delete.assert_called_with(volumes[0].id) + + def test_volume_delete_multi_volumes(self): + volumes = self.setup_volumes_mock(count=3) + + arglist = [v.id for v in volumes] + verifylist = [ + ('volumes', arglist), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + calls = [call(v.id) for v in volumes] + + self.volumes_mock.delete.assert_has_calls(calls) + + class TestVolumeList(TestVolume): columns = [ @@ -695,44 +736,3 @@ class TestVolumeShow(TestVolume): self.assertEqual(volume_fakes.VOLUME_columns, columns) self.assertEqual(volume_fakes.VOLUME_data, 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 = [ - ("volumes", [volumes[0].id]) - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.cmd.take_action(parsed_args) - self.volumes_mock.delete.assert_called_with(volumes[0].id) - - def test_volume_delete_multi_volumes(self): - volumes = self.setup_volumes_mock(count=3) - - arglist = [v.id for v in volumes] - verifylist = [ - ('volumes', arglist), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.cmd.take_action(parsed_args) - - calls = [call(v.id) for v in volumes] - - self.volumes_mock.delete.assert_has_calls(calls) |
