diff --git a/doc/source/cli/command-objects/floating-ip.rst b/doc/source/cli/command-objects/floating-ip.rst index 925a230c8..c0ba7ebf3 100644 --- a/doc/source/cli/command-objects/floating-ip.rst +++ b/doc/source/cli/command-objects/floating-ip.rst @@ -20,6 +20,7 @@ Create floating IP [--description ] [--qos-policy ] [--project [--project-domain ]] + [--tag | --no-tag] .. option:: --subnet @@ -66,6 +67,18 @@ Create floating IP *Network version 2 only* +.. option:: --tag + + Tag to be added to the floating IP (repeat option to set multiple tags) + + *Network version 2 only* + +.. option:: --no-tag + + No tags associated with the floating IP + + *Network version 2 only* + .. describe:: Network to allocate floating IP from (name or ID) @@ -100,6 +113,8 @@ List floating IP(s) [--status ] [--project [--project-domain ]] [--router ] + [--tags [,,...]] [--any-tags [,,...]] + [--not-tags [,,...]] [--not-any-tags [,,...]] .. option:: --network @@ -150,6 +165,30 @@ List floating IP(s) *Network version 2 only* +.. option:: --tags [,,...] + + List floating IP(s) which have all given tag(s) + + *Network version 2 only* + +.. option:: --any-tags [,,...] + + List floating IP(s) which have any given tag(s) + + *Network version 2 only* + +.. option:: --not-tags [,,...] + + Exclude floating IP(s) which have all given tag(s) + + *Network version 2 only* + +.. option:: --not-any-tags [,,...] + + Exclude floating IP(s) which have any given tag(s) + + *Network version 2 only* + floating ip set --------------- @@ -162,6 +201,7 @@ Set floating IP properties --port [--fixed-ip-address ] [--qos-policy | --no-qos-policy] + [--tag ] [--no-tag] .. option:: --port @@ -180,6 +220,15 @@ Set floating IP properties Remove the QoS policy attached to the floating IP +.. option:: --tag + + Tag to be added to the floating IP (repeat option to set multiple tags) + +.. option:: --no-tag + + Clear tags associated with the floating IP. Specify both --tag + and --no-tag to overwrite current tags + .. _floating_ip_set-floating-ip: .. describe:: @@ -210,6 +259,7 @@ Unset floating IP Properties openstack floating ip unset --port --qos-policy + [--tag | --all-tag] .. option:: --port @@ -220,6 +270,15 @@ Unset floating IP Properties Remove the QoS policy attached to the floating IP +.. option:: --tag + + Tag to be removed from the floating IP + (repeat option to remove multiple tags) + +.. option:: --all-tag + + Clear all tags associated with the floating IP + .. _floating_ip_unset-floating-ip: .. describe:: diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 4d07d9da3..f51baed57 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -22,6 +22,7 @@ from openstackclient.i18n import _ from openstackclient.identity import common as identity_common from openstackclient.network import common from openstackclient.network import sdk_utils +from openstackclient.network.v2 import _tag def _get_network_columns(item): @@ -139,11 +140,14 @@ class CreateFloatingIP(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, _('floating IP')) return parser def take_action_network(self, client, parsed_args): attrs = _get_attrs(self.app.client_manager, parsed_args) obj = client.create_ip(**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, columns = _get_network_columns(obj) data = utils.get_item_properties(obj, columns) return (display_columns, data) @@ -280,6 +284,7 @@ class ListFloatingIP(common.NetworkAndComputeLister): help=_("List floating IP(s) according to " "given router (name or ID)") ) + _tag.add_tag_filtering_option_to_parser(parser, _('floating IP')) return parser @@ -308,11 +313,13 @@ class ListFloatingIP(common.NetworkAndComputeLister): 'router_id', 'status', 'description', + 'tags', ) headers = headers + ( 'Router', 'Status', 'Description', + 'Tags', ) query = {} @@ -342,6 +349,8 @@ class ListFloatingIP(common.NetworkAndComputeLister): ignore_missing=False) query['router_id'] = router.id + _tag.get_tag_filtering_args(parsed_args, query) + data = client.ips(**query) return (headers, @@ -431,6 +440,9 @@ class SetFloatingIP(command.Command): action='store_true', help=_("Remove the QoS policy attached to the floating IP") ) + + _tag.add_tag_option_to_parser_for_set(parser, _('floating IP')) + return parser def take_action(self, parsed_args): @@ -453,7 +465,11 @@ class SetFloatingIP(command.Command): if 'no_qos_policy' in parsed_args and parsed_args.no_qos_policy: attrs['qos_policy_id'] = None - client.update_ip(obj, **attrs) + if attrs: + client.update_ip(obj, **attrs) + + # tags is a subresource and it needs to be updated separately. + _tag.update_tags_for_set(client, obj, parsed_args) class ShowFloatingIP(common.NetworkAndComputeShowOne): @@ -528,6 +544,8 @@ class UnsetFloatingIP(command.Command): default=False, help=_("Remove the QoS policy attached to the floating IP") ) + _tag.add_tag_option_to_parser_for_unset(parser, _('floating IP')) + return parser def take_action(self, parsed_args): @@ -544,3 +562,6 @@ class UnsetFloatingIP(command.Command): if attrs: client.update_ip(obj, **attrs) + + # 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 9f7941a71..0e21e2f8a 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1380,6 +1380,7 @@ class FakeFloatingIP(object): 'tenant_id': 'project-id-' + uuid.uuid4().hex, 'description': 'floating-ip-description-' + uuid.uuid4().hex, 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, + 'tags': [], } # Overwrite default attributes. diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py index f19849c40..65d873770 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py @@ -44,7 +44,7 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): subnet = network_fakes.FakeSubnet.create_one_subnet() port = network_fakes.FakePort.create_one_port() - # The floating ip to be deleted. + # The floating ip created. floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( attrs={ 'floating_network_id': floating_network.id, @@ -65,6 +65,7 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): 'qos_policy_id', 'router_id', 'status', + 'tags', ) data = ( @@ -80,12 +81,14 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): floating_ip.qos_policy_id, floating_ip.router_id, floating_ip.status, + floating_ip.tags, ) def setUp(self): super(TestCreateFloatingIPNetwork, self).setUp() self.network.create_ip = mock.Mock(return_value=self.floating_ip) + self.network.set_tags = mock.Mock(return_value=None) self.network.find_network = mock.Mock( return_value=self.floating_network) @@ -221,6 +224,42 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) + def _test_create_with_tag(self, add_tags=True): + arglist = [self.floating_ip.floating_network_id] + if add_tags: + arglist += ['--tag', 'red', '--tag', 'blue'] + else: + arglist += ['--no-tag'] + + verifylist = [ + ('network', self.floating_ip.floating_network_id), + ] + 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_ip.assert_called_once_with(**{ + 'floating_network_id': self.floating_ip.floating_network_id, + }) + if add_tags: + self.network.set_tags.assert_called_once_with( + self.floating_ip, + 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 TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): @@ -353,6 +392,7 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): 'Router', 'Status', 'Description', + 'Tags', ) data = [] @@ -376,6 +416,7 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): ip.router_id, ip.status, ip.description, + ip.tags, )) def setUp(self): @@ -539,6 +580,31 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, 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.ips.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 TestShowFloatingIPNetwork(TestFloatingIPNetwork): @@ -558,6 +624,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): 'qos_policy_id', 'router_id', 'status', + 'tags', ) data = ( @@ -573,6 +640,7 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): floating_ip.qos_policy_id, floating_ip.router_id, floating_ip.status, + floating_ip.tags, ) def setUp(self): @@ -609,11 +677,12 @@ class TestSetFloatingIP(TestFloatingIPNetwork): subnet = network_fakes.FakeSubnet.create_one_subnet() port = network_fakes.FakePort.create_one_port() - # The floating ip to be deleted. + # The floating ip to be set. floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( attrs={ 'floating_network_id': floating_network.id, 'port_id': port.id, + 'tags': ['green', 'red'], } ) @@ -622,6 +691,7 @@ class TestSetFloatingIP(TestFloatingIPNetwork): self.network.find_ip = mock.Mock(return_value=self.floating_ip) self.network.find_port = mock.Mock(return_value=self.port) self.network.update_ip = mock.Mock(return_value=None) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = fip.SetFloatingIP(self.app, self.namespace) @@ -731,6 +801,36 @@ class TestSetFloatingIP(TestFloatingIPNetwork): self.network.update_ip.assert_called_once_with( self.floating_ip, **attrs) + 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.extend(['--port', self.floating_ip.port_id, + self.floating_ip.id]) + verifylist.extend([ + ('port', self.floating_ip.port_id), + ('floating_ip', self.floating_ip.id)]) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertTrue(self.network.update_ip.called) + self.network.set_tags.assert_called_once_with( + self.floating_ip, + 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 TestUnsetFloatingIP(TestFloatingIPNetwork): @@ -738,11 +838,12 @@ class TestUnsetFloatingIP(TestFloatingIPNetwork): subnet = network_fakes.FakeSubnet.create_one_subnet() port = network_fakes.FakePort.create_one_port() - # The floating ip to be deleted. + # The floating ip to be unset. floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( attrs={ 'floating_network_id': floating_network.id, 'port_id': port.id, + 'tags': ['green', 'red'], } ) @@ -750,6 +851,7 @@ class TestUnsetFloatingIP(TestFloatingIPNetwork): super(TestUnsetFloatingIP, self).setUp() self.network.find_ip = mock.Mock(return_value=self.floating_ip) self.network.update_ip = mock.Mock(return_value=None) + self.network.set_tags = mock.Mock(return_value=None) # Get the command object to test self.cmd = fip.UnsetFloatingIP(self.app, self.namespace) @@ -803,3 +905,31 @@ class TestUnsetFloatingIP(TestFloatingIPNetwork): self.floating_ip, **attrs) 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.floating_ip.id) + verifylist.append( + ('floating_ip', self.floating_ip.id)) + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.assertFalse(self.network.update_ip.called) + self.network.set_tags.assert_called_once_with( + self.floating_ip, + 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-1750985-a5345f715a14825c.yaml b/releasenotes/notes/bug-1750985-a5345f715a14825c.yaml new file mode 100644 index 000000000..87b0f29ab --- /dev/null +++ b/releasenotes/notes/bug-1750985-a5345f715a14825c.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Add ``--tag`` support to ``floating ip create|list|set|unset`` commands. + [:lpbug:`1750985`]