From 9c4b85a523f9ad7e540a76b5ebfbea3bd88d13dd Mon Sep 17 00:00:00 2001 From: Gregory Thiemonge Date: Fri, 13 Nov 2020 08:12:31 +0100 Subject: [PATCH] Add tag support for Octavia resources Add tag support for load balancers, listeners, pools, health monitors, members, l7policies and l7rules. Tags can be set when creating a resource, and can be modified using set/unset CLI calls. Filtering by tags is also supported when listing resources. This commit also fixes some errors when using set/unset l7rule CLI with the --wait flag (l7rule_id and l7policy_id parameters were swapped in partial function calls). Story: 2008347 Task: 41249 Change-Id: I1d5119cb5ce9da0fd6c7a39aeb371a507607b9d7 --- octaviaclient/osc/v2/constants.py | 21 +- octaviaclient/osc/v2/health_monitor.py | 27 +- octaviaclient/osc/v2/l7policy.py | 27 +- octaviaclient/osc/v2/l7rule.py | 55 +++-- octaviaclient/osc/v2/listener.py | 28 ++- octaviaclient/osc/v2/load_balancer.py | 27 +- octaviaclient/osc/v2/member.py | 48 +++- octaviaclient/osc/v2/pool.py | 27 +- octaviaclient/osc/v2/utils.py | 47 +++- octaviaclient/tests/unit/osc/v2/constants.py | 7 +- .../tests/unit/osc/v2/test_health_monitor.py | 157 ++++++++++++ .../tests/unit/osc/v2/test_l7policy.py | 168 +++++++++++++ .../tests/unit/osc/v2/test_l7rule.py | 230 +++++++++++++++++- .../tests/unit/osc/v2/test_listener.py | 153 ++++++++++++ .../tests/unit/osc/v2/test_load_balancer.py | 190 +++++++++++++++ .../tests/unit/osc/v2/test_member.py | 197 ++++++++++++++- octaviaclient/tests/unit/osc/v2/test_pool.py | 153 ++++++++++++ .../add-tag-support-01087c4b3c4360dc.yaml | 12 + 18 files changed, 1513 insertions(+), 61 deletions(-) create mode 100644 releasenotes/notes/add-tag-support-01087c4b3c4360dc.yaml diff --git a/octaviaclient/osc/v2/constants.py b/octaviaclient/osc/v2/constants.py index f4d441d..eb26ae8 100644 --- a/octaviaclient/osc/v2/constants.py +++ b/octaviaclient/osc/v2/constants.py @@ -32,6 +32,7 @@ LOAD_BALANCER_ROWS = ( 'vip_port_id', 'vip_qos_policy_id', 'vip_subnet_id', + 'tags', ) LOAD_BALANCER_COLUMNS = ( @@ -79,7 +80,8 @@ LISTENER_ROWS = ( 'allowed_cidrs', 'tls_ciphers', 'tls_versions', - 'alpn_protocols') + 'alpn_protocols', + 'tags') LISTENER_COLUMNS = ( 'id', @@ -112,7 +114,8 @@ POOL_ROWS = ( 'crl_container_ref', 'tls_enabled', 'tls_ciphers', - 'tls_versions') + 'tls_versions', + 'tags') POOL_COLUMNS = ( 'id', @@ -138,8 +141,8 @@ MEMBER_ROWS = ( 'weight', 'monitor_port', 'monitor_address', - 'backup' -) + 'backup', + 'tags') MEMBER_COLUMNS = ( 'id', @@ -168,7 +171,8 @@ L7POLICY_ROWS = ( 'id', 'operating_status', 'name', - 'redirect_http_code') + 'redirect_http_code', + 'tags') L7POLICY_COLUMNS = ( 'id', @@ -191,7 +195,8 @@ L7RULE_ROWS = ( 'project_id', 'type', 'id', - 'operating_status') + 'operating_status', + 'tags') L7RULE_COLUMNS = ( 'id', @@ -223,8 +228,8 @@ MONITOR_ROWS = ( 'id', 'operating_status', 'http_version', - 'domain_name' -) + 'domain_name', + 'tags') MONITOR_COLUMNS = ( 'id', diff --git a/octaviaclient/osc/v2/health_monitor.py b/octaviaclient/osc/v2/health_monitor.py index 4f037dc..3d8fd76 100644 --- a/octaviaclient/osc/v2/health_monitor.py +++ b/octaviaclient/osc/v2/health_monitor.py @@ -21,6 +21,7 @@ from cliff import lister from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from osc_lib.utils import tags as _tag from oslo_utils import uuidutils from octaviaclient.osc.v2 import constants as const @@ -140,6 +141,9 @@ class CreateHealthMonitor(command.ShowOne): help='Wait for action to complete', ) + _tag.add_tag_option_to_parser_for_create( + parser, 'health monitor') + return parser def take_action(self, parsed_args): @@ -164,7 +168,8 @@ class CreateHealthMonitor(command.ShowOne): data['healthmonitor']['id'])) } - formatters = {'pools': v2_utils.format_list} + formatters = {'pools': v2_utils.format_list, + 'tags': v2_utils.format_list_flat} return (rows, (utils.get_dict_properties(data['healthmonitor'], @@ -214,6 +219,8 @@ class ListHealthMonitor(lister.Lister): def get_parser(self, prog_name): parser = super().get_parser(prog_name) + _tag.add_tag_filtering_option_to_parser(parser, 'health monitor') + return parser def take_action(self, parsed_args): @@ -262,7 +269,8 @@ class ShowHealthMonitor(command.ShowOne): data = self.app.client_manager.load_balancer.health_monitor_show( health_monitor_id=health_monitor_id, ) - formatters = {'pools': v2_utils.format_list} + formatters = {'pools': v2_utils.format_list, + 'tags': v2_utils.format_list_flat} return (rows, (utils.get_dict_properties(data, rows, formatters=formatters))) @@ -364,6 +372,8 @@ class SetHealthMonitor(command.Command): help='Wait for action to complete', ) + _tag.add_tag_option_to_parser_for_set(parser, 'health monitor') + return parser def take_action(self, parsed_args): @@ -372,6 +382,10 @@ class SetHealthMonitor(command.Command): hm_id = attrs.pop('health_monitor_id') + v2_utils.set_tags_for_set( + self.app.client_manager.load_balancer.health_monitor_show, + hm_id, attrs, clear_tags=parsed_args.no_tag) + body = {'healthmonitor': attrs} self.app.client_manager.load_balancer.health_monitor_set( @@ -437,17 +451,24 @@ class UnsetHealthMonitor(command.Command): action='store_true', help='Wait for action to complete', ) + + _tag.add_tag_option_to_parser_for_unset(parser, 'health monitor') + return parser def take_action(self, parsed_args): unset_args = v2_utils.get_unsets(parsed_args) - if not unset_args: + if not unset_args and not parsed_args.all_tag: return hm_id = v2_utils.get_resource_id( self.app.client_manager.load_balancer.health_monitor_list, 'healthmonitors', parsed_args.health_monitor) + v2_utils.set_tags_for_unset( + self.app.client_manager.load_balancer.health_monitor_show, + hm_id, unset_args, clear_tags=parsed_args.all_tag) + body = {'healthmonitor': unset_args} self.app.client_manager.load_balancer.health_monitor_set( diff --git a/octaviaclient/osc/v2/l7policy.py b/octaviaclient/osc/v2/l7policy.py index c425b56..4d129a3 100644 --- a/octaviaclient/osc/v2/l7policy.py +++ b/octaviaclient/osc/v2/l7policy.py @@ -20,6 +20,7 @@ from cliff import lister from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from osc_lib.utils import tags as _tag from oslo_utils import uuidutils from octaviaclient.osc.v2 import constants as const @@ -112,6 +113,9 @@ class CreateL7Policy(command.ShowOne): help='Wait for action to complete', ) + _tag.add_tag_option_to_parser_for_create( + parser, 'l7policy') + return parser def take_action(self, parsed_args): @@ -138,7 +142,8 @@ class CreateL7Policy(command.ShowOne): data['l7policy']['id'])) } - formatters = {'rules': v2_utils.format_list} + formatters = {'rules': v2_utils.format_list, + 'tags': v2_utils.format_list_flat} return (rows, (utils.get_dict_properties( data['l7policy'], rows, formatters=formatters))) @@ -192,6 +197,8 @@ class ListL7Policy(lister.Lister): "(name or ID)." ) + _tag.add_tag_filtering_option_to_parser(parser, 'l7policy') + return parser def take_action(self, parsed_args): @@ -241,7 +248,8 @@ class ShowL7Policy(command.ShowOne): data = self.app.client_manager.load_balancer.l7policy_show( l7policy_id=l7policy_id, ) - formatters = {'rules': v2_utils.format_list} + formatters = {'rules': v2_utils.format_list, + 'tags': v2_utils.format_list_flat} return (rows, (utils.get_dict_properties( data, rows, formatters=formatters))) @@ -325,6 +333,8 @@ class SetL7Policy(command.Command): help='Wait for action to complete', ) + _tag.add_tag_option_to_parser_for_set(parser, 'l7policy') + return parser def take_action(self, parsed_args): @@ -334,6 +344,10 @@ class SetL7Policy(command.Command): validate.check_l7policy_attrs(attrs) l7policy_id = attrs.pop('l7policy_id') + v2_utils.set_tags_for_set( + self.app.client_manager.load_balancer.l7policy_show, + l7policy_id, attrs, clear_tags=parsed_args.no_tag) + body = {'l7policy': attrs} self.app.client_manager.load_balancer.l7policy_set( @@ -378,17 +392,24 @@ class UnsetL7Policy(command.Command): action='store_true', help='Wait for action to complete', ) + + _tag.add_tag_option_to_parser_for_unset(parser, 'l7policy') + return parser def take_action(self, parsed_args): unset_args = v2_utils.get_unsets(parsed_args) - if not unset_args: + if not unset_args and not parsed_args.all_tag: return policy_id = v2_utils.get_resource_id( self.app.client_manager.load_balancer.l7policy_list, 'l7policies', parsed_args.l7policy) + v2_utils.set_tags_for_unset( + self.app.client_manager.load_balancer.l7policy_show, + policy_id, unset_args, clear_tags=parsed_args.all_tag) + body = {'l7policy': unset_args} self.app.client_manager.load_balancer.l7policy_set( diff --git a/octaviaclient/osc/v2/l7rule.py b/octaviaclient/osc/v2/l7rule.py index 2ba37ed..ef3a4d1 100644 --- a/octaviaclient/osc/v2/l7rule.py +++ b/octaviaclient/osc/v2/l7rule.py @@ -22,6 +22,7 @@ from cliff import lister from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from osc_lib.utils import tags as _tag from oslo_utils import uuidutils from octaviaclient.osc.v2 import constants as const @@ -95,6 +96,9 @@ class CreateL7Rule(command.ShowOne): help='Wait for action to complete', ) + _tag.add_tag_option_to_parser_for_create( + parser, 'l7rule') + return parser def take_action(self, parsed_args): @@ -126,8 +130,10 @@ class CreateL7Rule(command.ShowOne): l7policy_id, data['rule']['id'])) } + formatters = {'tags': v2_utils.format_list_flat} + return (rows, (utils.get_dict_properties( - data['rule'], rows, formatters={}))) + data['rule'], rows, formatters=formatters))) class DeleteL7Rule(command.Command): @@ -165,11 +171,11 @@ class DeleteL7Rule(command.Command): if parsed_args.wait: l7rule_show = functools.partial( self.app.client_manager.load_balancer.l7rule_show, - attrs['l7policy_id'] + attrs['l7rule_id'] ) v2_utils.wait_for_delete( status_f=l7rule_show, - res_id=attrs['l7rule_id'] + res_id=attrs['l7policy_id'] ) @@ -185,6 +191,8 @@ class ListL7Rule(lister.Lister): help='l7policy to list rules for (name or ID).' ) + _tag.add_tag_filtering_option_to_parser(parser, 'l7rule') + return parser def take_action(self, parsed_args): @@ -192,7 +200,7 @@ class ListL7Rule(lister.Lister): attrs = v2_utils.get_l7rule_attrs(self.app.client_manager, parsed_args) data = self.app.client_manager.load_balancer.l7rule_list( - l7policy_id=attrs['l7policy_id'] + **attrs ) return (columns, @@ -237,9 +245,10 @@ class ShowL7Rule(command.ShowOne): l7rule_id=attrs['l7rule_id'], l7policy_id=attrs['l7policy_id'] ) + formatters = {'tags': v2_utils.format_list_flat} return (rows, (utils.get_dict_properties( - data, rows, formatters={}))) + data, rows, formatters=formatters))) class SetL7Rule(command.Command): @@ -307,6 +316,8 @@ class SetL7Rule(command.Command): help='Wait for action to complete', ) + _tag.add_tag_option_to_parser_for_set(parser, 'l7rule') + return parser def take_action(self, parsed_args): @@ -316,6 +327,15 @@ class SetL7Rule(command.Command): l7policy_id = attrs.pop('l7policy_id') l7rule_id = attrs.pop('l7rule_id') + # l7rule_id is the first argument in l7rule_show + l7rule_show = functools.partial( + self.app.client_manager.load_balancer.l7rule_show, + l7rule_id + ) + + v2_utils.set_tags_for_set( + l7rule_show, l7policy_id, attrs, clear_tags=parsed_args.no_tag) + body = {'rule': attrs} self.app.client_manager.load_balancer.l7rule_set( @@ -325,13 +345,9 @@ class SetL7Rule(command.Command): ) if parsed_args.wait: - l7rule_show = functools.partial( - self.app.client_manager.load_balancer.l7rule_show, - l7policy_id - ) v2_utils.wait_for_active( status_f=l7rule_show, - res_id=l7rule_id + res_id=l7policy_id ) @@ -366,28 +382,35 @@ class UnsetL7Rule(command.Command): action='store_true', help='Wait for action to complete', ) + _tag.add_tag_option_to_parser_for_unset(parser, 'l7rule') + return parser def take_action(self, parsed_args): unset_args = v2_utils.get_unsets(parsed_args) - if not unset_args: + if not unset_args and not parsed_args.all_tag: return policy_id = v2_utils.get_resource_id( self.app.client_manager.load_balancer.l7policy_list, 'l7policies', parsed_args.l7policy) + l7rule_show = functools.partial( + self.app.client_manager.load_balancer.l7rule_show, + parsed_args.l7rule_id + ) + + v2_utils.set_tags_for_unset( + l7rule_show, policy_id, unset_args, + clear_tags=parsed_args.all_tag) + body = {'rule': unset_args} self.app.client_manager.load_balancer.l7rule_set( l7policy_id=policy_id, l7rule_id=parsed_args.l7rule_id, json=body) if parsed_args.wait: - l7rule_show = functools.partial( - self.app.client_manager.load_balancer.l7rule_show, - policy_id - ) v2_utils.wait_for_active( status_f=l7rule_show, - res_id=parsed_args.l7rule_id + res_id=policy_id, ) diff --git a/octaviaclient/osc/v2/listener.py b/octaviaclient/osc/v2/listener.py index 8e65b8a..9884c72 100644 --- a/octaviaclient/osc/v2/listener.py +++ b/octaviaclient/osc/v2/listener.py @@ -19,6 +19,7 @@ from cliff import lister from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from osc_lib.utils import tags as _tag from oslo_utils import uuidutils from octaviaclient.osc.v2 import constants as const @@ -200,6 +201,9 @@ class CreateListener(command.ShowOne): "by the listener (can be set multiple times)." ) + _tag.add_tag_option_to_parser_for_create( + parser, 'listener') + return parser def take_action(self, parsed_args): @@ -229,7 +233,8 @@ class CreateListener(command.ShowOne): 'pools': v2_utils.format_list, 'l7policies': v2_utils.format_list, 'insert_headers': v2_utils.format_hash, - 'allowed_cidrs': v2_utils.format_list_flat} + 'allowed_cidrs': v2_utils.format_list_flat, + 'tags': v2_utils.format_list_flat} return (rows, (utils.get_dict_properties(data['listener'], @@ -308,6 +313,9 @@ class ListListener(lister.Lister): metavar='', help="List listeners by project ID." ) + + _tag.add_tag_filtering_option_to_parser(parser, 'listener') + return parser def take_action(self, parsed_args): @@ -357,7 +365,8 @@ class ShowListener(command.ShowOne): 'pools': v2_utils.format_list, 'l7policies': v2_utils.format_list, 'insert_headers': v2_utils.format_hash, - 'allowed_cidrs': v2_utils.format_list_flat} + 'allowed_cidrs': v2_utils.format_list_flat, + 'tags': v2_utils.format_list_flat} return rows, utils.get_dict_properties(data, rows, formatters=formatters) @@ -519,6 +528,8 @@ class SetListener(command.Command): "by the listener (can be set multiple times)." ) + _tag.add_tag_option_to_parser_for_set(parser, 'listener') + return parser def take_action(self, parsed_args): @@ -527,6 +538,10 @@ class SetListener(command.Command): listener_id = attrs.pop('listener_id') + v2_utils.set_tags_for_set( + self.app.client_manager.load_balancer.listener_show, + listener_id, attrs, clear_tags=parsed_args.no_tag) + body = {'listener': attrs} self.app.client_manager.load_balancer.listener_set( @@ -648,17 +663,24 @@ class UnsetListener(command.Command): action='store_true', help="Clear all ALPN protocols from the listener." ) + + _tag.add_tag_option_to_parser_for_unset(parser, 'listener') + return parser def take_action(self, parsed_args): unset_args = v2_utils.get_unsets(parsed_args) - if not unset_args: + if not unset_args and not parsed_args.all_tag: return listener_id = v2_utils.get_resource_id( self.app.client_manager.load_balancer.listener_list, 'listeners', parsed_args.listener) + v2_utils.set_tags_for_unset( + self.app.client_manager.load_balancer.listener_show, + listener_id, unset_args, clear_tags=parsed_args.all_tag) + body = {'listener': unset_args} self.app.client_manager.load_balancer.listener_set( diff --git a/octaviaclient/osc/v2/load_balancer.py b/octaviaclient/osc/v2/load_balancer.py index 309d098..7662d16 100644 --- a/octaviaclient/osc/v2/load_balancer.py +++ b/octaviaclient/osc/v2/load_balancer.py @@ -17,6 +17,7 @@ from cliff import lister from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from osc_lib.utils import tags as _tag from oslo_serialization import jsonutils from oslo_utils import uuidutils @@ -132,6 +133,9 @@ class CreateLoadBalancer(command.ShowOne): help='Wait for action to complete', ) + _tag.add_tag_option_to_parser_for_create( + parser, 'load balancer') + return parser def take_action(self, parsed_args): @@ -159,7 +163,8 @@ class CreateLoadBalancer(command.ShowOne): formatters = { 'listeners': v2_utils.format_list, 'pools': v2_utils.format_list, - 'l7policies': v2_utils.format_list + 'l7policies': v2_utils.format_list, + 'tags': v2_utils.format_list_flat } return (rows, @@ -326,6 +331,8 @@ class ListLoadBalancer(lister.Lister): help="List load balancers according to their availability zone." ) + _tag.add_tag_filtering_option_to_parser(parser, 'load balancer') + return parser def take_action(self, parsed_args): @@ -380,7 +387,8 @@ class ShowLoadBalancer(command.ShowOne): formatters = { 'listeners': v2_utils.format_list, 'pools': v2_utils.format_list, - 'l7policies': v2_utils.format_list + 'l7policies': v2_utils.format_list, + 'tags': v2_utils.format_list_flat } return (rows, (utils.get_dict_properties( @@ -433,12 +441,19 @@ class SetLoadBalancer(command.Command): help='Wait for action to complete', ) + _tag.add_tag_option_to_parser_for_set(parser, 'load balancer') + return parser def take_action(self, parsed_args): attrs = v2_utils.get_loadbalancer_attrs(self.app.client_manager, parsed_args) lb_id = attrs.pop('loadbalancer_id') + + v2_utils.set_tags_for_set( + self.app.client_manager.load_balancer.load_balancer_show, + lb_id, attrs, clear_tags=parsed_args.no_tag) + body = {'loadbalancer': attrs} self.app.client_manager.load_balancer.load_balancer_set( @@ -484,17 +499,23 @@ class UnsetLoadBalancer(command.Command): help='Wait for action to complete', ) + _tag.add_tag_option_to_parser_for_unset(parser, 'load balancer') + return parser def take_action(self, parsed_args): unset_args = v2_utils.get_unsets(parsed_args) - if not unset_args: + if not unset_args and not parsed_args.all_tag: return lb_id = v2_utils.get_resource_id( self.app.client_manager.load_balancer.load_balancer_list, 'loadbalancers', parsed_args.loadbalancer) + v2_utils.set_tags_for_unset( + self.app.client_manager.load_balancer.load_balancer_show, + lb_id, unset_args, clear_tags=parsed_args.all_tag) + body = {'loadbalancer': unset_args} self.app.client_manager.load_balancer.load_balancer_set( diff --git a/octaviaclient/osc/v2/member.py b/octaviaclient/osc/v2/member.py index 0a54065..16be0a4 100644 --- a/octaviaclient/osc/v2/member.py +++ b/octaviaclient/osc/v2/member.py @@ -21,6 +21,7 @@ from cliff import lister from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from osc_lib.utils import tags as _tag from oslo_utils import uuidutils from octaviaclient.osc.v2 import constants as const @@ -40,16 +41,17 @@ class ListMember(lister.Lister): help="Pool name or ID to list the members of." ) + _tag.add_tag_filtering_option_to_parser(parser, 'member') + return parser def take_action(self, parsed_args): columns = const.MEMBER_COLUMNS attrs = v2_utils.get_member_attrs(self.app.client_manager, parsed_args) - pool_id = attrs.pop('pool_id') data = self.app.client_manager.load_balancer.member_list( - pool_id=pool_id) + **attrs) return (columns, (utils.get_dict_properties( @@ -97,8 +99,10 @@ class ShowMember(command.ShowOne): data = self.app.client_manager.load_balancer.member_show( pool_id=pool_id, member_id=member_id) + formatters = {'tags': v2_utils.format_list_flat} + return (rows, (utils.get_dict_properties( - data, rows, formatters={}))) + data, rows, formatters=formatters))) class CreateMember(command.ShowOne): @@ -189,6 +193,9 @@ class CreateMember(command.ShowOne): help='Wait for action to complete', ) + _tag.add_tag_option_to_parser_for_create( + parser, 'member') + return parser def take_action(self, parsed_args): @@ -218,9 +225,11 @@ class CreateMember(command.ShowOne): pool_id, data['member']['id'])) } + formatters = {'tags': v2_utils.format_list_flat} + return (rows, (utils.get_dict_properties( - data['member'], rows, formatters={}))) + data['member'], rows, formatters=formatters))) class SetMember(command.Command): @@ -294,6 +303,8 @@ class SetMember(command.Command): help='Wait for action to complete', ) + _tag.add_tag_option_to_parser_for_set(parser, 'member') + return parser def take_action(self, parsed_args): @@ -303,6 +314,14 @@ class SetMember(command.Command): pool_id = attrs.pop('pool_id') member_id = attrs.pop('member_id') + + member_show = functools.partial( + self.app.client_manager.load_balancer.member_show, + pool_id + ) + v2_utils.set_tags_for_set( + member_show, member_id, attrs, clear_tags=parsed_args.no_tag) + post_data = {"member": attrs} self.app.client_manager.load_balancer.member_set( @@ -312,10 +331,6 @@ class SetMember(command.Command): ) if parsed_args.wait: - member_show = functools.partial( - self.app.client_manager.load_balancer.member_show, - pool_id - ) v2_utils.wait_for_active( status_f=member_show, res_id=member_id @@ -414,32 +429,39 @@ class UnsetMember(command.Command): action='store_true', help='Wait for action to complete', ) + _tag.add_tag_option_to_parser_for_unset(parser, 'member') + return parser def take_action(self, parsed_args): unset_args = v2_utils.get_unsets(parsed_args) - if not unset_args: + if not unset_args and not parsed_args.all_tag: return pool_id = v2_utils.get_resource_id( self.app.client_manager.load_balancer.pool_list, 'pools', parsed_args.pool) + member_show = functools.partial( + self.app.client_manager.load_balancer.member_show, + pool_id + ) + member_dict = {'pool_id': pool_id, 'member_id': parsed_args.member} member_id = v2_utils.get_resource_id( self.app.client_manager.load_balancer.member_list, 'members', member_dict) + v2_utils.set_tags_for_unset( + member_show, member_id, unset_args, + clear_tags=parsed_args.all_tag) + body = {'member': unset_args} self.app.client_manager.load_balancer.member_set( pool_id=pool_id, member_id=member_id, json=body) if parsed_args.wait: - member_show = functools.partial( - self.app.client_manager.load_balancer.member_show, - pool_id - ) v2_utils.wait_for_active( status_f=member_show, res_id=member_id diff --git a/octaviaclient/osc/v2/pool.py b/octaviaclient/osc/v2/pool.py index f7e5cb9..cc86501 100644 --- a/octaviaclient/osc/v2/pool.py +++ b/octaviaclient/osc/v2/pool.py @@ -19,6 +19,7 @@ from cliff import lister from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils +from osc_lib.utils import tags as _tag from oslo_utils import uuidutils from octaviaclient.osc.v2 import constants as const @@ -146,6 +147,9 @@ class CreatePool(command.ShowOne): "by the pool (can be set multiple times)." ) + _tag.add_tag_option_to_parser_for_create( + parser, 'pool') + return parser def take_action(self, parsed_args): @@ -171,7 +175,8 @@ class CreatePool(command.ShowOne): formatters = {'loadbalancers': v2_utils.format_list, 'members': v2_utils.format_list, 'listeners': v2_utils.format_list, - 'session_persistence': v2_utils.format_hash} + 'session_persistence': v2_utils.format_hash, + 'tags': v2_utils.format_list_flat} return (rows, (utils.get_dict_properties( data['pool'], rows, formatters=formatters, @@ -222,6 +227,8 @@ class ListPool(lister.Lister): help="Filter by load balancer (name or ID).", ) + _tag.add_tag_filtering_option_to_parser(parser, 'pool') + return parser def take_action(self, parsed_args): @@ -271,7 +278,8 @@ class ShowPool(command.ShowOne): formatters = {'loadbalancers': v2_utils.format_list, 'members': v2_utils.format_list, 'listeners': v2_utils.format_list, - 'session_persistence': v2_utils.format_hash} + 'session_persistence': v2_utils.format_hash, + 'tags': v2_utils.format_list_flat} return (rows, (utils.get_dict_properties( data, rows, formatters=formatters, @@ -382,12 +390,18 @@ class SetPool(command.Command): ) + _tag.add_tag_option_to_parser_for_set(parser, 'pool') + return parser def take_action(self, parsed_args): attrs = v2_utils.get_pool_attrs(self.app.client_manager, parsed_args) pool_id = attrs.pop('pool_id') + v2_utils.set_tags_for_set( + self.app.client_manager.load_balancer.pool_show, + pool_id, attrs, clear_tags=parsed_args.no_tag) + body = {'pool': attrs} self.app.client_manager.load_balancer.pool_set( @@ -458,17 +472,24 @@ class UnsetPool(command.Command): action='store_true', help='Wait for action to complete', ) + + _tag.add_tag_option_to_parser_for_unset(parser, 'pool') + return parser def take_action(self, parsed_args): unset_args = v2_utils.get_unsets(parsed_args) - if not unset_args: + if not unset_args and not parsed_args.all_tag: return pool_id = v2_utils.get_resource_id( self.app.client_manager.load_balancer.pool_list, 'pools', parsed_args.pool) + v2_utils.set_tags_for_unset( + self.app.client_manager.load_balancer.pool_show, + pool_id, unset_args, clear_tags=parsed_args.all_tag) + body = {'pool': unset_args} self.app.client_manager.load_balancer.pool_set( diff --git a/octaviaclient/osc/v2/utils.py b/octaviaclient/osc/v2/utils.py index b99090e..bc9c128 100644 --- a/octaviaclient/osc/v2/utils.py +++ b/octaviaclient/osc/v2/utils.py @@ -124,6 +124,16 @@ def get_resource_id(resource, resource_name, name): raise osc_exc.CommandError(msg) from e +def add_tags_attr_map(attr_map): + tags_attr_map = { + 'tags': ('tags', list), + 'any_tags': ('tags-any', list), + 'not_tags': ('not-tags', list), + 'not_any_tags': ('not-tags-any', list), + } + attr_map.update(tags_attr_map) + + def get_loadbalancer_attrs(client_manager, parsed_args): attr_map = { 'name': ('name', str), @@ -174,8 +184,8 @@ def get_loadbalancer_attrs(client_manager, parsed_args): client_manager.load_balancer.flavor_list ), 'availability_zone': ('availability_zone', str), - } + add_tags_attr_map(attr_map) _attrs = vars(parsed_args) attrs = _map_attrs(_attrs, attr_map) @@ -229,6 +239,7 @@ def get_listener_attrs(client_manager, parsed_args): 'tls_versions': ('tls_versions', list), 'alpn_protocols': ('alpn_protocols', list), } + add_tags_attr_map(attr_map) _attrs = vars(parsed_args) attrs = _map_attrs(_attrs, attr_map) @@ -277,6 +288,7 @@ def get_pool_attrs(client_manager, parsed_args): 'tls_ciphers': ('tls_ciphers', str), 'tls_versions': ('tls_versions', list), } + add_tags_attr_map(attr_map) _attrs = vars(parsed_args) attrs = _map_attrs(_attrs, attr_map) @@ -318,6 +330,7 @@ def get_member_attrs(client_manager, parsed_args): 'enable': ('admin_state_up', lambda x: True), 'disable': ('admin_state_up', lambda x: False), } + add_tags_attr_map(attr_map) _attrs = vars(parsed_args) attrs = _map_attrs(_attrs, attr_map) @@ -357,6 +370,7 @@ def get_l7policy_attrs(client_manager, parsed_args): 'enable': ('admin_state_up', lambda x: True), 'disable': ('admin_state_up', lambda x: False) } + add_tags_attr_map(attr_map) _attrs = vars(parsed_args) attrs = _map_attrs(_attrs, attr_map) @@ -391,6 +405,7 @@ def get_l7rule_attrs(client_manager, parsed_args): 'enable': ('admin_state_up', lambda x: True), 'disable': ('admin_state_up', lambda x: False) } + add_tags_attr_map(attr_map) _attrs = vars(parsed_args) attrs = _map_attrs(_attrs, attr_map) @@ -429,6 +444,7 @@ def get_health_monitor_attrs(client_manager, parsed_args): 'http_version': ('http_version', float), 'domain_name': ('domain_name', str) } + add_tags_attr_map(attr_map) _attrs = vars(parsed_args) attrs = _map_attrs(_attrs, attr_map) @@ -604,8 +620,13 @@ def _format_str_if_need_treat_unset(data): def get_unsets(parsed_args): - return {arg: None for arg, value in vars(parsed_args).items() if - value is True and arg != 'wait'} + unsets = {} + for arg, value in vars(parsed_args).items(): + if value and arg == 'tags': + unsets[arg] = value + elif value is True and arg not in ('wait', 'all_tag'): + unsets[arg] = None + return unsets def wait_for_active(status_f, res_id): @@ -642,3 +663,23 @@ def wait_for_delete(status_f, res_id, except exceptions.OctaviaClientException as e: if e.code != 404: raise + + +def set_tags_for_set(resource_get, resource_id, attrs, clear_tags=False): + if attrs.get('tags'): + resource = resource_get(resource_id) + tags = set([] if clear_tags else resource['tags']) + tags |= set(attrs['tags']) + attrs['tags'] = list(tags) + elif clear_tags: + attrs['tags'] = [] + + +def set_tags_for_unset(resource_get, resource_id, attrs, clear_tags=False): + if clear_tags: + attrs['tags'] = [] + elif attrs.get('tags'): + resource = resource_get(resource_id) + tags = set(resource['tags']) + tags -= set(attrs['tags']) + attrs['tags'] = list(tags) diff --git a/octaviaclient/tests/unit/osc/v2/constants.py b/octaviaclient/tests/unit/osc/v2/constants.py index 9c0f14e..c103ede 100644 --- a/octaviaclient/tests/unit/osc/v2/constants.py +++ b/octaviaclient/tests/unit/osc/v2/constants.py @@ -81,7 +81,8 @@ LISTENER_ATTRS = { "allowed_cidrs": ['192.0.2.0/24', '198.51.100.0/24'], 'tls_ciphers': "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256", 'tls_versions': ['TLSv1.1', 'TLSv1.2'], - 'alpn_protocols': ['h2', 'http/1.1'] + 'alpn_protocols': ['h2', 'http/1.1'], + "tags": ["foo", "bar"] } LOADBALANCER_ATTRS = { @@ -96,6 +97,7 @@ LOADBALANCER_ATTRS = { "operating_status": "ONLINE", "provider": "octavia", "flavor_id": uuidutils.generate_uuid(dashed=True), + "tags": ["foo", "bar"] } L7POLICY_ATTRS = { @@ -159,7 +161,8 @@ POOL_ATTRS = { "crl_container_ref": uuidutils.generate_uuid(), "tls_enabled": True, "tls_ciphers": "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256", - "tls_versions": ['TLSv1.1', 'TLSv1.2'] + "tls_versions": ['TLSv1.1', 'TLSv1.2'], + "tags": ["foo", "bar"], } QUOTA_ATTRS = { diff --git a/octaviaclient/tests/unit/osc/v2/test_health_monitor.py b/octaviaclient/tests/unit/osc/v2/test_health_monitor.py index 15c4558..58ad54e 100644 --- a/octaviaclient/tests/unit/osc/v2/test_health_monitor.py +++ b/octaviaclient/tests/unit/osc/v2/test_health_monitor.py @@ -60,6 +60,62 @@ class TestHealthMonitorList(TestHealthMonitor): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) + def test_health_monitor_list_with_tags(self): + arglist = ['--tags', 'foo,bar'] + verifylist = [('tags', ['foo', 'bar'])] + expected_attrs = { + 'tags': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.health_monitor_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_health_monitor_list_with_any_tags(self): + arglist = ['--any-tags', 'foo,bar'] + verifylist = [('any_tags', ['foo', 'bar'])] + expected_attrs = { + 'tags-any': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.health_monitor_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_health_monitor_list_with_not_tags(self): + arglist = ['--not-tags', 'foo,bar'] + verifylist = [('not_tags', ['foo', 'bar'])] + expected_attrs = { + 'not-tags': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.health_monitor_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_health_monitor_list_with_not_any_tags(self): + arglist = ['--not-any-tags', 'foo,bar'] + verifylist = [('not_any_tags', ['foo', 'bar'])] + expected_attrs = { + 'not-tags-any': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.health_monitor_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + class TestHealthMonitorDelete(TestHealthMonitor): @@ -189,6 +245,32 @@ class TestHealthMonitorCreate(TestHealthMonitor): sleep_time=mock.ANY, status_field='provisioning_status') + @mock.patch('octaviaclient.osc.v2.utils.get_health_monitor_attrs') + def test_health_monitor_create_with_tag(self, mock_attrs): + mock_attrs.return_value = self.hm_info + arglist = ['mock_pool_id', + '--name', self._hm.name, + '--delay', str(self._hm.delay), + '--timeout', str(self._hm.timeout), + '--max-retries', str(self._hm.max_retries), + '--type', self._hm.type.lower(), + '--tag', 'foo'] + + verifylist = [ + ('pool', 'mock_pool_id'), + ('name', self._hm.name), + ('delay', str(self._hm.delay)), + ('timeout', str(self._hm.timeout)), + ('max_retries', self._hm.max_retries), + ('type', self._hm.type), + ('tags', ['foo']) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.health_monitor_create.assert_called_with( + json={'healthmonitor': self.hm_info}) + class TestHealthMonitorShow(TestHealthMonitor): @@ -257,6 +339,43 @@ class TestHealthMonitorSet(TestHealthMonitor): sleep_time=mock.ANY, status_field='provisioning_status') + def test_health_monitor_set_tag(self): + self.api_mock.health_monitor_show.return_value = { + 'tags': ['foo'] + } + arglist = [self._hm.id, '--tag', 'bar'] + verifylist = [ + ('health_monitor', self._hm.id), + ('tags', ['bar']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.health_monitor_set.assert_called_once() + kwargs = self.api_mock.health_monitor_set.mock_calls[0][2] + tags = kwargs['json']['healthmonitor']['tags'] + self.assertEqual(2, len(tags)) + self.assertIn('foo', tags) + self.assertIn('bar', tags) + + def test_health_monitor_set_tag_no_tag(self): + self.api_mock.health_monitor_show.return_value = { + 'tags': ['foo'] + } + arglist = [self._hm.id, '--tag', 'bar', '--no-tag'] + verifylist = [ + ('health_monitor', self._hm.id), + ('tags', ['bar']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.health_monitor_set.assert_called_once_with( + self._hm.id, + json={"healthmonitor": {"tags": ['bar']}}) + class TestHealthMonitorUnset(TestHealthMonitor): PARAMETERS = ('name', 'domain_name', 'expected_codes', 'http_method', @@ -350,3 +469,41 @@ class TestHealthMonitorUnset(TestHealthMonitor): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.api_mock.health_monitor_set.assert_not_called() + + def test_health_monitor_unset_tag(self): + self.api_mock.health_monitor_set.reset_mock() + self.api_mock.health_monitor_show.return_value = { + 'tags': ['foo', 'bar'] + } + + arglist = [self._hm.id, '--tag', 'foo'] + verifylist = [ + ('health_monitor', self._hm.id), + ('tags', ['foo']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.health_monitor_set.assert_called_once_with( + self._hm.id, + json={"healthmonitor": {"tags": ['bar']}}) + + def test_health_monitor_unset_all_tag(self): + self.api_mock.health_monitor_set.reset_mock() + self.api_mock.health_monitor_show.return_value = { + 'tags': ['foo', 'bar'] + } + + arglist = [self._hm.id, '--all-tag'] + verifylist = [ + ('health_monitor', self._hm.id), + ('all_tag', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.health_monitor_set.assert_called_once_with( + self._hm.id, + json={"healthmonitor": {"tags": []}}) diff --git a/octaviaclient/tests/unit/osc/v2/test_l7policy.py b/octaviaclient/tests/unit/osc/v2/test_l7policy.py index 152c0c3..ac6b3a5 100644 --- a/octaviaclient/tests/unit/osc/v2/test_l7policy.py +++ b/octaviaclient/tests/unit/osc/v2/test_l7policy.py @@ -76,6 +76,62 @@ class TestL7PolicyList(TestL7Policy): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) + def test_l7policy_list_with_tags(self): + arglist = ['--tags', 'foo,bar'] + verifylist = [('tags', ['foo', 'bar'])] + expected_attrs = { + 'tags': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.l7policy_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_l7policy_list_with_any_tags(self): + arglist = ['--any-tags', 'foo,bar'] + verifylist = [('any_tags', ['foo', 'bar'])] + expected_attrs = { + 'tags-any': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.l7policy_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_l7policy_list_with_not_tags(self): + arglist = ['--not-tags', 'foo,bar'] + verifylist = [('not_tags', ['foo', 'bar'])] + expected_attrs = { + 'not-tags': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.l7policy_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_l7policy_list_with_not_any_tags(self): + arglist = ['--not-any-tags', 'foo,bar'] + verifylist = [('not_any_tags', ['foo', 'bar'])] + expected_attrs = { + 'not-tags-any': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.l7policy_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + class TestL7PolicyDelete(TestL7Policy): @@ -205,6 +261,40 @@ class TestL7PolicyCreate(TestL7Policy): sleep_time=mock.ANY, status_field='provisioning_status') + @mock.patch('octaviaclient.osc.v2.utils.get_l7policy_attrs') + def test_l7policy_create_with_tag(self, mock_attrs): + mock_attrs.return_value = { + 'listener_id': self._l7po.listener_id, + 'name': self._l7po.name, + 'action': 'REDIRECT_TO_POOL', + 'redirect_pool_id': self._l7po.redirect_pool_id, + 'tags': ['foo'] + } + + arglist = ['mock_li_id', + '--name', self._l7po.name, + '--action', 'REDIRECT_TO_POOL'.lower(), + '--redirect-pool', self._l7po.redirect_pool_id, + '--tag', 'foo'] + + verifylist = [ + ('listener', 'mock_li_id'), + ('name', self._l7po.name), + ('action', 'REDIRECT_TO_POOL'), + ('redirect_pool', self._l7po.redirect_pool_id), + ('tags', ['foo']) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.l7policy_create.assert_called_with( + json={'l7policy': { + 'listener_id': self._l7po.listener_id, + 'name': self._l7po.name, + 'action': 'REDIRECT_TO_POOL', + 'redirect_pool_id': self._l7po.redirect_pool_id, + 'tags': ['foo']}}) + class TestL7PolicyShow(TestL7Policy): @@ -268,6 +358,44 @@ class TestL7PolicySet(TestL7Policy): sleep_time=mock.ANY, status_field='provisioning_status') + def test_l7policy_set_tag(self): + self.api_mock.l7policy_show.return_value = { + 'tags': ['foo'] + } + arglist = [self._l7po.id, '--tag', 'bar'] + verifylist = [ + ('l7policy', self._l7po.id), + ('tags', ['bar']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.l7policy_set.assert_called_once() + kwargs = self.api_mock.l7policy_set.mock_calls[0][2] + tags = kwargs['json']['l7policy']['tags'] + self.assertEqual(2, len(tags)) + self.assertIn('foo', tags) + self.assertIn('bar', tags) + + def test_l7policy_set_tag_no_tag(self): + self.api_mock.l7policy_show.return_value = { + 'tags': ['foo'] + } + arglist = [self._l7po.id, '--tag', 'bar', '--no-tag'] + verifylist = [ + ('l7policy', self._l7po.id), + ('tags', ['bar']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.l7policy_set.assert_called_once_with( + self._l7po.id, + json={'l7policy': {'tags': ['bar']}} + ) + class TestL7PolicyUnset(TestL7Policy): PARAMETERS = ('name', 'description', 'redirect_http_code') @@ -348,3 +476,43 @@ class TestL7PolicyUnset(TestL7Policy): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.api_mock.l7policy_set.assert_not_called() + + def test_l7policy_unset_tag(self): + self.api_mock.l7policy_set.reset_mock() + self.api_mock.l7policy_show.return_value = { + 'tags': ['foo', 'bar'] + } + + arglist = [self._l7po.id, '--tag', 'foo'] + verifylist = [ + ('l7policy', self._l7po.id), + ('tags', ['foo']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.l7policy_set.assert_called_once_with( + self._l7po.id, + json={'l7policy': {'tags': ['bar']}} + ) + + def test_l7policy_unset_all_tag(self): + self.api_mock.l7policy_set.reset_mock() + self.api_mock.l7policy_show.return_value = { + 'tags': ['foo', 'bar'] + } + + arglist = [self._l7po.id, '--all-tag'] + verifylist = [ + ('l7policy', self._l7po.id), + ('all_tag', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.l7policy_set.assert_called_once_with( + self._l7po.id, + json={'l7policy': {'tags': []}} + ) diff --git a/octaviaclient/tests/unit/osc/v2/test_l7rule.py b/octaviaclient/tests/unit/osc/v2/test_l7rule.py index 9ff568a..3c3eeac 100644 --- a/octaviaclient/tests/unit/osc/v2/test_l7rule.py +++ b/octaviaclient/tests/unit/osc/v2/test_l7rule.py @@ -62,6 +62,74 @@ class TestL7RuleList(TestL7Rule): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) + def test_l7rule_list_with_tags(self): + arglist = [self._l7po.id, + '--tags', 'foo,bar'] + verifylist = [('l7policy', self._l7po.id), + ('tags', ['foo', 'bar'])] + expected_attrs = { + 'l7policy_id': self._l7po.id, + 'tags': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.l7rule_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_l7rule_list_with_any_tags(self): + arglist = [self._l7po.id, + '--any-tags', 'foo,bar'] + verifylist = [('l7policy', self._l7po.id), + ('any_tags', ['foo', 'bar'])] + expected_attrs = { + 'l7policy_id': self._l7po.id, + 'tags-any': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.l7rule_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_l7rule_list_with_not_tags(self): + arglist = [self._l7po.id, + '--not-tags', 'foo,bar'] + verifylist = [('l7policy', self._l7po.id), + ('not_tags', ['foo', 'bar'])] + expected_attrs = { + 'l7policy_id': self._l7po.id, + 'not-tags': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.l7rule_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_l7rule_list_with_not_any_tags(self): + arglist = [self._l7po.id, + '--not-any-tags', 'foo,bar'] + verifylist = [('l7policy', self._l7po.id), + ('not_any_tags', ['foo', 'bar'])] + expected_attrs = { + 'l7policy_id': self._l7po.id, + 'not-tags-any': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.l7rule_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + class TestL7RuleDelete(TestL7Rule): @@ -110,7 +178,7 @@ class TestL7RuleDelete(TestL7Rule): ) mock_wait.assert_called_once_with( manager=mock.ANY, - res_id=self._l7ru.id, + res_id=self._l7po.id, sleep_time=mock.ANY, status_field='provisioning_status') @@ -199,6 +267,40 @@ class TestL7RuleCreate(TestL7Rule): sleep_time=mock.ANY, status_field='provisioning_status') + @mock.patch('octaviaclient.osc.v2.utils.get_l7rule_attrs') + def test_l7rule_create_with_tag(self, mock_attrs): + mock_attrs.return_value = { + 'l7policy_id': self._l7po.id, + 'compare-type': 'ENDS_WITH', + 'value': '.example.com', + 'type': 'HOST_NAME', + 'tags': ['foo'] + } + arglist = [self._l7po.id, + '--compare-type', 'ENDS_WITH', + '--value', '.example.com', + '--type', 'HOST_NAME'.lower(), + '--tag', 'foo'] + + verifylist = [ + ('l7policy', self._l7po.id), + ('compare_type', 'ENDS_WITH'), + ('value', '.example.com'), + ('type', 'HOST_NAME'), + ('tags', ['foo']) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.l7rule_create.assert_called_with( + l7policy_id=self._l7po.id, + json={'rule': { + 'compare-type': 'ENDS_WITH', + 'value': '.example.com', + 'type': 'HOST_NAME', + 'tags': ['foo']} + }) + class TestL7RuleShow(TestL7Rule): @@ -285,10 +387,72 @@ class TestL7RuleSet(TestL7Rule): json={'rule': {'admin_state_up': False}}) mock_wait.assert_called_once_with( status_f=mock.ANY, - res_id=self._l7ru.id, + res_id=self._l7po.id, sleep_time=mock.ANY, status_field='provisioning_status') + @mock.patch('octaviaclient.osc.v2.utils.get_l7rule_attrs') + def test_l7rule_set_tag(self, mock_attrs): + self.api_mock.l7rule_show.return_value = { + 'tags': ['foo'] + } + mock_attrs.return_value = { + 'l7policy_id': self._l7po.id, + 'l7rule_id': self._l7ru.id, + 'tags': ['bar'] + } + arglist = [ + self._l7po.id, + self._l7ru.id, + '--tag', 'bar' + ] + verifylist = [ + ('l7policy', self._l7po.id), + ('l7rule', self._l7ru.id), + ('tags', ['bar']) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.l7rule_set.assert_called_once() + kwargs = self.api_mock.l7rule_set.mock_calls[0][2] + tags = kwargs['json']['rule']['tags'] + self.assertEqual(2, len(tags)) + self.assertIn('foo', tags) + self.assertIn('bar', tags) + + @mock.patch('octaviaclient.osc.v2.utils.get_l7rule_attrs') + def test_l7rule_set_tag_no_tag(self, mock_attrs): + self.api_mock.l7rule_show.return_value = { + 'tags': ['foo'] + } + mock_attrs.return_value = { + 'l7policy_id': self._l7po.id, + 'l7rule_id': self._l7ru.id, + 'tags': ['bar'] + } + arglist = [ + self._l7po.id, + self._l7ru.id, + '--tag', 'bar', '--no-tag' + ] + verifylist = [ + ('l7policy', self._l7po.id), + ('l7rule', self._l7ru.id), + ('tags', ['bar']), + ('no_tag', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.l7rule_set.assert_called_once_with( + l7policy_id=self._l7po.id, + l7rule_id=self._l7ru.id, + json={'rule': {'tags': ['bar']}} + ) + class TestL7RuleUnset(TestL7Rule): PARAMETERS = ('invert', 'key') @@ -340,7 +504,7 @@ class TestL7RuleUnset(TestL7Rule): l7policy_id=self._l7po.id, l7rule_id=self._l7ru.id, json=ref_body) mock_wait.assert_called_once_with( status_f=mock.ANY, - res_id=self._l7ru.id, + res_id=self._l7po.id, sleep_time=mock.ANY, status_field='provisioning_status') @@ -367,3 +531,63 @@ class TestL7RuleUnset(TestL7Rule): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.api_mock.l7rule_set.assert_not_called() + + @mock.patch('octaviaclient.osc.v2.utils.get_l7rule_attrs') + def test_l7rule_unset_tag(self, mock_attrs): + self.api_mock.l7rule_show.return_value = { + 'tags': ['foo', 'bar'] + } + mock_attrs.return_value = { + 'l7policy_id': self._l7po.id, + 'l7rule_id': self._l7ru.id, + 'tags': ['foo', 'bar'] + } + arglist = [ + self._l7po.id, + self._l7ru.id, + '--tag', 'foo' + ] + verifylist = [ + ('l7policy', self._l7po.id), + ('l7rule_id', self._l7ru.id), + ('tags', ['foo']) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.l7rule_set.assert_called_once_with( + l7policy_id=self._l7po.id, + l7rule_id=self._l7ru.id, + json={'rule': {'tags': ['bar']}} + ) + + @mock.patch('octaviaclient.osc.v2.utils.get_l7rule_attrs') + def test_l7rule_unset_all_tags(self, mock_attrs): + self.api_mock.l7rule_show.return_value = { + 'tags': ['foo', 'bar'] + } + mock_attrs.return_value = { + 'l7policy_id': self._l7po.id, + 'l7rule_id': self._l7ru.id, + 'tags': ['foo', 'bar'] + } + arglist = [ + self._l7po.id, + self._l7ru.id, + '--all-tag' + ] + verifylist = [ + ('l7policy', self._l7po.id), + ('l7rule_id', self._l7ru.id), + ('all_tag', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.l7rule_set.assert_called_once_with( + l7policy_id=self._l7po.id, + l7rule_id=self._l7ru.id, + json={'rule': {'tags': []}} + ) diff --git a/octaviaclient/tests/unit/osc/v2/test_listener.py b/octaviaclient/tests/unit/osc/v2/test_listener.py index 32167f3..e40f9e9 100644 --- a/octaviaclient/tests/unit/osc/v2/test_listener.py +++ b/octaviaclient/tests/unit/osc/v2/test_listener.py @@ -67,6 +67,60 @@ class TestListenerList(TestListener): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) + def test_listener_list_with_tags(self): + arglist = ['--tags', 'foo,bar'] + verifylist = [('tags', ['foo', 'bar'])] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.api_mock.listener_list.assert_called_with( + tags=['foo', 'bar']) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_listener_list_with_any_tags(self): + arglist = ['--any-tags', 'foo,bar'] + verifylist = [('any_tags', ['foo', 'bar'])] + expected_attrs = { + "tags-any": ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.api_mock.listener_list.assert_called_with(**expected_attrs) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_listener_list_with_not_tags(self): + arglist = ['--not-tags', 'foo,bar'] + verifylist = [('not_tags', ['foo', 'bar'])] + expected_attrs = { + "not-tags": ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.api_mock.listener_list.assert_called_with(**expected_attrs) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_listener_list_with_not_any_tags(self): + arglist = ['--not-any-tags', 'foo,bar'] + verifylist = [('not_any_tags', ['foo', 'bar'])] + expected_attrs = { + "not-tags-any": ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + self.api_mock.listener_list.assert_called_with(**expected_attrs) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + class TestListenerDelete(TestListener): @@ -255,6 +309,27 @@ class TestListenerCreate(TestListener): self.api_mock.listener_create.assert_called_with( json={'listener': self.listener_info}) + @mock.patch('octaviaclient.osc.v2.utils.get_listener_attrs') + def test_listener_create_with_tag(self, mock_client): + mock_client.return_value = self.listener_info + arglist = ['mock_lb_id', + '--name', self._listener.name, + '--protocol', 'HTTP', + '--protocol-port', '80', + '--tag', 'foo'] + verifylist = [ + ('loadbalancer', 'mock_lb_id'), + ('name', self._listener.name), + ('protocol', 'HTTP'), + ('protocol_port', 80), + ('tags', ['foo']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.listener_create.assert_called_with( + json={'listener': self.listener_info}) + class TestListenerShow(TestListener): @@ -369,6 +444,44 @@ class TestListenerSet(TestListener): sleep_time=mock.ANY, status_field='provisioning_status') + def test_listener_set_tag(self): + self.api_mock.listener_show.return_value = { + 'tags': ['foo'] + } + arglist = [self._listener.id, '--tag', 'bar'] + verifylist = [ + ('listener', self._listener.id), + ('tags', ['bar']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.listener_set.assert_called_once() + kwargs = self.api_mock.listener_set.mock_calls[0][2] + tags = kwargs['json']['listener']['tags'] + self.assertEqual(2, len(tags)) + self.assertIn('foo', tags) + self.assertIn('bar', tags) + + def test_listener_set_tag_no_tag(self): + self.api_mock.listener_show.return_value = { + 'tags': ['foo'] + } + arglist = [self._listener.id, '--tag', 'bar', '--no-tag'] + verifylist = [ + ('listener', self._listener.id), + ('tags', ['bar']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.listener_set.assert_called_once_with( + self._listener.id, + json={"listener": {"tags": ['bar']}} + ) + class TestListenerStatsShow(TestListener): @@ -529,3 +642,43 @@ class TestListenerUnset(TestListener): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.api_mock.listener_set.assert_not_called() + + def test_listener_unset_tag(self): + self.api_mock.listener_set.reset_mock() + self.api_mock.listener_show.return_value = { + 'tags': ['foo', 'bar'] + } + + arglist = [self._listener.id, '--tag', 'foo'] + verifylist = [ + ('listener', self._listener.id), + ('tags', ['foo']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.listener_set.assert_called_once_with( + self._listener.id, + json={"listener": {"tags": ['bar']}} + ) + + def test_listener_unset_all_tag(self): + self.api_mock.listener_set.reset_mock() + self.api_mock.listener_show.return_value = { + 'tags': ['foo', 'bar'] + } + + arglist = [self._listener.id, '--all-tag'] + verifylist = [ + ('listener', self._listener.id), + ('all_tag', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.listener_set.assert_called_once_with( + self._listener.id, + json={"listener": {"tags": []}} + ) diff --git a/octaviaclient/tests/unit/osc/v2/test_load_balancer.py b/octaviaclient/tests/unit/osc/v2/test_load_balancer.py index 1393ba2..f37d96d 100644 --- a/octaviaclient/tests/unit/osc/v2/test_load_balancer.py +++ b/octaviaclient/tests/unit/osc/v2/test_load_balancer.py @@ -196,6 +196,74 @@ class TestLoadBalancerList(TestLoadBalancer): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) + @mock.patch('octaviaclient.osc.v2.utils.get_loadbalancer_attrs') + def test_load_balancer_list_with_tags(self, mock_client): + mock_client.return_value = { + 'tags': self._lb.tags, + } + arglist = [ + '--tags', ",".join(self._lb.tags), + ] + verify_list = [ + ('tags', self._lb.tags), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verify_list) + columns, data = self.cmd.take_action(parsed_args) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + @mock.patch('octaviaclient.osc.v2.utils.get_loadbalancer_attrs') + def test_load_balancer_list_with_any_tags(self, mock_client): + mock_client.return_value = { + 'tags': self._lb.tags, + } + arglist = [ + '--any-tags', ",".join(self._lb.tags), + ] + verify_list = [ + ('any_tags', self._lb.tags), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verify_list) + columns, data = self.cmd.take_action(parsed_args) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + @mock.patch('octaviaclient.osc.v2.utils.get_loadbalancer_attrs') + def test_load_balancer_list_with_not_tags(self, mock_client): + mock_client.return_value = { + 'tags': self._lb.tags[0], + } + arglist = [ + '--any-tags', ",".join(self._lb.tags), + ] + verify_list = [ + ('any_tags', self._lb.tags), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verify_list) + columns, data = self.cmd.take_action(parsed_args) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + @mock.patch('octaviaclient.osc.v2.utils.get_loadbalancer_attrs') + def test_load_balancer_list_with_not_any_tags(self, mock_client): + mock_client.return_value = { + 'tags': self._lb.tags[0], + } + arglist = [ + '--not-any-tags', ",".join(self._lb.tags), + ] + verify_list = [ + ('not_any_tags', self._lb.tags), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verify_list) + columns, data = self.cmd.take_action(parsed_args) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + class TestLoadBalancerDelete(TestLoadBalancer): @@ -353,6 +421,31 @@ class TestLoadBalancerCreate(TestLoadBalancer): self.api_mock.load_balancer_create.assert_called_with( json={'loadbalancer': lb_info}) + @mock.patch('octaviaclient.osc.v2.utils.get_loadbalancer_attrs') + def test_load_balancer_create_with_tags(self, mock_client): + lb_info = copy.deepcopy(self.lb_info) + lb_info.update({'tags': self._lb.tags}) + mock_client.return_value = lb_info + + arglist = [ + '--name', self._lb.name, + '--vip-network-id', self._lb.vip_network_id, + '--project', self._lb.project_id, + '--tag', self._lb.tags[0], + '--tag', self._lb.tags[1], + ] + verifylist = [ + ('name', self._lb.name), + ('vip_network_id', self._lb.vip_network_id), + ('project', self._lb.project_id), + ('tags', self._lb.tags), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.load_balancer_create.assert_called_with( + json={'loadbalancer': lb_info}) + @mock.patch('octaviaclient.osc.v2.utils.get_loadbalancer_attrs') def test_load_balancer_create_missing_args(self, mock_client): attrs_list = self.lb_info @@ -474,6 +567,61 @@ class TestLoadBalancerSet(TestLoadBalancer): sleep_time=mock.ANY, status_field='provisioning_status') + @mock.patch('octaviaclient.osc.v2.utils.get_loadbalancer_attrs') + def test_load_balancer_set_tag(self, mock_attrs): + self.api_mock.load_balancer_show.return_value = { + 'tags': ['foo'] + } + + mock_attrs.return_value = { + 'loadbalancer_id': self._lb.id, + 'tags': ['bar'] + } + arglist = [self._lb.id, '--tag', 'bar'] + verifylist = [ + ('loadbalancer', self._lb.id), + ('tags', ['bar']) + ] + + try: + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + except Exception as e: + self.fail("%s raised unexpectedly" % e) + + self.api_mock.load_balancer_set.assert_called_once() + kwargs = self.api_mock.load_balancer_set.mock_calls[0][2] + tags = kwargs['json']['loadbalancer']['tags'] + self.assertEqual(2, len(tags)) + self.assertIn('foo', tags) + self.assertIn('bar', tags) + + @mock.patch('octaviaclient.osc.v2.utils.get_loadbalancer_attrs') + def test_load_balancer_set_tag_no_tag(self, mock_attrs): + self.api_mock.load_balancer_show.return_value = { + 'tags': ['foo'] + } + + mock_attrs.return_value = { + 'loadbalancer_id': self._lb.id, + 'tags': ['bar'] + } + arglist = [self._lb.id, '--tag', 'bar', '--no-tag'] + verifylist = [ + ('loadbalancer', self._lb.id), + ('tags', ['bar']) + ] + + try: + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + except Exception as e: + self.fail("%s raised unexpectedly" % e) + + self.api_mock.load_balancer_set.assert_called_once_with( + self._lb.id, + json={'loadbalancer': {'tags': ['bar']}}) + @mock.patch('octaviaclient.osc.v2.utils.get_loadbalancer_attrs') def test_load_balancer_remove_qos_policy(self, mock_attrs): mock_attrs.return_value = { @@ -658,3 +806,45 @@ class TestLoadBalancerUnset(TestLoadBalancer): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.api_mock.load_balancer_set.assert_not_called() + + def test_load_balancer_unset_tag(self): + self.api_mock.load_balancer_show.return_value = { + 'tags': ['foo', 'bar'] + } + + arglist = [self._lb.id, '--tag', 'foo'] + verifylist = [ + ('loadbalancer', self._lb.id), + ('tags', ['foo']) + ] + + try: + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + except Exception as e: + self.fail("%s raised unexpectedly" % e) + + self.api_mock.load_balancer_set.assert_called_once_with( + self._lb.id, + json={'loadbalancer': {'tags': ['bar']}}) + + def test_load_balancer_unset_all_tag(self): + self.api_mock.load_balancer_show.return_value = { + 'tags': ['foo', 'bar'] + } + + arglist = [self._lb.id, '--all-tag'] + verifylist = [ + ('loadbalancer', self._lb.id), + ('all_tag', True) + ] + + try: + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + except Exception as e: + self.fail("%s raised unexpectedly" % e) + + self.api_mock.load_balancer_set.assert_called_once_with( + self._lb.id, + json={'loadbalancer': {'tags': []}}) diff --git a/octaviaclient/tests/unit/osc/v2/test_member.py b/octaviaclient/tests/unit/osc/v2/test_member.py index 9b30ef1..69834b0 100644 --- a/octaviaclient/tests/unit/osc/v2/test_member.py +++ b/octaviaclient/tests/unit/osc/v2/test_member.py @@ -71,7 +71,78 @@ class TestListMember(TestMember): columns, data = self.cmd.take_action(parsed_args) - self.api_mock.member_list.assert_called_once_with(pool_id='pool_id') + self.api_mock.member_list.assert_called_once_with( + pool_id='pool_id', + project_id=self._mem.project_id) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_member_list_with_tags(self): + arglist = [self._mem.pool_id, + '--tags', 'foo,bar'] + verifylist = [('pool', self._mem.pool_id), + ('tags', ['foo', 'bar'])] + expected_attrs = { + 'pool_id': self._mem.pool_id, + 'tags': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.member_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_member_list_with_any_tags(self): + arglist = [self._mem.pool_id, + '--any-tags', 'foo,bar'] + verifylist = [('pool', self._mem.pool_id), + ('any_tags', ['foo', 'bar'])] + expected_attrs = { + 'pool_id': self._mem.pool_id, + 'tags-any': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.member_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_member_list_with_not_tags(self): + arglist = [self._mem.pool_id, + '--not-tags', 'foo,bar'] + verifylist = [('pool', self._mem.pool_id), + ('not_tags', ['foo', 'bar'])] + expected_attrs = { + 'pool_id': self._mem.pool_id, + 'not-tags': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.member_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_member_list_with_not_any_tags(self): + arglist = [self._mem.pool_id, + '--not-any-tags', 'foo,bar'] + verifylist = [('pool', self._mem.pool_id), + ('not_any_tags', ['foo', 'bar'])] + expected_attrs = { + 'pool_id': self._mem.pool_id, + 'not-tags-any': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.member_list.assert_called_with(**expected_attrs) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) @@ -151,6 +222,32 @@ class TestCreateMember(TestMember): sleep_time=mock.ANY, status_field='provisioning_status') + @mock.patch('octaviaclient.osc.v2.utils.get_member_attrs') + def test_member_create_with_tag(self, mock_attrs): + mock_attrs.return_value = { + 'ip_address': '192.0.2.122', + 'protocol_port': self._mem.protocol_port, + 'pool_id': self._mem.pool_id, + 'tags': ['foo']} + + arglist = ['pool_id', '--address', '192.0.2.122', + '--protocol-port', '80', + '--tag', 'foo'] + verifylist = [ + ('address', '192.0.2.122'), + ('protocol_port', 80), + ('tags', ['foo']) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.member_create.assert_called_with( + pool_id=self._mem.pool_id, json={ + 'member': {'ip_address': '192.0.2.122', + 'protocol_port': self._mem.protocol_port, + 'tags': ['foo'] + }}) + class TestMemberDelete(TestMember): @@ -250,6 +347,56 @@ class TestMemberSet(TestMember): sleep_time=mock.ANY, status_field='provisioning_status') + @mock.patch('octaviaclient.osc.v2.utils.get_member_attrs') + def test_member_set_tag(self, mock_attrs): + self.api_mock.member_show.return_value = { + 'tags': ['foo'] + } + mock_attrs.return_value = {'pool_id': self._mem.pool_id, + 'member_id': self._mem.id, + 'tags': ['bar']} + arglist = [self._mem.pool_id, self._mem.id, + '--tag', 'bar'] + verifylist = [ + ('pool', self._mem.pool_id), + ('member', self._mem.id), + ('tags', ['bar']) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.member_set.assert_called_once() + kwargs = self.api_mock.member_set.mock_calls[0][2] + tags = kwargs['json']['member']['tags'] + self.assertEqual(2, len(tags)) + self.assertIn('foo', tags) + self.assertIn('bar', tags) + + @mock.patch('octaviaclient.osc.v2.utils.get_member_attrs') + def test_member_set_tag_no_tag(self, mock_attrs): + self.api_mock.member_show.return_value = { + 'tags': ['foo'] + } + mock_attrs.return_value = {'pool_id': self._mem.pool_id, + 'member_id': self._mem.id, + 'tags': ['bar']} + arglist = [self._mem.pool_id, self._mem.id, + '--tag', 'bar', '--no-tag'] + verifylist = [ + ('pool', self._mem.pool_id), + ('member', self._mem.id), + ('tags', ['bar']) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.member_set.assert_called_once_with( + pool_id=self._mem.pool_id, + member_id=self._mem.id, + json={'member': {'tags': ['bar']}}) + class TestMemberShow(TestMember): @@ -368,3 +515,51 @@ class TestMemberUnset(TestMember): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.api_mock.member_set.assert_not_called() + + @mock.patch('octaviaclient.osc.v2.utils.get_member_attrs') + def test_member_unset_tag(self, mock_attrs): + self.api_mock.member_show.return_value = { + 'tags': ['foo', 'bar'] + } + mock_attrs.return_value = {'pool_id': self._mem.pool_id, + 'member_id': self._mem.id, + 'tags': ['bar']} + arglist = [self._mem.pool_id, self._mem.id, + '--tag', 'bar'] + verifylist = [ + ('pool', self._mem.pool_id), + ('member', self._mem.id), + ('tags', ['bar']) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.member_set.assert_called_once_with( + pool_id=self._mem.pool_id, + member_id=self._mem.id, + json={'member': {'tags': ['foo']}}) + + @mock.patch('octaviaclient.osc.v2.utils.get_member_attrs') + def test_member_unset_all_tags(self, mock_attrs): + self.api_mock.member_show.return_value = { + 'tags': ['foo', 'bar'] + } + mock_attrs.return_value = {'pool_id': self._mem.pool_id, + 'member_id': self._mem.id, + 'tags': ['foo', 'bar']} + arglist = [self._mem.pool_id, self._mem.id, + '--all-tag'] + verifylist = [ + ('pool', self._mem.pool_id), + ('member', self._mem.id), + ('all_tag', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.member_set.assert_called_once_with( + pool_id=self._mem.pool_id, + member_id=self._mem.id, + json={'member': {'tags': []}}) diff --git a/octaviaclient/tests/unit/osc/v2/test_pool.py b/octaviaclient/tests/unit/osc/v2/test_pool.py index 9f72736..4ef213e 100644 --- a/octaviaclient/tests/unit/osc/v2/test_pool.py +++ b/octaviaclient/tests/unit/osc/v2/test_pool.py @@ -58,6 +58,62 @@ class TestPoolList(TestPool): self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) + def test_pool_list_with_tags(self): + arglist = ['--tags', 'foo,bar'] + verifylist = [('tags', ['foo', 'bar'])] + expected_attrs = { + 'tags': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.pool_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_pool_list_with_any_tags(self): + arglist = ['--any-tags', 'foo,bar'] + verifylist = [('any_tags', ['foo', 'bar'])] + expected_attrs = { + 'tags-any': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.pool_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_pool_list_with_not_tags(self): + arglist = ['--not-tags', 'foo,bar'] + verifylist = [('not_tags', ['foo', 'bar'])] + expected_attrs = { + 'not-tags': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.pool_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_pool_list_with_not_any_tags(self): + arglist = ['--not-any-tags', 'foo,bar'] + verifylist = [('not_any_tags', ['foo', 'bar'])] + expected_attrs = { + 'not-tags-any': ['foo', 'bar'] + } + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.api_mock.pool_list.assert_called_with(**expected_attrs) + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + class TestPoolDelete(TestPool): @@ -149,6 +205,28 @@ class TestPoolCreate(TestPool): self.api_mock.pool_create.assert_called_with( json={'pool': self.pool_info}) + @mock.patch('octaviaclient.osc.v2.utils.get_pool_attrs') + def test_pool_create_with_tag(self, mock_attrs): + mock_attrs.return_value = self.pool_info + arglist = ['--loadbalancer', 'mock_lb_id', + '--name', self._po.name, + '--protocol', 'HTTP', + '--lb-algorithm', 'ROUND_ROBIN', + '--tag', 'foo'] + + verifylist = [ + ('loadbalancer', 'mock_lb_id'), + ('name', self._po.name), + ('protocol', 'HTTP'), + ('lb_algorithm', 'ROUND_ROBIN'), + ('tags', ['foo']) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.pool_create.assert_called_with( + json={'pool': self.pool_info}) + @mock.patch('osc_lib.utils.wait_for_status') @mock.patch('octaviaclient.osc.v2.utils.get_pool_attrs') def test_pool_create_wait(self, mock_attrs, mock_wait): @@ -253,6 +331,43 @@ class TestPoolSet(TestPool): sleep_time=mock.ANY, status_field='provisioning_status') + def test_pool_set_tag(self): + self.api_mock.pool_show.return_value = { + 'tags': ['foo'] + } + arglist = [self._po.id, '--tag', 'bar'] + verifylist = [ + ('pool', self._po.id), + ('tags', ['bar']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.pool_set.assert_called_once() + kwargs = self.api_mock.pool_set.mock_calls[0][2] + tags = kwargs['json']['pool']['tags'] + self.assertEqual(2, len(tags)) + self.assertIn('foo', tags) + self.assertIn('bar', tags) + + def test_pool_set_tag_no_tag(self): + self.api_mock.pool_show.return_value = { + 'tags': ['foo'] + } + arglist = [self._po.id, '--tag', 'bar', '--no-tag'] + verifylist = [ + ('pool', self._po.id), + ('tags', ['bar']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.pool_set.assert_called_once_with( + self._po.id, + json={"pool": {"tags": ["bar"]}}) + class TestPoolUnset(TestPool): PARAMETERS = ('name', 'description', 'ca_tls_container_ref', @@ -350,3 +465,41 @@ class TestPoolUnset(TestPool): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.api_mock.pool_set.assert_not_called() + + def test_pool_unset_tag(self): + self.api_mock.pool_set.reset_mock() + self.api_mock.pool_show.return_value = { + 'tags': ['foo', 'bar'] + } + + arglist = [self._po.id, '--tag', 'foo'] + verifylist = [ + ('pool', self._po.id), + ('tags', ['foo']), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.pool_set.assert_called_once_with( + self._po.id, + json={"pool": {"tags": ["bar"]}}) + + def test_pool_unset_all_tag(self): + self.api_mock.pool_set.reset_mock() + self.api_mock.pool_show.return_value = { + 'tags': ['foo', 'bar'] + } + + arglist = [self._po.id, '--all-tag'] + verifylist = [ + ('pool', self._po.id), + ('all_tag', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.api_mock.pool_set.assert_called_once_with( + self._po.id, + json={"pool": {"tags": []}}) diff --git a/releasenotes/notes/add-tag-support-01087c4b3c4360dc.yaml b/releasenotes/notes/add-tag-support-01087c4b3c4360dc.yaml new file mode 100644 index 0000000..73d8fcb --- /dev/null +++ b/releasenotes/notes/add-tag-support-01087c4b3c4360dc.yaml @@ -0,0 +1,12 @@ +--- +features: + - | + Add support for tags for Octavia resources. Tags are supported with load + balancers, listeners, pools, members, health monitors, l7policies and + l7rules. + Tags can be added when creating a resource, and they can be set and unset. + Tags allow also to filter elements when listing Octavia resources. +fixes: + - | + Fix errors when combining set/unset CLI for l7rule resources and the --wait + option.