summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/compute/v2/server.py9
-rw-r--r--openstackclient/compute/v2/server_backup.py134
-rw-r--r--openstackclient/compute/v2/server_group.py2
-rw-r--r--openstackclient/image/v2/image.py89
-rw-r--r--openstackclient/network/v2/address_scope.py24
-rw-r--r--openstackclient/tests/compute/v2/test_server.py61
-rw-r--r--openstackclient/tests/compute/v2/test_server_backup.py270
-rw-r--r--openstackclient/tests/fakes.py3
-rw-r--r--openstackclient/tests/image/v2/fakes.py2
-rw-r--r--openstackclient/tests/image/v2/test_image.py93
-rw-r--r--openstackclient/tests/network/v2/fakes.py19
-rw-r--r--openstackclient/tests/network/v2/test_address_scope.py70
-rw-r--r--openstackclient/volume/v2/volume_type.py2
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: