From 2f76bfa3a69b6867491a6c5f0bf3c7e9f62743ca Mon Sep 17 00:00:00 2001 From: tianhui Date: Fri, 18 May 2018 18:59:37 +0800 Subject: [PATCH] Compute: Add tags support for server Change-Id: If065602792958ff0145ae9f2e05f5b7a3177905c Story: 2002006 Task: 19641 --- openstackclient/compute/v2/server.py | 115 +++++++- .../tests/unit/compute/v2/test_server.py | 260 ++++++++++++++++++ .../server-add-tag-63f9cd01dbd82d1b.yaml | 14 + 3 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/server-add-tag-63f9cd01dbd82d1b.yaml diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 89ea006709..09842f8874 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -828,6 +828,18 @@ class CreateServer(command.ShowOne): action='store_true', help=_('Wait for build to complete'), ) + parser.add_argument( + '--tag', + metavar='', + action='append', + default=[], + dest='tags', + help=_( + 'Tags for the server. ' + 'Specify multiple times to add multiple tags. ' + '(supported by --os-compute-api-version 2.52 or above)' + ), + ) return parser def take_action(self, parsed_args): @@ -1141,6 +1153,16 @@ class CreateServer(command.ShowOne): if parsed_args.description: boot_kwargs['description'] = parsed_args.description + if parsed_args.tags: + if compute_client.api_version < api_versions.APIVersion('2.52'): + msg = _( + '--os-compute-api-version 2.52 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + boot_kwargs['tags'] = parsed_args.tags + if parsed_args.host: if compute_client.api_version < api_versions.APIVersion("2.74"): msg = _("Specifying --host is not supported for " @@ -1408,6 +1430,30 @@ class ListServer(command.Lister): help=_('Only display unlocked servers. ' 'Requires ``--os-compute-api-version`` 2.73 or greater.'), ) + parser.add_argument( + '--tags', + metavar='', + action='append', + default=[], + dest='tags', + help=_( + 'Only list servers with the specified tag. ' + 'Specify multiple times to filter on multiple tags. ' + '(supported by --os-compute-api-version 2.26 or above)' + ), + ) + parser.add_argument( + '--not-tags', + metavar='', + action='append', + default=[], + dest='not_tags', + help=_( + 'Only list servers without the specified tag. ' + 'Specify multiple times to filter on multiple tags. ' + '(supported by --os-compute-api-version 2.26 or above)' + ), + ) return parser def take_action(self, parsed_args): @@ -1463,6 +1509,27 @@ class ListServer(command.Lister): 'changes-before': parsed_args.changes_before, 'changes-since': parsed_args.changes_since, } + + if parsed_args.tags: + if compute_client.api_version < api_versions.APIVersion('2.26'): + msg = _( + '--os-compute-api-version 2.26 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + search_opts['tags'] = parsed_args.tags + + if parsed_args.not_tags: + if compute_client.api_version < api_versions.APIVersion('2.26'): + msg = _( + '--os-compute-api-version 2.26 or greater is required to ' + 'support the --not-tag option' + ) + raise exceptions.CommandError(msg) + + search_opts['not-tags'] = parsed_args.not_tags + support_locked = (compute_client.api_version >= api_versions.APIVersion('2.73')) if not support_locked and (parsed_args.locked or parsed_args.unlocked): @@ -2795,6 +2862,18 @@ class SetServer(command.Command): help=_('New server description (supported by ' '--os-compute-api-version 2.19 or above)'), ) + parser.add_argument( + '--tag', + metavar='', + action='append', + default=[], + dest='tags', + help=_( + 'Tag for the server. ' + 'Specify multiple times to add multiple tags. ' + '(supported by --os-compute-api-version 2.26 or above)' + ), + ) return parser def take_action(self, parsed_args): @@ -2833,6 +2912,17 @@ class SetServer(command.Command): raise exceptions.CommandError(msg) server.update(description=parsed_args.description) + if parsed_args.tags: + if server.api_version < api_versions.APIVersion('2.26'): + msg = _( + '--os-compute-api-version 2.26 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + for tag in parsed_args.tags: + server.add_tag(tag=tag) + class ShelveServer(command.Command): _description = _("Shelve server(s)") @@ -3174,7 +3264,7 @@ class UnrescueServer(command.Command): class UnsetServer(command.Command): - _description = _("Unset server properties") + _description = _("Unset server properties and tags") def get_parser(self, prog_name): parser = super(UnsetServer, self).get_parser(prog_name) @@ -3198,6 +3288,18 @@ class UnsetServer(command.Command): help=_('Unset server description (supported by ' '--os-compute-api-version 2.19 or above)'), ) + parser.add_argument( + '--tag', + metavar='', + action='append', + default=[], + dest='tags', + help=_( + 'Tag to remove from the server. ' + 'Specify multiple times to remove multiple tags. ' + '(supported by --os-compute-api-version 2.26 or later' + ), + ) return parser def take_action(self, parsed_args): @@ -3223,6 +3325,17 @@ class UnsetServer(command.Command): description="", ) + if parsed_args.tags: + if compute_client.api_version < api_versions.APIVersion('2.26'): + msg = _( + '--os-compute-api-version 2.26 or greater is required to ' + 'support the --tag option' + ) + raise exceptions.CommandError(msg) + + for tag in parsed_args.tags: + compute_client.servers.delete_tag(server, tag=tag) + class UnshelveServer(command.Command): _description = _("Unshelve server(s)") diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 5f1d5d0620..544e013740 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2434,6 +2434,87 @@ class TestServerCreate(TestServer): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_server_create_with_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.52') + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--tag', 'tag1', + '--tag', 'tag2', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('tags', ['tag1', 'tag2']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'meta': None, + 'files': {}, + 'reservation_id': None, + 'min_count': 1, + 'max_count': 1, + 'security_groups': [], + 'userdata': None, + 'key_name': None, + 'availability_zone': None, + 'block_device_mapping_v2': [], + 'admin_pass': None, + 'nics': 'auto', + 'scheduler_hints': {}, + 'config_drive': None, + 'tags': ['tag1', 'tag2'], + } + # ServerManager.create(name, image, flavor, **kwargs) + self.servers_mock.create.assert_called_with( + self.new_server.name, + self.image, + self.flavor, + **kwargs + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist(), data) + self.assertFalse(self.images_mock.called) + self.assertFalse(self.flavors_mock.called) + + def test_server_create_with_tag_pre_v252(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.51') + + arglist = [ + '--image', 'image1', + '--flavor', 'flavor1', + '--tag', 'tag1', + '--tag', 'tag2', + self.new_server.name, + ] + verifylist = [ + ('image', 'image1'), + ('flavor', 'flavor1'), + ('tags', ['tag1', 'tag2']), + ('config_drive', False), + ('server_name', self.new_server.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.52 or greater is required', + str(ex)) + def test_server_create_with_host_v274(self): # Explicit host is supported for nova api version 2.74 or above @@ -3206,6 +3287,7 @@ class TestServerList(TestServer): self.search_opts['changes-before'] = '2016-03-05T06:27:59Z' self.search_opts['deleted'] = True + self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) @@ -3298,6 +3380,92 @@ class TestServerList(TestServer): 'UNKNOWN', '', '', '') self.assertEqual(expected_row, partial_server) + def test_server_list_with_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.26') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['tags'] = ['tag1', 'tag2'] + + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_tag_pre_v225(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.25') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.26 or greater is required', + str(ex)) + + def test_server_list_with_not_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.26') + + arglist = [ + '--not-tag', 'tag1', + '--not-tag', 'tag2', + ] + verifylist = [ + ('not_tags', ['tag1', 'tag2']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['not-tags'] = ['tag1', 'tag2'] + + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(self.columns, columns) + self.assertEqual(tuple(self.data), tuple(data)) + + def test_server_list_with_not_tag_pre_v226(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.25') + + arglist = [ + '--not-tag', 'tag1', + '--not-tag', 'tag2', + ] + verifylist = [ + ('not_tags', ['tag1', 'tag2']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.26 or greater is required', + str(ex)) + class TestServerLock(TestServer): @@ -5388,6 +5556,8 @@ class TestServerSet(TestServer): 'update': None, 'reset_state': None, 'change_password': None, + 'add_tag': None, + 'set_tags': None, } self.fake_servers = self.setup_servers_mock(2) @@ -5528,6 +5698,50 @@ class TestServerSet(TestServer): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_server_set_with_tag(self): + self.fake_servers[0].api_version = api_versions.APIVersion('2.26') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + 'foo_vm', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.fake_servers[0].add_tag.assert_has_calls([ + mock.call(tag='tag1'), + mock.call(tag='tag2'), + ]) + self.assertIsNone(result) + + def test_server_set_with_tag_pre_v226(self): + self.fake_servers[0].api_version = api_versions.APIVersion('2.25') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + 'foo_vm', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.26 or greater is required', + str(ex)) + class TestServerShelve(TestServer): @@ -5853,6 +6067,52 @@ class TestServerUnset(TestServer): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) + def test_server_unset_with_tag(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.26') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + 'foo_vm', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.servers_mock.delete_tag.assert_has_calls([ + mock.call(self.fake_server, tag='tag1'), + mock.call(self.fake_server, tag='tag2'), + ]) + + def test_server_unset_with_tag_pre_v226(self): + self.app.client_manager.compute.api_version = api_versions.APIVersion( + '2.25') + + arglist = [ + '--tag', 'tag1', + '--tag', 'tag2', + 'foo_vm', + ] + verifylist = [ + ('tags', ['tag1', 'tag2']), + ('server', 'foo_vm'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.26 or greater is required', + str(ex)) + class TestServerUnshelve(TestServer): diff --git a/releasenotes/notes/server-add-tag-63f9cd01dbd82d1b.yaml b/releasenotes/notes/server-add-tag-63f9cd01dbd82d1b.yaml new file mode 100644 index 0000000000..78e7482c48 --- /dev/null +++ b/releasenotes/notes/server-add-tag-63f9cd01dbd82d1b.yaml @@ -0,0 +1,14 @@ +--- +features: + - Add ``--tag`` option to ``server create`` command to add tags when creating + a server. + Only available starting with ``--os-compute-api-version 2.52``. + - Add ``--tag`` option to ``server set`` command to add a tag to an + existing server. + Only available starting with ``--os-compute-api-version 2.26``. + - Add ``--tag`` options to ``server unset`` command to remove a tag from an + existing server. + Only available starting with ``--os-compute-api-version 2.26``. + - Add ``--tags`` and ``--not-tags`` options to ``server list`` command to + list instances with and without the specified tag(s), respectively. + Only available starting with ``--os-compute-api-version 2.26``.