diff options
Diffstat (limited to 'openstackclient')
| -rw-r--r-- | openstackclient/compute/v2/server.py | 9 | ||||
| -rw-r--r-- | openstackclient/compute/v2/server_backup.py | 134 | ||||
| -rw-r--r-- | openstackclient/compute/v2/server_group.py | 2 | ||||
| -rw-r--r-- | openstackclient/image/v2/image.py | 89 | ||||
| -rw-r--r-- | openstackclient/network/v2/address_scope.py | 24 | ||||
| -rw-r--r-- | openstackclient/tests/compute/v2/test_server.py | 61 | ||||
| -rw-r--r-- | openstackclient/tests/compute/v2/test_server_backup.py | 270 | ||||
| -rw-r--r-- | openstackclient/tests/fakes.py | 3 | ||||
| -rw-r--r-- | openstackclient/tests/image/v2/fakes.py | 2 | ||||
| -rw-r--r-- | openstackclient/tests/image/v2/test_image.py | 93 | ||||
| -rw-r--r-- | openstackclient/tests/network/v2/fakes.py | 19 | ||||
| -rw-r--r-- | openstackclient/tests/network/v2/test_address_scope.py | 70 | ||||
| -rw-r--r-- | openstackclient/volume/v2/volume_type.py | 2 |
13 files changed, 762 insertions, 16 deletions
diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 781ccb1b..2312575a 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1370,6 +1370,12 @@ class SetServer(command.Command): help=_('Property to add/change for this server ' '(repeat option to set multiple properties)'), ) + parser.add_argument( + '--state', + metavar='<state>', + choices=['active', 'error'], + help=_('New server state (valid value: active, error)'), + ) return parser def take_action(self, parsed_args): @@ -1389,6 +1395,9 @@ class SetServer(command.Command): parsed_args.property, ) + if parsed_args.state: + server.reset_state(state=parsed_args.state) + if parsed_args.root_password: p1 = getpass.getpass(_('New password: ')) p2 = getpass.getpass(_('Retype new password: ')) diff --git a/openstackclient/compute/v2/server_backup.py b/openstackclient/compute/v2/server_backup.py new file mode 100644 index 00000000..24d71015 --- /dev/null +++ b/openstackclient/compute/v2/server_backup.py @@ -0,0 +1,134 @@ +# Copyright 2012-2013 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Compute v2 Server action implementations""" + +import sys + +from oslo_utils import importutils +import six + +from openstackclient.common import command +from openstackclient.common import exceptions +from openstackclient.common import utils +from openstackclient.i18n import _ + + +def _show_progress(progress): + if progress: + sys.stderr.write('\rProgress: %s' % progress) + sys.stderr.flush() + + +class CreateServerBackup(command.ShowOne): + """Create a server backup image""" + + IMAGE_API_VERSIONS = { + "1": "openstackclient.image.v1.image", + "2": "openstackclient.image.v2.image", + } + + def get_parser(self, prog_name): + parser = super(CreateServerBackup, self).get_parser(prog_name) + parser.add_argument( + 'server', + metavar='<server>', + help=_('Server to back up (name or ID)'), + ) + parser.add_argument( + '--name', + metavar='<image-name>', + help=_('Name of the backup image (default: server name)'), + ) + parser.add_argument( + '--type', + metavar='<backup-type>', + help=_( + 'Used to populate the backup_type property of the backup ' + 'image (default: empty)' + ), + ) + parser.add_argument( + '--rotate', + metavar='<count>', + type=int, + help=_('Number of backups to keep (default: 1)'), + ) + parser.add_argument( + '--wait', + action='store_true', + help=_('Wait for backup image create to complete'), + ) + return parser + + def take_action(self, parsed_args): + compute_client = self.app.client_manager.compute + + server = utils.find_resource( + compute_client.servers, + parsed_args.server, + ) + + # Set sane defaults as this API wants all mouths to be fed + if parsed_args.name is None: + backup_name = server.name + else: + backup_name = parsed_args.name + if parsed_args.type is None: + backup_type = "" + else: + backup_type = parsed_args.type + if parsed_args.rotate is None: + backup_rotation = 1 + else: + backup_rotation = parsed_args.rotate + + compute_client.servers.backup( + server.id, + backup_name, + backup_type, + backup_rotation, + ) + + image_client = self.app.client_manager.image + image = utils.find_resource( + image_client.images, + backup_name, + ) + + if parsed_args.wait: + if utils.wait_for_status( + image_client.images.get, + image.id, + callback=_show_progress, + ): + sys.stdout.write('\n') + else: + msg = _('Error creating server backup: %s') % parsed_args.name + raise exceptions.CommandError(msg) + + if self.app.client_manager._api_version['image'] == '1': + info = {} + info.update(image._info) + info['properties'] = utils.format_dict(info.get('properties', {})) + else: + # Get the right image module to format the output + image_module = importutils.import_module( + self.IMAGE_API_VERSIONS[ + self.app.client_manager._api_version['image'] + ] + ) + info = image_module._format_image(image) + return zip(*sorted(six.iteritems(info))) diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 973095ca..7baa6fe7 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -71,7 +71,7 @@ class CreateServerGroup(command.ShowOne): class DeleteServerGroup(command.Command): - """Delete an existing server group.""" + """Delete existing server group(s).""" def get_parser(self, prog_name): parser = super(DeleteServerGroup, self).get_parser(prog_name) diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index a9c0f1fd..a81f092c 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -805,8 +805,8 @@ class SetImage(command.Command): # Checks if anything that requires getting the image if not (kwargs or parsed_args.deactivate or parsed_args.activate): - self.log.warning(_("No arguments specified")) - return {}, {} + msg = _("No arguments specified") + raise exceptions.CommandError(msg) image = utils.find_resource( image_client.images, parsed_args.image) @@ -856,3 +856,88 @@ class ShowImage(command.ShowOne): info = _format_image(image) return zip(*sorted(six.iteritems(info))) + + +class UnsetImage(command.Command): + """Unset image tags and properties""" + + def get_parser(self, prog_name): + parser = super(UnsetImage, self).get_parser(prog_name) + parser.add_argument( + "image", + metavar="<image>", + help=_("Image to modify (name or ID)"), + ) + parser.add_argument( + "--tag", + dest="tags", + metavar="<tag>", + default=[], + action='append', + help=_("Unset a tag on this image " + "(repeat option to set multiple tags)"), + ) + parser.add_argument( + "--property", + dest="properties", + metavar="<property_key>", + default=[], + action='append', + help=_("Unset a property on this image " + "(repeat option to set multiple properties)"), + ) + return parser + + def take_action(self, parsed_args): + image_client = self.app.client_manager.image + image = utils.find_resource( + image_client.images, + parsed_args.image, + ) + + if not (parsed_args.tags or parsed_args.properties): + msg = _("No arguments specified") + raise exceptions.CommandError(msg) + + kwargs = {} + tagret = 0 + propret = 0 + if parsed_args.tags: + for k in parsed_args.tags: + try: + image_client.image_tags.delete(image.id, k) + except Exception: + self.log.error(_("tag unset failed," + " '%s' is a nonexistent tag ") % k) + tagret += 1 + + if parsed_args.properties: + for k in parsed_args.properties: + try: + assert(k in image.keys()) + except AssertionError: + self.log.error(_("property unset failed," + " '%s' is a nonexistent property ") % k) + propret += 1 + image_client.images.update( + image.id, + parsed_args.properties, + **kwargs) + + tagtotal = len(parsed_args.tags) + proptotal = len(parsed_args.properties) + if (tagret > 0 and propret > 0): + msg = (_("Failed to unset %(tagret)s of %(tagtotal)s tags," + "Failed to unset %(propret)s of %(proptotal)s properties.") + % {'tagret': tagret, 'tagtotal': tagtotal, + 'propret': propret, 'proptotal': proptotal}) + raise exceptions.CommandError(msg) + elif tagret > 0: + msg = (_("Failed to unset %(target)s of %(tagtotal)s tags.") + % {'tagret': tagret, 'tagtotal': tagtotal}) + raise exceptions.CommandError(msg) + elif propret > 0: + msg = (_("Failed to unset %(propret)s of %(proptotal)s" + " properties.") + % {'propret': propret, 'proptotal': proptotal}) + raise exceptions.CommandError(msg) diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 614900c9..dbc6865f 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -98,22 +98,38 @@ class CreateAddressScope(command.ShowOne): class DeleteAddressScope(command.Command): - """Delete an address scope""" + """Delete address scope(s)""" def get_parser(self, prog_name): parser = super(DeleteAddressScope, self).get_parser(prog_name) parser.add_argument( 'address_scope', metavar="<address-scope>", - help=_("Address scope to delete (name or ID)") + nargs='+', + help=_("Address scope(s) to delete (name or ID)") ) return parser def take_action(self, parsed_args): client = self.app.client_manager.network - obj = client.find_address_scope(parsed_args.address_scope) - client.delete_address_scope(obj) + result = 0 + + for scope in parsed_args.address_scope: + try: + obj = client.find_address_scope(scope, ignore_missing=False) + client.delete_address_scope(obj) + except Exception as e: + result += 1 + self.app.log.error(_("Failed to delete address scope with " + "name or ID '%(scope)s': %(e)s") + % {'scope': scope, 'e': e}) + + if result > 0: + total = len(parsed_args.address_scope) + msg = (_("%(result)s of %(total)s address scopes failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) class ListAddressScope(command.Lister): diff --git a/openstackclient/tests/compute/v2/test_server.py b/openstackclient/tests/compute/v2/test_server.py index 7d184b3a..2dfdb68a 100644 --- a/openstackclient/tests/compute/v2/test_server.py +++ b/openstackclient/tests/compute/v2/test_server.py @@ -1213,6 +1213,67 @@ class TestServerResume(TestServer): self.run_method_with_servers('resume', 3) +class TestServerSet(TestServer): + + def setUp(self): + super(TestServerSet, self).setUp() + + self.methods = { + 'update': None, + 'reset_state': None, + 'change_password': None, + } + + self.fake_servers = self.setup_servers_mock(2) + + # Get the command object to test + self.cmd = server.SetServer(self.app, None) + + def test_server_set_no_option(self): + arglist = [ + 'foo_vm' + ] + verifylist = [ + ('server', 'foo_vm') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.assertNotCalled(self.fake_servers[0].update) + self.assertNotCalled(self.fake_servers[0].reset_state) + self.assertNotCalled(self.fake_servers[0].change_password) + self.assertNotCalled(self.servers_mock.set_meta) + self.assertIsNone(result) + + def test_server_set_with_state(self): + for index, state in enumerate(['active', 'error']): + arglist = [ + '--state', state, + 'foo_vm', + ] + verifylist = [ + ('state', state), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + self.fake_servers[index].reset_state.assert_called_once_with( + state=state) + self.assertIsNone(result) + + def test_server_set_with_invalid_state(self): + arglist = [ + '--state', 'foo_state', + 'foo_vm', + ] + verifylist = [ + ('state', 'foo_state'), + ('server', 'foo_vm'), + ] + self.assertRaises(utils.ParserException, + self.check_parser, + self.cmd, arglist, verifylist) + + class TestServerShelve(TestServer): def setUp(self): diff --git a/openstackclient/tests/compute/v2/test_server_backup.py b/openstackclient/tests/compute/v2/test_server_backup.py new file mode 100644 index 00000000..b35f9f52 --- /dev/null +++ b/openstackclient/tests/compute/v2/test_server_backup.py @@ -0,0 +1,270 @@ +# 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 as common_utils +from openstackclient.compute.v2 import server_backup +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests.image.v2 import fakes as image_fakes + + +class TestServerBackup(compute_fakes.TestComputev2): + + def setUp(self): + super(TestServerBackup, self).setUp() + + # Get a shortcut to the compute client ServerManager Mock + self.servers_mock = self.app.client_manager.compute.servers + self.servers_mock.reset_mock() + + # Get a shortcut to the image client ImageManager Mock + self.images_mock = self.app.client_manager.image.images + self.images_mock.reset_mock() + + # Set object attributes to be tested. Could be overwriten in subclass. + self.attrs = {} + + # Set object methods to be tested. Could be overwriten in subclass. + self.methods = {} + + def setup_servers_mock(self, count): + servers = compute_fakes.FakeServer.create_servers( + attrs=self.attrs, + methods=self.methods, + count=count, + ) + + # This is the return value for utils.find_resource() + self.servers_mock.get = compute_fakes.FakeServer.get_servers( + servers, + 0, + ) + return servers + + +class TestServerBackupCreate(TestServerBackup): + + # Just return whatever Image is testing with these days + def image_columns(self, image): + columnlist = tuple(sorted(image.keys())) + return columnlist + + def image_data(self, image): + datalist = ( + image['id'], + image['name'], + image['owner'], + image['protected'], + 'active', + common_utils.format_list(image.get('tags')), + image['visibility'], + ) + return datalist + + def setUp(self): + super(TestServerBackupCreate, self).setUp() + + # Get the command object to test + self.cmd = server_backup.CreateServerBackup(self.app, None) + + self.methods = { + 'backup': None, + } + + def setup_images_mock(self, count, servers=None): + if servers: + images = image_fakes.FakeImage.create_images( + attrs={ + 'name': servers[0].name, + 'status': 'active', + }, + count=count, + ) + else: + images = image_fakes.FakeImage.create_images( + attrs={ + 'status': 'active', + }, + count=count, + ) + + self.images_mock.get = mock.MagicMock(side_effect=images) + return images + + def test_server_backup_defaults(self): + servers = self.setup_servers_mock(count=1) + images = self.setup_images_mock(count=1, servers=servers) + + arglist = [ + servers[0].id, + ] + verifylist = [ + ('name', None), + ('type', None), + ('rotate', None), + ('wait', False), + ('server', servers[0].id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns a two-part tuple with a tuple of column names and a tuple of + # data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + # ServerManager.backup(server, backup_name, backup_type, rotation) + self.servers_mock.backup.assert_called_with( + servers[0].id, + servers[0].name, + '', + 1, + ) + + self.assertEqual(self.image_columns(images[0]), columns) + self.assertEqual(self.image_data(images[0]), data) + + def test_server_backup_create_options(self): + servers = self.setup_servers_mock(count=1) + images = self.setup_images_mock(count=1, servers=servers) + + arglist = [ + '--name', 'image', + '--type', 'daily', + '--rotate', '2', + servers[0].id, + ] + verifylist = [ + ('name', 'image'), + ('type', 'daily'), + ('rotate', 2), + ('server', servers[0].id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns a two-part tuple with a tuple of column names and a tuple of + # data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + # ServerManager.backup(server, backup_name, backup_type, rotation) + self.servers_mock.backup.assert_called_with( + servers[0].id, + 'image', + 'daily', + 2, + ) + + self.assertEqual(self.image_columns(images[0]), columns) + self.assertEqual(self.image_data(images[0]), data) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=False) + def test_server_backup_wait_fail(self, mock_wait_for_status): + servers = self.setup_servers_mock(count=1) + images = image_fakes.FakeImage.create_images( + attrs={ + 'name': servers[0].name, + 'status': 'active', + }, + count=5, + ) + + self.images_mock.get = mock.MagicMock( + side_effect=images, + ) + + arglist = [ + '--name', 'image', + '--type', 'daily', + '--wait', + servers[0].id, + ] + verifylist = [ + ('name', 'image'), + ('type', 'daily'), + ('wait', True), + ('server', servers[0].id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + + # ServerManager.backup(server, backup_name, backup_type, rotation) + self.servers_mock.backup.assert_called_with( + servers[0].id, + 'image', + 'daily', + 1, + ) + + mock_wait_for_status.assert_called_once_with( + self.images_mock.get, + images[0].id, + callback=mock.ANY + ) + + @mock.patch.object(common_utils, 'wait_for_status', return_value=True) + def test_server_backup_wait_ok(self, mock_wait_for_status): + servers = self.setup_servers_mock(count=1) + images = image_fakes.FakeImage.create_images( + attrs={ + 'name': servers[0].name, + 'status': 'active', + }, + count=5, + ) + + self.images_mock.get = mock.MagicMock( + side_effect=images, + ) + + arglist = [ + '--name', 'image', + '--type', 'daily', + '--wait', + servers[0].id, + ] + verifylist = [ + ('name', 'image'), + ('type', 'daily'), + ('wait', True), + ('server', servers[0].id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class ShowOne in cliff, abstract method take_action() + # returns a two-part tuple with a tuple of column names and a tuple of + # data to be shown. + columns, data = self.cmd.take_action(parsed_args) + + # ServerManager.backup(server, backup_name, backup_type, rotation) + self.servers_mock.backup.assert_called_with( + servers[0].id, + 'image', + 'daily', + 1, + ) + + mock_wait_for_status.assert_called_once_with( + self.images_mock.get, + images[0].id, + callback=mock.ANY + ) + + self.assertEqual(self.image_columns(images[0]), columns) + self.assertEqual(self.image_data(images[0]), data) diff --git a/openstackclient/tests/fakes.py b/openstackclient/tests/fakes.py index ac91257e..7fb1daa9 100644 --- a/openstackclient/tests/fakes.py +++ b/openstackclient/tests/fakes.py @@ -110,6 +110,9 @@ class FakeClient(object): class FakeClientManager(object): + _api_version = { + 'image': '2', + } def __init__(self): self.compute = None diff --git a/openstackclient/tests/image/v2/fakes.py b/openstackclient/tests/image/v2/fakes.py index a662a585..24aaec51 100644 --- a/openstackclient/tests/image/v2/fakes.py +++ b/openstackclient/tests/image/v2/fakes.py @@ -157,6 +157,8 @@ class FakeImagev2Client(object): self.images.resource_class = fakes.FakeResource(None, {}) self.image_members = mock.Mock() self.image_members.resource_class = fakes.FakeResource(None, {}) + self.image_tags = mock.Mock() + self.image_tags.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] diff --git a/openstackclient/tests/image/v2/test_image.py b/openstackclient/tests/image/v2/test_image.py index beebdef9..ca20d83d 100644 --- a/openstackclient/tests/image/v2/test_image.py +++ b/openstackclient/tests/image/v2/test_image.py @@ -37,6 +37,8 @@ class TestImage(image_fakes.TestImagev2): self.images_mock.reset_mock() self.image_members_mock = self.app.client_manager.image.image_members self.image_members_mock.reset_mock() + self.image_tags_mock = self.app.client_manager.image.image_tags + self.image_tags_mock.reset_mock() # Get shortcut to the Mocks in identity client self.project_mock = self.app.client_manager.identity.projects @@ -1184,3 +1186,94 @@ class TestImageShow(TestImage): self.assertEqual(image_fakes.IMAGE_columns, columns) self.assertEqual(image_fakes.IMAGE_SHOW_data, data) + + +class TestImageUnset(TestImage): + + attrs = {} + attrs['tags'] = ['test'] + attrs['prop'] = 'test' + image = image_fakes.FakeImage.create_one_image(attrs) + + def setUp(self): + super(TestImageUnset, self).setUp() + + # Set up the schema + self.model = warlock.model_factory( + image_fakes.IMAGE_schema, + schemas.SchemaBasedModel, + ) + + self.images_mock.get.return_value = self.image + self.image_tags_mock.delete.return_value = self.image + + # Get the command object to test + self.cmd = image.UnsetImage(self.app, None) + + def test_image_unset_tag_option(self): + + arglist = [ + '--tag', 'test', + self.image.id, + ] + + verifylist = [ + ('tags', ['test']), + ('image', self.image.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.image_tags_mock.delete.assert_called_with( + self.image.id, 'test' + ) + self.assertIsNone(result) + + def test_image_unset_property_option(self): + + arglist = [ + '--property', 'prop', + self.image.id, + ] + + verifylist = [ + ('properties', ['prop']), + ('image', self.image.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + kwargs = {} + self.images_mock.update.assert_called_with( + self.image.id, + parsed_args.properties, + **kwargs) + + self.assertIsNone(result) + + def test_image_unset_mixed_option(self): + + arglist = [ + '--tag', 'test', + '--property', 'prop', + self.image.id, + ] + + verifylist = [ + ('tags', ['test']), + ('properties', ['prop']), + ('image', self.image.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + kwargs = {} + self.images_mock.update.assert_called_with( + self.image.id, + parsed_args.properties, + **kwargs) + + self.image_tags_mock.delete.assert_called_with( + self.image.id, 'test' + ) + self.assertIsNone(result) diff --git a/openstackclient/tests/network/v2/fakes.py b/openstackclient/tests/network/v2/fakes.py index 8507e278..587fdc1a 100644 --- a/openstackclient/tests/network/v2/fakes.py +++ b/openstackclient/tests/network/v2/fakes.py @@ -127,6 +127,25 @@ class FakeAddressScope(object): return address_scopes + @staticmethod + def get_address_scopes(address_scopes=None, count=2): + """Get an iterable MagicMock object with a list of faked address scopes. + + If address scopes list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List address scopes: + A list of FakeResource objects faking address scopes + :param int count: + The number of address scopes to fake + :return: + An iterable Mock object with side_effect set to a list of faked + address scopes + """ + if address_scopes is None: + address_scopes = FakeAddressScope.create_address_scopes(count) + return mock.MagicMock(side_effect=address_scopes) + class FakeAvailabilityZone(object): """Fake one or more network availability zones (AZs).""" diff --git a/openstackclient/tests/network/v2/test_address_scope.py b/openstackclient/tests/network/v2/test_address_scope.py index ac94b489..b4f4fa88 100644 --- a/openstackclient/tests/network/v2/test_address_scope.py +++ b/openstackclient/tests/network/v2/test_address_scope.py @@ -14,6 +14,7 @@ import copy import mock +from mock import call from openstackclient.common import exceptions from openstackclient.network.v2 import address_scope from openstackclient.tests import fakes @@ -168,33 +169,86 @@ class TestCreateAddressScope(TestAddressScope): class TestDeleteAddressScope(TestAddressScope): # The address scope to delete. - _address_scope = ( - network_fakes.FakeAddressScope.create_one_address_scope()) + _address_scopes = ( + network_fakes.FakeAddressScope.create_address_scopes(count=2)) def setUp(self): super(TestDeleteAddressScope, self).setUp() self.network.delete_address_scope = mock.Mock(return_value=None) - self.network.find_address_scope = mock.Mock( - return_value=self._address_scope) + self.network.find_address_scope = ( + network_fakes.FakeAddressScope.get_address_scopes( + address_scopes=self._address_scopes) + ) # Get the command object to test self.cmd = address_scope.DeleteAddressScope(self.app, self.namespace) - def test_delete(self): + def test_address_scope_delete(self): arglist = [ - self._address_scope.name, + self._address_scopes[0].name, ] verifylist = [ - ('address_scope', self._address_scope.name), + ('address_scope', [self._address_scopes[0].name]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) + self.network.find_address_scope.assert_called_once_with( + self._address_scopes[0].name, ignore_missing=False) self.network.delete_address_scope.assert_called_once_with( - self._address_scope) + self._address_scopes[0]) self.assertIsNone(result) + def test_multi_address_scopes_delete(self): + arglist = [] + verifylist = [] + + for a in self._address_scopes: + arglist.append(a.name) + verifylist = [ + ('address_scope', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for a in self._address_scopes: + calls.append(call(a)) + self.network.delete_address_scope.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_address_scopes_delete_with_exception(self): + arglist = [ + self._address_scopes[0].name, + 'unexist_address_scope', + ] + verifylist = [ + ('address_scope', + [self._address_scopes[0].name, 'unexist_address_scope']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._address_scopes[0], exceptions.CommandError] + self.network.find_address_scope = ( + mock.MagicMock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 address scopes failed to delete.', str(e)) + + self.network.find_address_scope.assert_any_call( + self._address_scopes[0].name, ignore_missing=False) + self.network.find_address_scope.assert_any_call( + 'unexist_address_scope', ignore_missing=False) + self.network.delete_address_scope.assert_called_once_with( + self._address_scopes[0] + ) + class TestListAddressScope(TestAddressScope): diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index adaccb0a..2261f4c3 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -311,7 +311,7 @@ class UnsetVolumeType(command.Command): volume_type.id, project_info.id) except Exception as e: self.app.log.error(_("Failed to remove volume type access from" - " project: ") + str(e)) + " project: %s") % str(e)) result += 1 if result > 0: |
