From aaed4b315fa1e86b1ddf73201b5350dbdb4a660e Mon Sep 17 00:00:00 2001 From: Dongcan Ye Date: Sat, 24 Feb 2018 09:13:48 +0000 Subject: [PATCH] Network: Add tag support for security group Change-Id: Icccb23429913724c6a8bd15d4737672b47a5f13a Closes-Bug: #1750983 --- .../cli/command-objects/security-group.rst | 74 ++++++++ openstackclient/network/v2/security_group.py | 43 ++++- .../tests/unit/network/v2/fakes.py | 1 + .../network/v2/test_security_group_network.py | 174 +++++++++++++++++- .../notes/bug-1750983-420945d6c0afb509.yaml | 8 + setup.cfg | 1 + 6 files changed, 295 insertions(+), 6 deletions(-) create mode 100644 releasenotes/notes/bug-1750983-420945d6c0afb509.yaml diff --git a/doc/source/cli/command-objects/security-group.rst b/doc/source/cli/command-objects/security-group.rst index a95a96f49..403e5fc08 100644 --- a/doc/source/cli/command-objects/security-group.rst +++ b/doc/source/cli/command-objects/security-group.rst @@ -19,6 +19,7 @@ Create a new security group openstack security group create [--description ] [--project [--project-domain ]] + [--tag | --no-tag] .. option:: --description @@ -38,6 +39,18 @@ Create a new security group *Network version 2 only* +.. option:: --tag + + Tag to be added to the security group (repeat option to set multiple tags) + + *Network version 2 only* + +.. option:: --no-tag + + No tags associated with the security group + + *Network version 2 only* + .. describe:: New security group name @@ -68,6 +81,8 @@ List security groups openstack security group list [--all-projects] [--project [--project-domain ]] + [--tags [,,...]] [--any-tags [,,...]] + [--not-tags [,,...]] [--not-any-tags [,,...]] .. option:: --all-projects @@ -89,6 +104,30 @@ List security groups *Network version 2 only* +.. option:: --tags [,,...] + + List security groups which have all given tag(s) + + *Network version 2 only* + +.. option:: --any-tags [,,...] + + List security groups which have any given tag(s) + + *Network version 2 only* + +.. option:: --not-tags [,,...] + + Exclude security groups which have all given tag(s) + + *Network version 2 only* + +.. option:: --not-any-tags [,,...] + + Exclude security groups which have any given tag(s) + + *Network version 2 only* + security group set ------------------ @@ -100,6 +139,7 @@ Set security group properties openstack security group set [--name ] [--description ] + [--tag ] [--no-tag] .. option:: --name @@ -110,6 +150,15 @@ Set security group properties New security group description +.. option:: --tag + + Tag to be added to the security group (repeat option to set multiple tags) + +.. option:: --no-tag + + Clear tags associated with the security group. Specify both --tag + and --no-tag to overwrite current tags + .. describe:: Security group to modify (name or ID) @@ -128,3 +177,28 @@ Display security group details .. describe:: Security group to display (name or ID) + +security group unset +-------------------- + +Unset security group properties + +.. program:: security group unset +.. code:: bash + + openstack security group unset + [--tag | --all-tag] + + +.. option:: --tag + + Tag to be removed from the security group + (repeat option to remove multiple tags) + +.. option:: --all-tag + + Clear all tags associated with the security group + +.. describe:: + + Security group to modify (name or ID) diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 75af3587e..ed6c8d7c2 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -15,6 +15,7 @@ import argparse +from osc_lib.command import command from osc_lib import utils import six @@ -23,6 +24,7 @@ from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import sdk_utils from openstackclient.network import utils as network_utils +from openstackclient.network.v2 import _tag def _format_network_security_group_rules(sg_rules): @@ -106,6 +108,7 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne): help=_("Owner's project (name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) + _tag.add_tag_option_to_parser_for_create(parser, _('security group')) return parser def _get_description(self, parsed_args): @@ -130,6 +133,8 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne): # Create the security group and display the results. obj = client.create_security_group(**attrs) + # tags cannot be set when created, so tags need to be set later. + _tag.update_tags_for_set(client, obj, parsed_args) display_columns, property_columns = _get_columns(obj) data = utils.get_item_properties( obj, @@ -198,6 +203,7 @@ class ListSecurityGroup(common.NetworkAndComputeLister): "(name or ID)") ) identity_common.add_project_domain_option_to_parser(parser) + _tag.add_tag_filtering_option_to_parser(parser, _('security group')) return parser def update_parser_compute(self, parser): @@ -220,19 +226,23 @@ class ListSecurityGroup(common.NetworkAndComputeLister): ).id filters['tenant_id'] = project_id filters['project_id'] = project_id + + _tag.get_tag_filtering_args(parsed_args, filters) data = client.security_groups(**filters) columns = ( "ID", "Name", "Description", - "Project ID" + "Project ID", + "tags" ) column_headers = ( "ID", "Name", "Description", - "Project" + "Project", + "Tags" ) return (column_headers, (utils.get_item_properties( @@ -282,6 +292,10 @@ class SetSecurityGroup(common.NetworkAndComputeCommand): ) return parser + def update_parser_network(self, parser): + _tag.add_tag_option_to_parser_for_set(parser, _('security group')) + return parser + def take_action_network(self, client, parsed_args): obj = client.find_security_group(parsed_args.group, ignore_missing=False) @@ -295,6 +309,9 @@ class SetSecurityGroup(common.NetworkAndComputeCommand): # the update. client.update_security_group(obj, **attrs) + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_set(client, obj, parsed_args) + def take_action_compute(self, client, parsed_args): data = client.api.security_group_find(parsed_args.group) @@ -344,3 +361,25 @@ class ShowSecurityGroup(common.NetworkAndComputeShowOne): formatters=_formatters_compute ) return (display_columns, data) + + +class UnsetSecurityGroup(command.Command): + _description = _("Unset security group properties") + + def get_parser(self, prog_name): + parser = super(UnsetSecurityGroup, self).get_parser(prog_name) + parser.add_argument( + 'group', + metavar="", + help=_("Security group to modify (name or ID)") + ) + _tag.add_tag_option_to_parser_for_unset(parser, _('security group')) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_security_group(parsed_args.group, + ignore_missing=False) + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_unset(client, obj, parsed_args) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index a77cab8b5..e69df88ad 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1131,6 +1131,7 @@ class FakeSecurityGroup(object): 'description': 'security-group-description-' + uuid.uuid4().hex, 'project_id': 'project-id-' + uuid.uuid4().hex, 'security_group_rules': [], + 'tags': [] } # Overwrite default attributes. diff --git a/openstackclient/tests/unit/network/v2/test_security_group_network.py b/openstackclient/tests/unit/network/v2/test_security_group_network.py index 35b7e366d..83208287c 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py @@ -40,8 +40,8 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): project = identity_fakes.FakeProject.create_one_project() domain = identity_fakes.FakeDomain.create_one_domain() # The security group to be created. - _security_group = \ - network_fakes.FakeSecurityGroup.create_one_security_group() + _security_group = ( + network_fakes.FakeSecurityGroup.create_one_security_group()) columns = ( 'description', @@ -49,6 +49,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): 'name', 'project_id', 'rules', + 'tags', ) data = ( @@ -57,6 +58,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): _security_group.name, _security_group.project_id, '', + _security_group.tags, ) def setUp(self): @@ -67,6 +69,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): self.projects_mock.get.return_value = self.project self.domains_mock.get.return_value = self.domain + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = security_group.CreateSecurityGroup(self.app, self.namespace) @@ -118,6 +121,43 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def _test_create_with_tag(self, add_tags=True): + arglist = [self._security_group.name] + if add_tags: + arglist += ['--tag', 'red', '--tag', 'blue'] + else: + arglist += ['--no-tag'] + + verifylist = [ + ('name', self._security_group.name), + ] + if add_tags: + verifylist.append(('tags', ['red', 'blue'])) + else: + verifylist.append(('no_tag', True)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_security_group.assert_called_once_with(**{ + 'description': self._security_group.name, + 'name': self._security_group.name, + }) + if add_tags: + self.network.set_tags.assert_called_once_with( + self._security_group, + tests_utils.CompareBySet(['red', 'blue'])) + else: + self.assertFalse(self.network.set_tags.called) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, data) + + def test_create_with_tags(self): + self._test_create_with_tag(add_tags=True) + + def test_create_with_no_tag(self): + self._test_create_with_tag(add_tags=False) + class TestDeleteSecurityGroupNetwork(TestSecurityGroupNetwork): @@ -214,6 +254,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): 'Name', 'Description', 'Project', + 'Tags', ) data = [] @@ -223,6 +264,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): grp.name, grp.description, grp.project_id, + grp.tags, )) def setUp(self): @@ -300,12 +342,38 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) + def test_list_with_tag_options(self): + arglist = [ + '--tags', 'red,blue', + '--any-tags', 'red,green', + '--not-tags', 'orange,yellow', + '--not-any-tags', 'black,white', + ] + verifylist = [ + ('tags', ['red', 'blue']), + ('any_tags', ['red', 'green']), + ('not_tags', ['orange', 'yellow']), + ('not_any_tags', ['black', 'white']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.security_groups.assert_called_once_with( + **{'tags': 'red,blue', + 'any_tags': 'red,green', + 'not_tags': 'orange,yellow', + 'not_any_tags': 'black,white'} + ) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork): # The security group to be set. - _security_group = \ - network_fakes.FakeSecurityGroup.create_one_security_group() + _security_group = ( + network_fakes.FakeSecurityGroup.create_one_security_group( + attrs={'tags': ['green', 'red']})) def setUp(self): super(TestSetSecurityGroupNetwork, self).setUp() @@ -314,6 +382,7 @@ class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork): self.network.find_security_group = mock.Mock( return_value=self._security_group) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = security_group.SetSecurityGroup(self.app, self.namespace) @@ -366,6 +435,34 @@ class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork): ) self.assertIsNone(result) + def _test_set_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['red', 'blue', 'green'] + else: + arglist = ['--no-tag'] + verifylist = [('no_tag', True)] + expected_args = [] + arglist.append(self._security_group.name) + verifylist.append( + ('group', self._security_group.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertTrue(self.network.update_security_group.called) + self.network.set_tags.assert_called_once_with( + self._security_group, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_set_with_tags(self): + self._test_set_tags(with_tags=True) + + def test_set_with_no_tag(self): + self._test_set_tags(with_tags=False) + class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): @@ -385,6 +482,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): 'name', 'project_id', 'rules', + 'tags', ) data = ( @@ -394,6 +492,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): _security_group.project_id, security_group._format_network_security_group_rules( [_security_group_rule._info]), + _security_group.tags, ) def setUp(self): @@ -424,3 +523,70 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): self._security_group.id, ignore_missing=False) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + + +class TestUnsetSecurityGroupNetwork(TestSecurityGroupNetwork): + + # The security group to be unset. + _security_group = ( + network_fakes.FakeSecurityGroup.create_one_security_group( + attrs={'tags': ['green', 'red']})) + + def setUp(self): + super(TestUnsetSecurityGroupNetwork, self).setUp() + + self.network.update_security_group = mock.Mock(return_value=None) + + self.network.find_security_group = mock.Mock( + return_value=self._security_group) + self.network.set_tags = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = security_group.UnsetSecurityGroup(self.app, self.namespace) + + def test_set_no_options(self): + self.assertRaises(tests_utils.ParserException, + self.check_parser, self.cmd, [], []) + + def test_set_no_updates(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.assertFalse(self.network.update_security_group.called) + self.assertFalse(self.network.set_tags.called) + self.assertIsNone(result) + + def _test_unset_tags(self, with_tags=True): + if with_tags: + arglist = ['--tag', 'red', '--tag', 'blue'] + verifylist = [('tags', ['red', 'blue'])] + expected_args = ['green'] + else: + arglist = ['--all-tag'] + verifylist = [('all_tag', True)] + expected_args = [] + arglist.append(self._security_group.name) + verifylist.append( + ('group', self._security_group.name)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_security_group.called) + self.network.set_tags.assert_called_once_with( + self._security_group, + tests_utils.CompareBySet(expected_args)) + self.assertIsNone(result) + + def test_unset_with_tags(self): + self._test_unset_tags(with_tags=True) + + def test_unset_with_all_tag(self): + self._test_unset_tags(with_tags=False) diff --git a/releasenotes/notes/bug-1750983-420945d6c0afb509.yaml b/releasenotes/notes/bug-1750983-420945d6c0afb509.yaml new file mode 100644 index 000000000..2c37d88fa --- /dev/null +++ b/releasenotes/notes/bug-1750983-420945d6c0afb509.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Add supports tagging for Network security group. + Add ``tag``, ``no-tag`` to ``security group create`` and ``security group set`` commands. + Add ``tags``, ``any-tags``, ``not-tags``, ``not-any-tags`` to ``security group list`` command. + Add ``tag`` and ``all-tag`` to ``security group unset`` command. + [Bug `1750983 `_] diff --git a/setup.cfg b/setup.cfg index 63bfdafb1..37592f495 100644 --- a/setup.cfg +++ b/setup.cfg @@ -467,6 +467,7 @@ openstack.network.v2 = security_group_list = openstackclient.network.v2.security_group:ListSecurityGroup security_group_set = openstackclient.network.v2.security_group:SetSecurityGroup security_group_show = openstackclient.network.v2.security_group:ShowSecurityGroup + security_group_unset = openstackclient.network.v2.security_group:UnsetSecurityGroup security_group_rule_create = openstackclient.network.v2.security_group_rule:CreateSecurityGroupRule security_group_rule_delete = openstackclient.network.v2.security_group_rule:DeleteSecurityGroupRule