From ae244fcf25188f38c28870238e648e765a2e3adf Mon Sep 17 00:00:00 2001 From: Adam Harwell Date: Tue, 29 Oct 2019 16:59:12 -0700 Subject: [PATCH] Add "--wait" functionality Change-Id: Ia8e451d7f806fac7d36b2860f256eb34f9f7400e --- octaviaclient/api/exceptions.py | 26 +++ octaviaclient/api/v2/octavia.py | 17 +- octaviaclient/osc/v2/amphora.py | 46 ++++- octaviaclient/osc/v2/constants.py | 2 + octaviaclient/osc/v2/health_monitor.py | 55 ++++++ octaviaclient/osc/v2/l7policy.py | 55 ++++++ octaviaclient/osc/v2/l7rule.py | 68 ++++++++ octaviaclient/osc/v2/listener.py | 51 ++++++ octaviaclient/osc/v2/load_balancer.py | 68 +++++++- octaviaclient/osc/v2/member.py | 64 +++++++ octaviaclient/osc/v2/pool.py | 51 ++++++ octaviaclient/osc/v2/utils.py | 51 +++++- octaviaclient/tests/unit/api/test_octavia.py | 65 +++---- .../tests/unit/osc/v2/test_amphora.py | 75 ++++++++- .../tests/unit/osc/v2/test_health_monitor.py | 103 ++++++++++++ .../tests/unit/osc/v2/test_l7policy.py | 103 ++++++++++++ .../tests/unit/osc/v2/test_l7rule.py | 140 ++++++++++++++++ .../tests/unit/osc/v2/test_listener.py | 94 +++++++++++ .../tests/unit/osc/v2/test_load_balancer.py | 127 +++++++++++++- .../tests/unit/osc/v2/test_member.py | 158 ++++++++++++++++-- octaviaclient/tests/unit/osc/v2/test_pool.py | 91 ++++++++++ ...tion-to-CUD-commands-97375387e7762b83.yaml | 6 + 22 files changed, 1441 insertions(+), 75 deletions(-) create mode 100644 octaviaclient/api/exceptions.py create mode 100644 releasenotes/notes/add-wait-option-to-CUD-commands-97375387e7762b83.yaml diff --git a/octaviaclient/api/exceptions.py b/octaviaclient/api/exceptions.py new file mode 100644 index 0000000..df5675a --- /dev/null +++ b/octaviaclient/api/exceptions.py @@ -0,0 +1,26 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + + +class OctaviaClientException(Exception): + """The base exception class for all exceptions this library raises.""" + + def __init__(self, code, message=None, request_id=None): + self.code = code + self.message = message or self.__class__.message + self.request_id = request_id + + def __str__(self): + return "%s (HTTP %s) (Request-ID: %s)" % (self.message, + self.code, + self.request_id) diff --git a/octaviaclient/api/v2/octavia.py b/octaviaclient/api/v2/octavia.py index 8b2b50d..297b635 100644 --- a/octaviaclient/api/v2/octavia.py +++ b/octaviaclient/api/v2/octavia.py @@ -18,6 +18,7 @@ from osc_lib.api import api from osc_lib import exceptions as osc_exc from octaviaclient.api import constants as const +from octaviaclient.api import exceptions def correct_return_codes(func): @@ -45,7 +46,7 @@ def correct_return_codes(func): else: raise - raise OctaviaClientException( + raise exceptions.OctaviaClientException( code=code, message=message, request_id=request_id) @@ -934,17 +935,3 @@ class OctaviaAPI(api.BaseAPI): response = self._delete(url) return response - - -class OctaviaClientException(Exception): - """The base exception class for all exceptions this library raises.""" - - def __init__(self, code, message=None, request_id=None): - self.code = code - self.message = message or self.__class__.message - self.request_id = request_id - - def __str__(self): - return "%s (HTTP %s) (Request-ID: %s)" % (self.message, - self.code, - self.request_id) diff --git a/octaviaclient/osc/v2/amphora.py b/octaviaclient/osc/v2/amphora.py index 20162a9..9b79162 100644 --- a/octaviaclient/osc/v2/amphora.py +++ b/octaviaclient/osc/v2/amphora.py @@ -128,15 +128,32 @@ class ConfigureAmphora(command.Command): metavar='', help='UUID of the amphora to configure.', ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser def take_action(self, parsed_args): attrs = v2_utils.get_amphora_attrs(self.app.client_manager, parsed_args) - + amp_id = attrs.pop('amphora_id') self.app.client_manager.load_balancer.amphora_configure( - amphora_id=attrs.pop('amphora_id')) + amphora_id=amp_id) + + if parsed_args.wait: + amphora = self.app.client_manager.load_balancer.amphora_show( + amp_id) + lb_id = amphora.get('loadbalancer_id') + # TODO(rm_work): No status change if the amp isn't linked to an LB? + if lb_id: + v2_utils.wait_for_active( + status_f=(self.app.client_manager.load_balancer. + load_balancer_show), + res_id=lb_id + ) class FailoverAmphora(command.Command): @@ -150,12 +167,33 @@ class FailoverAmphora(command.Command): metavar='', help='UUID of the amphora.', ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser def take_action(self, parsed_args): attrs = v2_utils.get_amphora_attrs(self.app.client_manager, parsed_args) - + amp_id = attrs.pop('amphora_id') + amphora = self.app.client_manager.load_balancer.amphora_show(amp_id) self.app.client_manager.load_balancer.amphora_failover( - amphora_id=attrs.pop('amphora_id')) + amphora_id=amp_id) + + if parsed_args.wait: + lb_id = amphora.get('loadbalancer_id') + if lb_id: + v2_utils.wait_for_active( + status_f=(self.app.client_manager.load_balancer. + load_balancer_show), + res_id=lb_id + ) + else: + v2_utils.wait_for_delete( + status_f=(self.app.client_manager.load_balancer. + amphora_show), + res_id=amp_id + ) diff --git a/octaviaclient/osc/v2/constants.py b/octaviaclient/osc/v2/constants.py index f019587..ee44b6d 100644 --- a/octaviaclient/osc/v2/constants.py +++ b/octaviaclient/osc/v2/constants.py @@ -313,3 +313,5 @@ FLAVORPROFILE_COLUMNS = ( 'name', 'provider_name', ) + +PROVISIONING_STATUS = 'provisioning_status' diff --git a/octaviaclient/osc/v2/health_monitor.py b/octaviaclient/osc/v2/health_monitor.py index 18fca54..b236404 100644 --- a/octaviaclient/osc/v2/health_monitor.py +++ b/octaviaclient/osc/v2/health_monitor.py @@ -132,6 +132,11 @@ class CreateHealthMonitor(command.ShowOne): default=None, help="Disable health monitor." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -143,6 +148,20 @@ class CreateHealthMonitor(command.ShowOne): data = self.app.client_manager.load_balancer.health_monitor_create( json=body) + if parsed_args.wait: + pool = self.app.client_manager.load_balancer.pool_show( + data['healthmonitor']['pools'][0]['id']) + v2_utils.wait_for_active( + status_f=(self.app.client_manager.load_balancer. + load_balancer_show), + res_id=pool['loadbalancers'][0]['id'] + ) + data = { + 'healthmonitor': ( + self.app.client_manager.load_balancer.health_monitor_show( + data['healthmonitor']['id'])) + } + formatters = {'pools': v2_utils.format_list} return (rows, @@ -162,6 +181,11 @@ class DeleteHealthMonitor(command.Command): metavar='', help="Health monitor to delete (name or ID)." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -174,6 +198,13 @@ class DeleteHealthMonitor(command.Command): self.app.client_manager.load_balancer.health_monitor_delete( health_monitor_id=health_monitor_id) + if parsed_args.wait: + v2_utils.wait_for_delete( + status_f=(self.app.client_manager.load_balancer. + health_monitor_show), + res_id=health_monitor_id + ) + class ListHealthMonitor(lister.Lister): """List health monitors""" @@ -316,6 +347,11 @@ class SetHealthMonitor(command.Command): default=None, help="Disable health monitor." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -330,6 +366,13 @@ class SetHealthMonitor(command.Command): self.app.client_manager.load_balancer.health_monitor_set( hm_id, json=body) + if parsed_args.wait: + v2_utils.wait_for_active( + status_f=(self.app.client_manager.load_balancer. + health_monitor_show), + res_id=hm_id + ) + class UnsetHealthMonitor(command.Command): """Clear health monitor settings""" @@ -378,6 +421,11 @@ class UnsetHealthMonitor(command.Command): action='store_true', help="Clear the health monitor URL path." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser def take_action(self, parsed_args): @@ -393,3 +441,10 @@ class UnsetHealthMonitor(command.Command): self.app.client_manager.load_balancer.health_monitor_set( hm_id, json=body) + + if parsed_args.wait: + v2_utils.wait_for_active( + status_f=(self.app.client_manager.load_balancer. + health_monitor_show), + res_id=hm_id + ) diff --git a/octaviaclient/osc/v2/l7policy.py b/octaviaclient/osc/v2/l7policy.py index 11eb6b3..52207c1 100644 --- a/octaviaclient/osc/v2/l7policy.py +++ b/octaviaclient/osc/v2/l7policy.py @@ -104,6 +104,11 @@ class CreateL7Policy(command.ShowOne): default=None, help="Disable l7policy." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -117,6 +122,20 @@ class CreateL7Policy(command.ShowOne): data = self.app.client_manager.load_balancer.l7policy_create( json=body) + if parsed_args.wait: + listener = self.app.client_manager.load_balancer.listener_show( + data['l7policy']['listener_id']) + v2_utils.wait_for_active( + status_f=(self.app.client_manager.load_balancer. + load_balancer_show), + res_id=listener['loadbalancers'][0]['id'] + ) + data = { + 'l7policy': ( + self.app.client_manager.load_balancer.l7policy_show( + data['l7policy']['id'])) + } + formatters = {'rules': v2_utils.format_list} return (rows, (utils.get_dict_properties( @@ -134,6 +153,11 @@ class DeleteL7Policy(command.Command): metavar="", help="l7policy to delete (name or ID)." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -146,6 +170,13 @@ class DeleteL7Policy(command.Command): self.app.client_manager.load_balancer.l7policy_delete( l7policy_id=l7policy_id) + if parsed_args.wait: + v2_utils.wait_for_delete( + status_f=(self.app.client_manager.load_balancer. + l7policy_show), + res_id=l7policy_id + ) + class ListL7Policy(lister.Lister): """List l7policies""" @@ -278,6 +309,11 @@ class SetL7Policy(command.Command): default=None, help="Disable l7policy." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -293,6 +329,13 @@ class SetL7Policy(command.Command): self.app.client_manager.load_balancer.l7policy_set( l7policy_id, json=body) + if parsed_args.wait: + v2_utils.wait_for_active( + status_f=(self.app.client_manager.load_balancer. + l7policy_show), + res_id=l7policy_id + ) + class UnsetL7Policy(command.Command): """Clear l7policy settings""" @@ -320,6 +363,11 @@ class UnsetL7Policy(command.Command): action='store_true', help="Clear the l7policy redirect HTTP code." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser def take_action(self, parsed_args): @@ -335,3 +383,10 @@ class UnsetL7Policy(command.Command): self.app.client_manager.load_balancer.l7policy_set( policy_id, json=body) + + if parsed_args.wait: + v2_utils.wait_for_active( + status_f=(self.app.client_manager.load_balancer. + l7policy_show), + res_id=policy_id + ) diff --git a/octaviaclient/osc/v2/l7rule.py b/octaviaclient/osc/v2/l7rule.py index 2566395..4d20644 100644 --- a/octaviaclient/osc/v2/l7rule.py +++ b/octaviaclient/osc/v2/l7rule.py @@ -16,6 +16,8 @@ """L7rule action implementation""" +import functools + from cliff import lister from osc_lib.command import command from osc_lib import utils @@ -85,6 +87,11 @@ class CreateL7Rule(command.ShowOne): default=None, help="Disable l7rule." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -101,6 +108,22 @@ class CreateL7Rule(command.ShowOne): json=body ) + if parsed_args.wait: + l7policy = self.app.client_manager.load_balancer.l7policy_show( + l7policy_id) + listener = self.app.client_manager.load_balancer.listener_show( + l7policy['listener_id']) + v2_utils.wait_for_active( + status_f=(self.app.client_manager.load_balancer. + load_balancer_show), + res_id=listener['loadbalancers'][0]['id'] + ) + data = { + 'rule': ( + self.app.client_manager.load_balancer.l7rule_show( + l7policy_id, data['rule']['id'])) + } + return (rows, (utils.get_dict_properties( data['rule'], rows, formatters={}))) @@ -121,6 +144,11 @@ class DeleteL7Rule(command.Command): metavar="", help="l7rule to delete." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -132,6 +160,16 @@ class DeleteL7Rule(command.Command): l7policy_id=attrs['l7policy_id'] ) + if parsed_args.wait: + l7rule_show = functools.partial( + self.app.client_manager.load_balancer.l7rule_show, + attrs['l7policy_id'] + ) + v2_utils.wait_for_delete( + status_f=l7rule_show, + res_id=attrs['l7rule_id'] + ) + class ListL7Rule(lister.Lister): """List l7rules for l7policy""" @@ -251,6 +289,11 @@ class SetL7Rule(command.Command): default=None, help="Disable l7rule." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -269,6 +312,16 @@ class SetL7Rule(command.Command): json=body ) + 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 + ) + class UnsetL7Rule(command.Command): """Clear l7rule settings""" @@ -296,6 +349,11 @@ class UnsetL7Rule(command.Command): action='store_true', help="Clear the l7rule key." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser def take_action(self, parsed_args): @@ -311,3 +369,13 @@ class UnsetL7Rule(command.Command): 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 + ) diff --git a/octaviaclient/osc/v2/listener.py b/octaviaclient/osc/v2/listener.py index 6eb2f03..cbd33aa 100644 --- a/octaviaclient/osc/v2/listener.py +++ b/octaviaclient/osc/v2/listener.py @@ -166,6 +166,11 @@ class CreateListener(command.ShowOne): help="CIDR to allow access to the listener (can be set multiple " "times)." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -176,6 +181,19 @@ class CreateListener(command.ShowOne): body = {"listener": attrs} data = self.app.client_manager.load_balancer.listener_create( json=body) + + if parsed_args.wait: + v2_utils.wait_for_active( + status_f=(self.app.client_manager.load_balancer. + load_balancer_show), + res_id=data['listener']['loadbalancers'][0]['id'] + ) + data = { + 'listener': ( + self.app.client_manager.load_balancer.listener_show( + data['listener']['id'])) + } + formatters = {'loadbalancers': v2_utils.format_list, 'pools': v2_utils.format_list, 'l7policies': v2_utils.format_list, @@ -199,6 +217,11 @@ class DeleteListener(command.Command): metavar="", help="Listener to delete (name or ID)" ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -211,6 +234,12 @@ class DeleteListener(command.Command): self.app.client_manager.load_balancer.listener_delete( listener_id=listener_id) + if parsed_args.wait: + v2_utils.wait_for_delete( + status_f=self.app.client_manager.load_balancer.listener_show, + res_id=listener_id + ) + class ListListener(lister.Lister): """List listeners""" @@ -421,6 +450,11 @@ class SetListener(command.Command): help="CIDR to allow access to the listener (can be set multiple " "times)." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -435,6 +469,12 @@ class SetListener(command.Command): self.app.client_manager.load_balancer.listener_set( listener_id, json=body) + if parsed_args.wait: + v2_utils.wait_for_active( + status_f=self.app.client_manager.load_balancer.listener_show, + res_id=listener_id + ) + class UnsetListener(command.Command): """Clear listener settings""" @@ -525,6 +565,11 @@ class UnsetListener(command.Command): action='store_true', help="Clear all allowed CIDRs from the listener." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser def take_action(self, parsed_args): @@ -541,6 +586,12 @@ class UnsetListener(command.Command): self.app.client_manager.load_balancer.listener_set( listener_id, json=body) + if parsed_args.wait: + v2_utils.wait_for_active( + status_f=self.app.client_manager.load_balancer.listener_show, + res_id=listener_id + ) + class ShowListenerStats(command.ShowOne): """Shows the current statistics for a listener.""" diff --git a/octaviaclient/osc/v2/load_balancer.py b/octaviaclient/osc/v2/load_balancer.py index 46d1505..44c441b 100644 --- a/octaviaclient/osc/v2/load_balancer.py +++ b/octaviaclient/osc/v2/load_balancer.py @@ -118,6 +118,11 @@ class CreateLoadBalancer(command.ShowOne): metavar='', help="The name or ID of the flavor for the load balancer." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -131,6 +136,18 @@ class CreateLoadBalancer(command.ShowOne): data = self.app.client_manager.load_balancer.load_balancer_create( json=body) + if parsed_args.wait: + v2_utils.wait_for_active( + status_f=(self.app.client_manager.load_balancer. + load_balancer_show), + res_id=data['loadbalancer']['id'] + ) + data = { + 'loadbalancer': ( + self.app.client_manager.load_balancer.load_balancer_show( + data['loadbalancer']['id'])) + } + formatters = { 'listeners': v2_utils.format_list, 'pools': v2_utils.format_list, @@ -160,6 +177,11 @@ class DeleteLoadBalancer(command.Command): help="Cascade the delete to all child elements of the load " "balancer." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -171,6 +193,13 @@ class DeleteLoadBalancer(command.Command): self.app.client_manager.load_balancer.load_balancer_delete( lb_id=lb_id, **attrs) + if parsed_args.wait: + v2_utils.wait_for_delete( + status_f=(self.app.client_manager.load_balancer. + load_balancer_show), + res_id=lb_id + ) + class FailoverLoadBalancer(command.Command): """Trigger load balancer failover""" @@ -183,14 +212,27 @@ class FailoverLoadBalancer(command.Command): metavar='', help="Name or UUID of the load balancer." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) 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') self.app.client_manager.load_balancer.load_balancer_failover( - lb_id=attrs.pop('loadbalancer_id')) + lb_id=lb_id) + + if parsed_args.wait: + v2_utils.wait_for_active( + status_f=(self.app.client_manager.load_balancer. + load_balancer_show), + res_id=lb_id + ) class ListLoadBalancer(lister.Lister): @@ -362,6 +404,11 @@ class SetLoadBalancer(command.Command): default=None, help="Disable load balancer." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -374,6 +421,13 @@ class SetLoadBalancer(command.Command): self.app.client_manager.load_balancer.load_balancer_set( lb_id, json=body) + if parsed_args.wait: + v2_utils.wait_for_active( + status_f=(self.app.client_manager.load_balancer. + load_balancer_show), + res_id=lb_id + ) + class UnsetLoadBalancer(command.Command): """Clear load balancer settings""" @@ -401,6 +455,11 @@ class UnsetLoadBalancer(command.Command): action='store_true', help="Clear the load balancer QoS policy.", ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -418,6 +477,13 @@ class UnsetLoadBalancer(command.Command): self.app.client_manager.load_balancer.load_balancer_set( lb_id, json=body) + if parsed_args.wait: + v2_utils.wait_for_active( + status_f=(self.app.client_manager.load_balancer. + load_balancer_show), + res_id=lb_id + ) + class ShowLoadBalancerStats(command.ShowOne): """Shows the current statistics for a load balancer""" diff --git a/octaviaclient/osc/v2/member.py b/octaviaclient/osc/v2/member.py index c72a023..938d1f5 100644 --- a/octaviaclient/osc/v2/member.py +++ b/octaviaclient/osc/v2/member.py @@ -15,6 +15,7 @@ """Member action implementation""" +import functools from cliff import lister from osc_lib.command import command @@ -172,6 +173,11 @@ class CreateMember(command.ShowOne): default=None, help="Disable member" ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -186,6 +192,19 @@ class CreateMember(command.ShowOne): json=body ) + if parsed_args.wait: + pool = self.app.client_manager.load_balancer.pool_show(pool_id) + v2_utils.wait_for_active( + status_f=(self.app.client_manager.load_balancer. + load_balancer_show), + res_id=pool['loadbalancers'][0]['id'] + ) + data = { + 'member': ( + self.app.client_manager.load_balancer.member_show( + pool_id, data['member']['id'])) + } + return (rows, (utils.get_dict_properties( data['member'], rows, formatters={}))) @@ -258,6 +277,11 @@ class SetMember(command.Command): action='store_true', default=None, help="Set the admin_state_up to False") + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -273,6 +297,16 @@ class SetMember(command.Command): json=post_data ) + 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 + ) + class DeleteMember(command.Command): """Delete a member from a pool """ @@ -291,6 +325,11 @@ class DeleteMember(command.Command): metavar='', help="Name or ID of the member to be deleted." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -304,6 +343,16 @@ class DeleteMember(command.Command): member_id=id ) + if parsed_args.wait: + member_show = functools.partial( + self.app.client_manager.load_balancer.member_show, + pool_id + ) + v2_utils.wait_for_delete( + status_f=member_show, + res_id=id + ) + class UnsetMember(command.Command): """Clear member settings""" @@ -346,6 +395,11 @@ class UnsetMember(command.Command): action='store_true', help="Reset the member weight to the API default." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser def take_action(self, parsed_args): @@ -366,3 +420,13 @@ class UnsetMember(command.Command): 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 b26d528..d669f47 100644 --- a/octaviaclient/osc/v2/pool.py +++ b/octaviaclient/osc/v2/pool.py @@ -123,6 +123,11 @@ class CreatePool(command.ShowOne): default=None, help="Disable backend member re-encryption." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -133,6 +138,19 @@ class CreatePool(command.ShowOne): body = {"pool": attrs} data = self.app.client_manager.load_balancer.pool_create( json=body) + + if parsed_args.wait: + v2_utils.wait_for_active( + status_f=(self.app.client_manager.load_balancer. + load_balancer_show), + res_id=data['pool']['loadbalancers'][0]['id'] + ) + data = { + 'pool': ( + self.app.client_manager.load_balancer.pool_show( + data['pool']['id'])) + } + formatters = {'loadbalancers': v2_utils.format_list, 'members': v2_utils.format_list, 'listeners': v2_utils.format_list, @@ -154,6 +172,11 @@ class DeletePool(command.Command): metavar="", help="Pool to delete (name or ID)." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -163,6 +186,12 @@ class DeletePool(command.Command): self.app.client_manager.load_balancer.pool_delete( pool_id=pool_id) + if parsed_args.wait: + v2_utils.wait_for_delete( + status_f=self.app.client_manager.load_balancer.pool_show, + res_id=pool_id + ) + class ListPool(lister.Lister): """List pools""" @@ -306,6 +335,11 @@ class SetPool(command.Command): default=None, help="disable backend associated members re-encryption." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser @@ -318,6 +352,12 @@ class SetPool(command.Command): self.app.client_manager.load_balancer.pool_set( pool_id, json=body) + if parsed_args.wait: + v2_utils.wait_for_active( + status_f=self.app.client_manager.load_balancer.pool_show, + res_id=pool_id + ) + class UnsetPool(command.Command): """Clear pool settings""" @@ -362,6 +402,11 @@ class UnsetPool(command.Command): action='store_true', help="Clear the certificate reference for this pool." ) + parser.add_argument( + '--wait', + action='store_true', + help='Wait for action to complete', + ) return parser def take_action(self, parsed_args): @@ -377,3 +422,9 @@ class UnsetPool(command.Command): self.app.client_manager.load_balancer.pool_set( pool_id, json=body) + + if parsed_args.wait: + v2_utils.wait_for_active( + status_f=self.app.client_manager.load_balancer.pool_show, + res_id=pool_id + ) diff --git a/octaviaclient/osc/v2/utils.py b/octaviaclient/osc/v2/utils.py index 86bd68b..4d6e90f 100644 --- a/octaviaclient/osc/v2/utils.py +++ b/octaviaclient/osc/v2/utils.py @@ -12,9 +12,13 @@ # under the License. # -from osc_lib import exceptions - +import munch from openstackclient.identity import common as identity_common +from osc_lib import exceptions as osc_exc +from osc_lib import utils + +from octaviaclient.api import exceptions +from octaviaclient.osc.v2 import constants def _map_attrs(args, source_attr_map): @@ -95,7 +99,7 @@ def get_resource_id(resource, resource_name, name): msg = ("{0} {1} found with name or ID of {2}. Please try " "again with UUID".format(len(names), resource_name, name)) - raise exceptions.CommandError(msg) + raise osc_exc.CommandError(msg) else: return names[0].get('id') elif resource_name == 'l7rules': @@ -110,12 +114,12 @@ def get_resource_id(resource, resource_name, name): msg = ("{0} {1} found with name or ID of {2}. Please try " "again with UUID".format(len(names), resource_name, name)) - raise exceptions.CommandError(msg) + raise osc_exc.CommandError(msg) else: return names[0].get('id') except IndexError: msg = "Unable to locate {0} in {1}".format(name, resource_name) - raise exceptions.CommandError(msg) + raise osc_exc.CommandError(msg) def get_loadbalancer_attrs(client_manager, parsed_args): @@ -547,4 +551,39 @@ 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} + value is True and arg != 'wait'} + + +def wait_for_active(status_f, res_id): + success = utils.wait_for_status( + status_f=lambda x: munch.Munch(status_f(x)), + res_id=res_id, + status_field=constants.PROVISIONING_STATUS, + sleep_time=3 + ) + if not success: + raise exceptions.OctaviaClientException( + code="n/a", + message="The resource did not successfully reach ACTIVE status.") + + +def wait_for_delete(status_f, res_id): + class Getter(object): + @staticmethod + def get(id): + return munch.Munch(status_f(id)) + + try: + success = utils.wait_for_delete( + manager=Getter, + res_id=res_id, + status_field=constants.PROVISIONING_STATUS, + sleep_time=3 + ) + if not success: + raise exceptions.OctaviaClientException( + code="n/a", + message="The resource could not be successfully deleted.") + except exceptions.OctaviaClientException as e: + if e.code != 404: + raise diff --git a/octaviaclient/tests/unit/api/test_octavia.py b/octaviaclient/tests/unit/api/test_octavia.py index 4d98067..59d7865 100644 --- a/octaviaclient/tests/unit/api/test_octavia.py +++ b/octaviaclient/tests/unit/api/test_octavia.py @@ -19,6 +19,7 @@ from requests_mock.contrib import fixture from osc_lib.tests import utils +from octaviaclient.api import exceptions from octaviaclient.api.v2 import octavia FAKE_ACCOUNT = 'q12we34r' @@ -204,7 +205,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.load_balancer_create, json=SINGLE_LB_RESP) @@ -226,7 +227,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.load_balancer_set, FAKE_LB, @@ -248,7 +249,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=409, ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.load_balancer_failover, FAKE_LB) @@ -269,7 +270,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.load_balancer_delete, FAKE_LB) @@ -331,7 +332,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.listener_create, json=SINGLE_LI_RESP) @@ -353,7 +354,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.listener_set, FAKE_LI, json=SINGLE_LI_UPDATE) @@ -374,7 +375,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.listener_delete, FAKE_LI) @@ -426,7 +427,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.pool_create, json=SINGLE_PO_RESP) @@ -448,7 +449,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.pool_set, FAKE_PO, json=SINGLE_PO_UPDATE) @@ -469,7 +470,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.pool_delete, FAKE_PO) @@ -511,7 +512,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.member_create, json=SINGLE_ME_RESP, pool_id=FAKE_PO) @@ -534,7 +535,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.member_set, pool_id=FAKE_PO, member_id=FAKE_ME, @@ -556,7 +557,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.member_delete, pool_id=FAKE_PO, member_id=FAKE_ME) @@ -598,7 +599,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.l7policy_create, json=SINGLE_L7PO_RESP) @@ -620,7 +621,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.l7policy_set, FAKE_L7PO, json=SINGLE_L7PO_UPDATE) @@ -641,7 +642,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.l7policy_delete, FAKE_L7PO) @@ -683,7 +684,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.l7rule_create, FAKE_L7PO, json=SINGLE_L7RU_RESP) @@ -709,7 +710,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.l7rule_set, l7rule_id=FAKE_L7RU, @@ -735,7 +736,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.l7rule_delete, l7rule_id=FAKE_L7RU, @@ -778,7 +779,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.health_monitor_create, json=SINGLE_HM_RESP) @@ -800,7 +801,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.health_monitor_set, FAKE_HM, json=SINGLE_HM_UPDATE) @@ -821,7 +822,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.health_monitor_delete, FAKE_HM) @@ -863,7 +864,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.quota_set, FAKE_PRJ, json=SINGLE_QT_UPDATE) @@ -884,7 +885,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.quota_reset, FAKE_PRJ) @@ -925,7 +926,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=409, ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.amphora_configure, FAKE_AMP) @@ -946,7 +947,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=409, ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.amphora_failover, FAKE_AMP) @@ -1010,7 +1011,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.flavor_create, json=SINGLE_FV_RESP) @@ -1032,7 +1033,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.flavor_set, FAKE_FV, @@ -1054,7 +1055,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.flavor_delete, FAKE_FV) @@ -1096,7 +1097,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.flavorprofile_create, json=SINGLE_FVPF_RESP) @@ -1118,7 +1119,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.flavorprofile_set, FAKE_FVPF, json=SINGLE_FVPF_UPDATE) @@ -1139,7 +1140,7 @@ class TestLoadBalancer(TestOctaviaClient): text='{"faultstring": "%s"}' % self._error_message, status_code=400 ) - self.assertRaisesRegex(octavia.OctaviaClientException, + self.assertRaisesRegex(exceptions.OctaviaClientException, self._error_message, self.api.flavorprofile_delete, FAKE_FVPF) diff --git a/octaviaclient/tests/unit/osc/v2/test_amphora.py b/octaviaclient/tests/unit/osc/v2/test_amphora.py index 05e2a8d..f6c0d0d 100644 --- a/octaviaclient/tests/unit/osc/v2/test_amphora.py +++ b/octaviaclient/tests/unit/osc/v2/test_amphora.py @@ -38,9 +38,7 @@ class TestAmphora(fakes.TestOctaviaClient): ]} self.api_mock = mock.Mock() self.api_mock.amphora_list.return_value = info_list - self.api_mock.amphora_show.return_value = { - "amphora": info_list['amphorae'][0], - } + self.api_mock.amphora_show.return_value = info_list['amphorae'][0] lb_client = self.app.client_manager lb_client.load_balancer = self.api_mock @@ -130,6 +128,34 @@ class TestAmphoraConfigure(TestAmphora): self.api_mock.amphora_configure.assert_called_with( amphora_id=self._amp.id) + @mock.patch('osc_lib.utils.wait_for_status') + def test_amphora_configure_linked_wait(self, mock_wait): + arglist = [self._amp.id, '--wait'] + verify_list = [('amphora_id', self._amp.id)] + + parsed_args = self.check_parser(self.cmd, arglist, verify_list) + self.cmd.take_action(parsed_args) + self.api_mock.amphora_configure.assert_called_with( + amphora_id=self._amp.id) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self._amp.loadbalancer_id, + sleep_time=mock.ANY, + status_field='provisioning_status') + + @mock.patch('osc_lib.utils.wait_for_status') + def test_amphora_configure_unlinked_wait(self, mock_wait): + self.api_mock.amphora_show.return_value.pop('loadbalancer_id') + arglist = [self._amp.id, '--wait'] + verify_list = [('amphora_id', self._amp.id)] + + parsed_args = self.check_parser(self.cmd, arglist, verify_list) + self.cmd.take_action(parsed_args) + self.api_mock.amphora_configure.assert_called_with( + amphora_id=self._amp.id) + # TODO(rm_work): No wait expected if the amp isn't linked to an LB? + mock_wait.assert_not_called() + class TestAmphoraFailover(TestAmphora): def setUp(self): @@ -144,3 +170,46 @@ class TestAmphoraFailover(TestAmphora): self.cmd.take_action(parsed_args) self.api_mock.amphora_failover.assert_called_with( amphora_id=self._amp.id) + + @mock.patch('osc_lib.utils.wait_for_status') + @mock.patch('osc_lib.utils.wait_for_delete') + def test_amphora_failover_linked_wait(self, mock_wait_delete, + mock_wait_active): + arglist = [self._amp.id, '--wait'] + verify_list = [ + ('amphora_id', self._amp.id), + ('wait', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verify_list) + self.cmd.take_action(parsed_args) + self.api_mock.amphora_failover.assert_called_with( + amphora_id=self._amp.id) + mock_wait_active.assert_called_once_with( + status_f=mock.ANY, + res_id=self._amp.loadbalancer_id, + sleep_time=mock.ANY, + status_field='provisioning_status') + mock_wait_delete.assert_not_called() + + @mock.patch('osc_lib.utils.wait_for_status') + @mock.patch('osc_lib.utils.wait_for_delete') + def test_amphora_failover_unlinked_wait(self, mock_wait_delete, + mock_wait_active): + self.api_mock.amphora_show.return_value.pop('loadbalancer_id') + arglist = [self._amp.id, '--wait'] + verify_list = [ + ('amphora_id', self._amp.id), + ('wait', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verify_list) + self.cmd.take_action(parsed_args) + self.api_mock.amphora_failover.assert_called_with( + amphora_id=self._amp.id) + mock_wait_active.assert_not_called() + mock_wait_delete.assert_called_once_with( + manager=mock.ANY, + res_id=self._amp.id, + sleep_time=mock.ANY, + status_field='provisioning_status') diff --git a/octaviaclient/tests/unit/osc/v2/test_health_monitor.py b/octaviaclient/tests/unit/osc/v2/test_health_monitor.py index 8f539d7..d62797e 100644 --- a/octaviaclient/tests/unit/osc/v2/test_health_monitor.py +++ b/octaviaclient/tests/unit/osc/v2/test_health_monitor.py @@ -79,6 +79,24 @@ class TestHealthMonitorDelete(TestHealthMonitor): self.api_mock.health_monitor_delete.assert_called_with( health_monitor_id=self._hm.id) + @mock.patch('osc_lib.utils.wait_for_delete') + def test_health_monitor_delete_wait(self, mock_wait): + arglist = [self._hm.id, '--wait'] + verifylist = [ + ('health_monitor', self._hm.id), + ('wait', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.health_monitor_delete.assert_called_with( + health_monitor_id=self._hm.id) + mock_wait.assert_called_once_with( + manager=mock.ANY, + res_id=self._hm.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + def test_health_monitor_delete_failure(self): arglist = ['unknown_hm'] verifylist = [ @@ -131,6 +149,47 @@ class TestHealthMonitorCreate(TestHealthMonitor): self.api_mock.health_monitor_create.assert_called_with( json={'healthmonitor': self.hm_info}) + @mock.patch('osc_lib.utils.wait_for_status') + @mock.patch('octaviaclient.osc.v2.utils.get_health_monitor_attrs') + def test_health_monitor_create_wait(self, mock_client, mock_wait): + self.hm_info['pools'] = [{'id': 'mock_pool_id'}] + mock_client.return_value = self.hm_info + self.api_mock.pool_show.return_value = { + 'loadbalancers': [{'id': 'mock_lb_id'}]} + self.api_mock.health_monitor_show.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(), + '--http-method', self._hm.http_method.lower(), + '--http-version', str(self._hm.http_version), + '--domain-name', self._hm.domain_name, + '--wait'] + 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), + ('http_method', self._hm.http_method), + ('http_version', self._hm.http_version), + ('domain_name', self._hm.domain_name), + ('wait', True), + ] + + 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}) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id='mock_lb_id', + sleep_time=mock.ANY, + status_field='provisioning_status') + class TestHealthMonitorShow(TestHealthMonitor): @@ -180,6 +239,25 @@ class TestHealthMonitorSet(TestHealthMonitor): 'name': 'new_name', 'http_version': self._hm.http_version, 'domain_name': self._hm.domain_name}}) + @mock.patch('osc_lib.utils.wait_for_status') + def test_health_monitor_set_wait(self, mock_wait): + arglist = [self._hm.id, '--name', 'new_name', '--wait'] + verifylist = [ + ('health_monitor', self._hm.id), + ('name', 'new_name'), + ('wait', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.health_monitor_set.assert_called_with( + self._hm.id, json={'healthmonitor': {'name': 'new_name'}}) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self._hm.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + class TestHealthMonitorUnset(TestHealthMonitor): PARAMETERS = ('name', 'domain_name', 'expected_codes', 'http_method', @@ -207,6 +285,9 @@ class TestHealthMonitorUnset(TestHealthMonitor): def test_hm_unset_name(self): self._test_hm_unset_param('name') + def test_hm_unset_name_wait(self): + self._test_hm_unset_param_wait('name') + def test_hm_unset_url_path(self): self._test_hm_unset_param('url_path') @@ -225,6 +306,28 @@ class TestHealthMonitorUnset(TestHealthMonitor): self.api_mock.health_monitor_set.assert_called_once_with( self._hm.id, json=ref_body) + @mock.patch('osc_lib.utils.wait_for_status') + def _test_hm_unset_param_wait(self, param, mock_wait): + self.api_mock.health_monitor_set.reset_mock() + arg_param = param.replace('_', '-') if '_' in param else param + arglist = [self._hm.id, '--%s' % arg_param, '--wait'] + ref_body = {'healthmonitor': {param: None}} + verifylist = [ + ('health_monitor', self._hm.id), + ('wait', True), + ] + for ref_param in self.PARAMETERS: + verifylist.append((ref_param, param == ref_param)) + 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=ref_body) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self._hm.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + def test_hm_unset_all(self): self.api_mock.health_monitor_set.reset_mock() ref_body = {'healthmonitor': {x: None for x in self.PARAMETERS}} diff --git a/octaviaclient/tests/unit/osc/v2/test_l7policy.py b/octaviaclient/tests/unit/osc/v2/test_l7policy.py index ed55894..92f350d 100644 --- a/octaviaclient/tests/unit/osc/v2/test_l7policy.py +++ b/octaviaclient/tests/unit/osc/v2/test_l7policy.py @@ -95,6 +95,24 @@ class TestL7PolicyDelete(TestL7Policy): self.api_mock.l7policy_delete.assert_called_with( l7policy_id=self._l7po.id) + @mock.patch('osc_lib.utils.wait_for_delete') + def test_l7policy_delete_wait(self, mock_wait): + arglist = [self._l7po.id, '--wait'] + verifylist = [ + ('l7policy', self._l7po.id), + ('wait', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.l7policy_delete.assert_called_with( + l7policy_id=self._l7po.id) + mock_wait.assert_called_once_with( + manager=mock.ANY, + res_id=self._l7po.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + def test_l7policy_delete_failure(self): arglist = ['unknown_policy'] verifylist = [ @@ -147,6 +165,47 @@ class TestL7PolicyCreate(TestL7Policy): 'redirect_pool_id': self._l7po.redirect_pool_id }}) + @mock.patch('osc_lib.utils.wait_for_status') + @mock.patch('octaviaclient.osc.v2.utils.get_l7policy_attrs') + def test_l7policy_create_wait(self, mock_attrs, mock_wait): + 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 + } + self.api_mock.listener_show.return_value = { + 'loadbalancers': [{'id': 'mock_lb_id'}]} + self.api_mock.l7policy_show.return_value = self.l7po_info + arglist = ['mock_li_id', + '--name', self._l7po.name, + '--action', 'REDIRECT_TO_POOL'.lower(), + '--redirect-pool', self._l7po.redirect_pool_id, + '--wait'] + + verifylist = [ + ('listener', 'mock_li_id'), + ('name', self._l7po.name), + ('action', 'REDIRECT_TO_POOL'), + ('redirect_pool', self._l7po.redirect_pool_id), + ('wait', True), + ] + + 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 + }}) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id='mock_lb_id', + sleep_time=mock.ANY, + status_field='provisioning_status') + class TestL7PolicyShow(TestL7Policy): @@ -191,6 +250,25 @@ class TestL7PolicySet(TestL7Policy): self.api_mock.l7policy_set.assert_called_with( self._l7po.id, json={'l7policy': {'name': 'new_name'}}) + @mock.patch('osc_lib.utils.wait_for_status') + def test_l7policy_set_wait(self, mock_wait): + arglist = [self._l7po.id, '--name', 'new_name', '--wait'] + verifylist = [ + ('l7policy', self._l7po.id), + ('name', 'new_name'), + ('wait', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.l7policy_set.assert_called_with( + self._l7po.id, json={'l7policy': {'name': 'new_name'}}) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self._l7po.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + class TestL7PolicyUnset(TestL7Policy): PARAMETERS = ('name', 'description', 'redirect_http_code') @@ -205,6 +283,9 @@ class TestL7PolicyUnset(TestL7Policy): def test_l7policy_unset_name(self): self._test_l7policy_unset_param('name') + def test_l7policy_unset_name_wait(self): + self._test_l7policy_unset_param_wait('name') + def test_l7policy_unset_redirect_http_code(self): self._test_l7policy_unset_param('redirect_http_code') @@ -223,6 +304,28 @@ class TestL7PolicyUnset(TestL7Policy): self.api_mock.l7policy_set.assert_called_once_with( self._l7po.id, json=ref_body) + @mock.patch('osc_lib.utils.wait_for_status') + def _test_l7policy_unset_param_wait(self, param, mock_wait): + self.api_mock.l7policy_set.reset_mock() + arg_param = param.replace('_', '-') if '_' in param else param + arglist = [self._l7po.id, '--%s' % arg_param, '--wait'] + ref_body = {'l7policy': {param: None}} + verifylist = [ + ('l7policy', self._l7po.id), + ('wait', True), + ] + for ref_param in self.PARAMETERS: + verifylist.append((ref_param, param == ref_param)) + 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=ref_body) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self._l7po.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + def test_l7policy_unset_all(self): self.api_mock.l7policy_set.reset_mock() ref_body = {'l7policy': {x: None for x in self.PARAMETERS}} diff --git a/octaviaclient/tests/unit/osc/v2/test_l7rule.py b/octaviaclient/tests/unit/osc/v2/test_l7rule.py index 80d0601..a0cf6f7 100644 --- a/octaviaclient/tests/unit/osc/v2/test_l7rule.py +++ b/octaviaclient/tests/unit/osc/v2/test_l7rule.py @@ -89,6 +89,36 @@ class TestL7RuleDelete(TestL7Rule): l7policy_id=self._l7po.id ) + @mock.patch('functools.partial') + @mock.patch('osc_lib.utils.wait_for_delete') + @mock.patch('octaviaclient.osc.v2.utils.get_l7rule_attrs') + def test_l7rule_delete_wait(self, mock_attrs, mock_wait, mock_partial): + mock_attrs.return_value = { + 'l7policy_id': self._l7po.id, + 'l7rule_id': self._l7ru.id, + } + arglist = [self._l7po.id, self._l7ru.id, '--wait'] + verifylist = [ + ('l7policy', self._l7po.id), + ('l7rule', self._l7ru.id), + ('wait', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.l7rule_delete.assert_called_with( + l7rule_id=self._l7ru.id, + l7policy_id=self._l7po.id + ) + mock_partial.assert_called_once_with( + self.api_mock.l7rule_show, self._l7po.id + ) + mock_wait.assert_called_once_with( + manager=mock.ANY, + res_id=self._l7ru.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + class TestL7RuleCreate(TestL7Rule): @@ -131,6 +161,49 @@ class TestL7RuleCreate(TestL7Rule): 'type': 'HOST_NAME'} }) + @mock.patch('osc_lib.utils.wait_for_status') + @mock.patch('octaviaclient.osc.v2.utils.get_l7rule_attrs') + def test_l7rule_create_wait(self, mock_attrs, mock_wait): + mock_attrs.return_value = { + 'l7policy_id': self._l7po.id, + 'compare-type': 'ENDS_WITH', + 'value': '.example.com', + 'type': 'HOST_NAME' + } + self.api_mock.l7policy_show.return_value = { + 'listener_id': 'mock_listener_id'} + self.api_mock.listener_show.return_value = { + 'loadbalancers': [{'id': 'mock_lb_id'}]} + self.api_mock.l7rule_show.return_value = self.l7rule_info + arglist = [self._l7po.id, + '--compare-type', 'ENDS_WITH', + '--value', '.example.com', + '--type', 'HOST_NAME'.lower(), + '--wait'] + + verifylist = [ + ('l7policy', self._l7po.id), + ('compare_type', 'ENDS_WITH'), + ('value', '.example.com'), + ('type', 'HOST_NAME'), + ('wait', True), + ] + + 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'} + }) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id='mock_lb_id', + sleep_time=mock.ANY, + status_field='provisioning_status') + class TestL7RuleShow(TestL7Rule): @@ -188,6 +261,43 @@ class TestL7RuleSet(TestL7Rule): l7policy_id=self._l7po.id, json={'rule': {'admin_state_up': False}}) + @mock.patch('functools.partial') + @mock.patch('osc_lib.utils.wait_for_status') + @mock.patch('octaviaclient.osc.v2.utils.get_l7rule_attrs') + def test_l7rule_set_wait(self, mock_attrs, mock_wait, mock_partial): + mock_attrs.return_value = { + 'admin_state_up': False, + 'l7policy_id': self._l7po.id, + 'l7rule_id': self._l7ru.id + } + arglist = [ + self._l7po.id, + self._l7ru.id, + '--disable', + '--wait', + ] + verifylist = [ + ('l7policy', self._l7po.id), + ('l7rule', self._l7ru.id), + ('disable', True), + ('wait', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.l7rule_set.assert_called_with( + l7rule_id=self._l7ru.id, + l7policy_id=self._l7po.id, + json={'rule': {'admin_state_up': False}}) + mock_partial.assert_called_once_with( + self.api_mock.l7rule_show, self._l7po.id + ) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self._l7ru.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + class TestL7RuleUnset(TestL7Rule): PARAMETERS = ('invert', 'key') @@ -199,6 +309,9 @@ class TestL7RuleUnset(TestL7Rule): def test_l7rule_unset_invert(self): self._test_l7rule_unset_param('invert') + def test_l7rule_unset_invert_wait(self): + self._test_l7rule_unset_param_wait('invert') + def test_l7rule_unset_key(self): self._test_l7rule_unset_param('key') @@ -217,6 +330,33 @@ class TestL7RuleUnset(TestL7Rule): self.api_mock.l7rule_set.assert_called_once_with( l7policy_id=self._l7po.id, l7rule_id=self._l7ru.id, json=ref_body) + @mock.patch('functools.partial') + @mock.patch('osc_lib.utils.wait_for_status') + def _test_l7rule_unset_param_wait(self, param, mock_wait, mock_partial): + self.api_mock.l7rule_set.reset_mock() + arg_param = param.replace('_', '-') if '_' in param else param + arglist = [self._l7po.id, self._l7ru.id, '--%s' % arg_param, '--wait'] + ref_body = {'rule': {param: None}} + verifylist = [ + ('l7policy', self._l7po.id), + ('l7rule_id', self._l7ru.id), + ('wait', True), + ] + for ref_param in self.PARAMETERS: + verifylist.append((ref_param, param == ref_param)) + 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=ref_body) + mock_partial.assert_called_once_with( + self.api_mock.l7rule_show, self._l7po.id + ) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self._l7ru.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + def test_l7rule_unset_all(self): self.api_mock.l7rule_set.reset_mock() ref_body = {'rule': {x: None for x in self.PARAMETERS}} diff --git a/octaviaclient/tests/unit/osc/v2/test_listener.py b/octaviaclient/tests/unit/osc/v2/test_listener.py index 0098785..8b24775 100644 --- a/octaviaclient/tests/unit/osc/v2/test_listener.py +++ b/octaviaclient/tests/unit/osc/v2/test_listener.py @@ -86,6 +86,24 @@ class TestListenerDelete(TestListener): self.api_mock.listener_delete.assert_called_with( listener_id=self._listener.id) + @mock.patch('osc_lib.utils.wait_for_delete') + def test_listener_delete_wait(self, mock_wait): + arglist = [self._listener.id, '--wait'] + verifylist = [ + ('listener', self._listener.id), + ('wait', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.listener_delete.assert_called_with( + listener_id=self._listener.id) + mock_wait.assert_called_once_with( + manager=mock.ANY, + res_id=self._listener.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + def test_listener_delete_failure(self): arglist = ['unknown_lb'] verifylist = [ @@ -127,6 +145,35 @@ class TestListenerCreate(TestListener): self.api_mock.listener_create.assert_called_with( json={'listener': self.listener_info}) + @mock.patch('osc_lib.utils.wait_for_status') + @mock.patch('octaviaclient.osc.v2.utils.get_listener_attrs') + def test_listener_create_wait(self, mock_client, mock_wait): + self.listener_info['loadbalancers'] = [{'id': 'mock_lb_id'}] + mock_client.return_value = self.listener_info + self.api_mock.listener_show.return_value = self.listener_info + arglist = ['mock_lb_id', + '--name', self._listener.name, + '--protocol', 'HTTP', + '--protocol-port', '80', + '--wait'] + verifylist = [ + ('loadbalancer', 'mock_lb_id'), + ('name', self._listener.name), + ('protocol', 'HTTP'), + ('protocol_port', '80'), + ('wait', True), + ] + + 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}) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self.listener_info['loadbalancers'][0]['id'], + sleep_time=mock.ANY, + status_field='provisioning_status') + @mock.patch('octaviaclient.osc.v2.utils.get_listener_attrs') def test_tls_listener_create(self, mock_client): mock_client.return_value = self.listener_info @@ -271,6 +318,25 @@ class TestListenerSet(TestListener): 'allowed_cidrs': self._listener.allowed_cidrs, }}) + @mock.patch('osc_lib.utils.wait_for_status') + def test_listener_set_wait(self, mock_wait): + arglist = [self._listener.id, '--name', 'new_name', '--wait'] + verifylist = [ + ('listener', self._listener.id), + ('name', 'new_name'), + ('wait', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.listener_set.assert_called_with( + self._listener.id, json={'listener': {'name': 'new_name'}}) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self._listener.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + class TestListenerStatsShow(TestListener): @@ -311,6 +377,9 @@ class TestListenerUnset(TestListener): def test_listener_unset_name(self): self._test_listener_unset_param('name') + def test_listener_unset_name_wait(self): + self._test_listener_unset_param_wait('name') + def test_listener_unset_description(self): self._test_listener_unset_param('description') @@ -371,6 +440,31 @@ class TestListenerUnset(TestListener): self.api_mock.listener_set.assert_called_once_with( self._listener.id, json=ref_body) + @mock.patch('osc_lib.utils.wait_for_status') + def _test_listener_unset_param_wait(self, param, mock_wait): + self.api_mock.listener_set.reset_mock() + arg_param = param.replace('_', '-') if '_' in param else param + arglist = [self._listener.id, '--%s' % arg_param, '--wait'] + # Handle the special rename case of default_pool rename + if param == 'default_pool': + param = 'default_pool_id' + ref_body = {'listener': {param: None}} + verifylist = [ + ('listener', self._listener.id), + ('wait', True), + ] + for ref_param in self.PARAMETERS: + verifylist.append((ref_param, param == ref_param)) + 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=ref_body) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self._listener.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + def test_listener_unset_all(self): self.api_mock.listener_set.reset_mock() ref_body = {'listener': {x: None for x in self.PARAMETERS}} diff --git a/octaviaclient/tests/unit/osc/v2/test_load_balancer.py b/octaviaclient/tests/unit/osc/v2/test_load_balancer.py index 133496d..fa8c82c 100644 --- a/octaviaclient/tests/unit/osc/v2/test_load_balancer.py +++ b/octaviaclient/tests/unit/osc/v2/test_load_balancer.py @@ -15,6 +15,7 @@ import copy import itertools import mock +import munch from osc_lib import exceptions from oslo_utils import uuidutils @@ -214,6 +215,24 @@ class TestLoadBalancerDelete(TestLoadBalancer): self.api_mock.load_balancer_delete.assert_called_with( lb_id=self._lb.id) + @mock.patch('osc_lib.utils.wait_for_delete') + def test_load_balancer_delete_wait(self, mock_wait): + arglist = [self._lb.id, '--wait'] + verifylist = [ + ('loadbalancer', self._lb.id), + ('wait', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.load_balancer_delete.assert_called_with( + lb_id=self._lb.id) + mock_wait.assert_called_once_with( + manager=mock.ANY, + res_id=self.lb_info['id'], + sleep_time=mock.ANY, + status_field='provisioning_status') + def test_load_balancer_delete_failure(self): arglist = ['unknown_lb'] verifylist = [ @@ -257,6 +276,34 @@ class TestLoadBalancerCreate(TestLoadBalancer): self.api_mock.load_balancer_create.assert_called_with( json={'loadbalancer': self.lb_info}) + @mock.patch('osc_lib.utils.wait_for_status') + @mock.patch('octaviaclient.osc.v2.utils.get_loadbalancer_attrs') + def test_load_balancer_create_wait(self, mock_client, mock_wait): + mock_client.return_value = self.lb_info + self.api_mock.load_balancer_show.return_value = self.lb_info + arglist = ['--name', self._lb.name, + '--vip-network-id', self._lb.vip_network_id, + '--project', self._lb.project_id, + '--flavor', self._lb.flavor_id, + '--wait'] + verifylist = [ + ('name', self._lb.name), + ('vip_network_id', self._lb.vip_network_id), + ('project', self._lb.project_id), + ('flavor', self._lb.flavor_id), + ('wait', True), + ] + + 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': self.lb_info}) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self.lb_info['id'], + sleep_time=mock.ANY, + status_field='provisioning_status') + @mock.patch('octaviaclient.osc.v2.utils.get_loadbalancer_attrs') def test_load_balancer_create_with_qos_policy(self, mock_client): qos_policy_id = 'qos_id' @@ -322,6 +369,8 @@ class TestLoadBalancerCreate(TestLoadBalancer): # subtract comb's keys from attrs_list filtered_attrs = {k: v for k, v in attrs_list.items() if ( k not in comb)} + # Add the 'wait' attribute, which isn't part of an LB directly + filtered_attrs['wait'] = False mock_client.return_value = filtered_attrs if not any(k in filtered_attrs for k in args) or all( k in filtered_attrs for k in ("vip_network_id", @@ -333,7 +382,7 @@ class TestLoadBalancerCreate(TestLoadBalancer): filtered_attrs) else: try: - self.cmd.take_action(filtered_attrs) + self.cmd.take_action(munch.Munch(filtered_attrs)) except exceptions.CommandError as e: self.fail("%s raised unexpectedly" % e) @@ -393,6 +442,39 @@ class TestLoadBalancerSet(TestLoadBalancer): } }) + @mock.patch('osc_lib.utils.wait_for_status') + @mock.patch('octaviaclient.osc.v2.utils.get_loadbalancer_attrs') + def test_load_balancer_set_wait(self, mock_attrs, mock_wait): + qos_policy_id = uuidutils.generate_uuid() + mock_attrs.return_value = { + 'loadbalancer_id': self._lb.id, + 'name': 'new_name', + 'vip_qos_policy_id': qos_policy_id, + } + arglist = [self._lb.id, '--name', 'new_name', + '--vip-qos-policy-id', qos_policy_id, '--wait'] + verifylist = [ + ('loadbalancer', self._lb.id), + ('name', 'new_name'), + ('vip_qos_policy_id', qos_policy_id), + ('wait', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.load_balancer_set.assert_called_with( + self._lb.id, json={ + 'loadbalancer': { + 'name': 'new_name', + 'vip_qos_policy_id': qos_policy_id, + } + }) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self.lb_info['id'], + sleep_time=mock.ANY, + status_field='provisioning_status') + @mock.patch('octaviaclient.osc.v2.utils.get_loadbalancer_attrs') def test_load_balancer_remove_qos_policy(self, mock_attrs): mock_attrs.return_value = { @@ -477,6 +559,24 @@ class TestLoadBalancerFailover(TestLoadBalancer): self.api_mock.load_balancer_failover.assert_called_with( lb_id=self._lb.id) + @mock.patch('osc_lib.utils.wait_for_status') + def test_load_balancer_failover_wait(self, mock_wait): + arglist = [self._lb.id, '--wait'] + verifylist = [ + ('loadbalancer', self._lb.id), + ('wait', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.load_balancer_failover.assert_called_with( + lb_id=self._lb.id) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self._lb.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + class TestLoadBalancerUnset(TestLoadBalancer): PARAMETERS = ('name', 'description', 'vip_qos_policy_id') @@ -490,6 +590,9 @@ class TestLoadBalancerUnset(TestLoadBalancer): def test_load_balancer_unset_name(self): self._test_load_balancer_unset_param('name') + def test_load_balancer_unset_name_wait(self): + self._test_load_balancer_unset_param_wait('name') + def test_load_balancer_unset_description(self): self._test_load_balancer_unset_param('description') @@ -511,6 +614,28 @@ class TestLoadBalancerUnset(TestLoadBalancer): self.api_mock.load_balancer_set.assert_called_once_with( self._lb.id, json=ref_body) + @mock.patch('osc_lib.utils.wait_for_status') + def _test_load_balancer_unset_param_wait(self, param, mock_wait): + self.api_mock.load_balancer_set.reset_mock() + ref_body = {'loadbalancer': {param: None}} + arg_param = param.replace('_', '-') if '_' in param else param + arglist = [self._lb.id, '--%s' % arg_param, '--wait'] + verifylist = [ + ('loadbalancer', self._lb.id), + ('wait', True), + ] + for ref_param in self.PARAMETERS: + verifylist.append((ref_param, param == ref_param)) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.load_balancer_set.assert_called_once_with( + self._lb.id, json=ref_body) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self.lb_info['id'], + sleep_time=mock.ANY, + status_field='provisioning_status') + def test_load_balancer_unset_all(self): self.api_mock.load_balancer_set.reset_mock() ref_body = {'loadbalancer': {x: None for x in self.PARAMETERS}} diff --git a/octaviaclient/tests/unit/osc/v2/test_member.py b/octaviaclient/tests/unit/osc/v2/test_member.py index 0187f91..29c3b3b 100644 --- a/octaviaclient/tests/unit/osc/v2/test_member.py +++ b/octaviaclient/tests/unit/osc/v2/test_member.py @@ -115,6 +115,44 @@ class TestCreateMember(TestMember): 'admin_state_up': True, 'backup': False}}) + @mock.patch('osc_lib.utils.wait_for_status') + @mock.patch('octaviaclient.osc.v2.utils.get_member_attrs') + def test_member_create_wait(self, mock_attrs, mock_wait): + mock_attrs.return_value = { + 'ip_address': '192.0.2.122', + 'protocol_port': self._mem.protocol_port, + 'weight': self._mem.weight, + 'admin_state_up': True, + 'pool_id': self._mem.pool_id, + 'backup': False} + self.api_mock.pool_show.return_value = { + 'loadbalancers': [{'id': 'mock_lb_id'}]} + self.api_mock.member_show.return_value = self.mem_info + arglist = ['pool_id', '--address', '192.0.2.122', + '--protocol-port', '80', + '--weight', '1', '--enable', '--disable-backup', '--wait'] + verifylist = [ + ('address', '192.0.2.122'), + ('protocol_port', 80), + ('weight', 1), + ('wait', True), + ] + + 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, + 'weight': self._mem.weight, + 'admin_state_up': True, + 'backup': False}}) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id='mock_lb_id', + sleep_time=mock.ANY, + status_field='provisioning_status') + class TestMemberDelete(TestMember): @@ -124,15 +162,44 @@ class TestMemberDelete(TestMember): @mock.patch('octaviaclient.osc.v2.utils.get_member_attrs') def test_member_delete(self, mock_attrs): - mock_attrs.return_value = {'pool_id': 'test_pool_id', - 'member_id': 'test_mem_id'} - arglist = ['test_pool_id', 'test_mem_id'] - verifylist = [] + mock_attrs.return_value = {'pool_id': self._mem.pool_id, + 'member_id': self._mem.id} + arglist = [self._mem.pool_id, self._mem.id] + verifylist = [ + ('pool', self._mem.pool_id), + ('member', self._mem.id), + ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.api_mock.member_delete.assert_called_with( - pool_id='test_pool_id', member_id='test_mem_id') + pool_id=self._mem.pool_id, member_id=self._mem.id) + + @mock.patch('functools.partial') + @mock.patch('osc_lib.utils.wait_for_delete') + @mock.patch('octaviaclient.osc.v2.utils.get_member_attrs') + def test_member_delete_wait(self, mock_attrs, mock_wait, mock_partial): + mock_attrs.return_value = {'pool_id': self._mem.pool_id, + 'member_id': self._mem.id} + arglist = [self._mem.pool_id, self._mem.id, '--wait'] + verifylist = [ + ('pool', self._mem.pool_id), + ('member', self._mem.id), + ('wait', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.member_delete.assert_called_with( + pool_id=self._mem.pool_id, member_id=self._mem.id) + mock_partial.assert_called_once_with( + self.api_mock.member_show, self._mem.pool_id + ) + mock_wait.assert_called_once_with( + manager=mock.ANY, + res_id=self._mem.id, + sleep_time=mock.ANY, + status_field='provisioning_status') class TestMemberSet(TestMember): @@ -143,25 +210,56 @@ class TestMemberSet(TestMember): @mock.patch('octaviaclient.osc.v2.utils.get_member_attrs') def test_member_set(self, mock_attrs): - mock_attrs.return_value = {'pool_id': 'test_pool_id', - 'member_id': 'test_mem_id', + mock_attrs.return_value = {'pool_id': self._mem.pool_id, + 'member_id': self._mem.id, 'name': 'new_name', 'backup': True} - arglist = ['test_pool_id', 'test_mem_id', '--name', + arglist = [self._mem.pool_id, self._mem.id, '--name', 'new_name', '--enable-backup'] verifylist = [ - ('pool', 'test_pool_id'), - ('member', 'test_mem_id'), - ('name', 'new_name') + ('pool', self._mem.pool_id), + ('member', self._mem.id), + ('name', 'new_name'), + ('enable_backup', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) self.api_mock.member_set.assert_called_with( - pool_id='test_pool_id', member_id='test_mem_id', + pool_id=self._mem.pool_id, member_id=self._mem.id, json={'member': {'name': 'new_name', 'backup': True}}) + @mock.patch('functools.partial') + @mock.patch('osc_lib.utils.wait_for_status') + @mock.patch('octaviaclient.osc.v2.utils.get_member_attrs') + def test_member_set_wait(self, mock_attrs, mock_wait, mock_partial): + mock_attrs.return_value = {'pool_id': self._mem.pool_id, + 'member_id': self._mem.id, + 'name': 'new_name'} + arglist = [self._mem.pool_id, self._mem.id, '--name', + 'new_name', '--wait'] + verifylist = [ + ('pool', self._mem.pool_id), + ('member', self._mem.id), + ('name', 'new_name'), + ('wait', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.member_set.assert_called_with( + pool_id=self._mem.pool_id, member_id=self._mem.id, + json={'member': {'name': 'new_name'}}) + mock_partial.assert_called_once_with( + self.api_mock.member_show, self._mem.pool_id + ) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self._mem.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + class TestMemberShow(TestMember): @@ -211,6 +309,9 @@ class TestMemberUnset(TestMember): def test_member_unset_name(self): self._test_member_unset_param('name') + def test_member_unset_name_wait(self): + self._test_member_unset_param_wait('name') + def test_member_unset_weight(self): self._test_member_unset_param('weight') @@ -219,7 +320,10 @@ class TestMemberUnset(TestMember): arg_param = param.replace('_', '-') if '_' in param else param arglist = [self._mem.pool_id, self._mem.id, '--%s' % arg_param] ref_body = {'member': {param: None}} - verifylist = [('member', self._mem.id)] + verifylist = [ + ('pool', self._mem.pool_id), + ('member', self._mem.id), + ] for ref_param in self.PARAMETERS: verifylist.append((ref_param, param == ref_param)) parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -227,6 +331,34 @@ class TestMemberUnset(TestMember): self.api_mock.member_set.assert_called_once_with( pool_id=self._mem.pool_id, member_id=self._mem.id, json=ref_body) + @mock.patch('functools.partial') + @mock.patch('osc_lib.utils.wait_for_status') + def _test_member_unset_param_wait(self, param, mock_wait, mock_partial): + self.api_mock.member_set.reset_mock() + arg_param = param.replace('_', '-') if '_' in param else param + arglist = [self._mem.pool_id, self._mem.id, '--%s' % arg_param, + '--wait'] + ref_body = {'member': {param: None}} + verifylist = [ + ('pool', self._mem.pool_id), + ('member', self._mem.id), + ('wait', True), + ] + for ref_param in self.PARAMETERS: + verifylist.append((ref_param, param == ref_param)) + 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=ref_body) + mock_partial.assert_called_once_with( + self.api_mock.member_show, self._mem.pool_id + ) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self._mem.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + def test_member_unset_all(self): self.api_mock.pool_set.reset_mock() ref_body = {'member': {x: None for x in self.PARAMETERS}} diff --git a/octaviaclient/tests/unit/osc/v2/test_pool.py b/octaviaclient/tests/unit/osc/v2/test_pool.py index 0fbfd1d..a8baf43 100644 --- a/octaviaclient/tests/unit/osc/v2/test_pool.py +++ b/octaviaclient/tests/unit/osc/v2/test_pool.py @@ -77,6 +77,24 @@ class TestPoolDelete(TestPool): self.api_mock.pool_delete.assert_called_with( pool_id=self._po.id) + @mock.patch('osc_lib.utils.wait_for_delete') + def test_pool_delete_wait(self, mock_wait): + arglist = [self._po.id, '--wait'] + verifylist = [ + ('pool', self._po.id), + ('wait', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.pool_delete.assert_called_with( + pool_id=self._po.id) + mock_wait.assert_called_once_with( + manager=mock.ANY, + res_id=self._po.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + def test_listener_delete_failure(self): arglist = ['unknown_pool'] verifylist = [ @@ -127,6 +145,36 @@ class TestPoolCreate(TestPool): 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): + self.pool_info['loadbalancers'] = [{'id': 'mock_lb_id'}] + mock_attrs.return_value = self.pool_info + self.api_mock.pool_show.return_value = self.pool_info + arglist = ['--loadbalancer', 'mock_lb_id', + '--name', self._po.name, + '--protocol', 'HTTP', + '--lb-algorithm', 'ROUND_ROBIN', + '--wait'] + + verifylist = [ + ('loadbalancer', 'mock_lb_id'), + ('name', self._po.name), + ('protocol', 'HTTP'), + ('lb_algorithm', 'ROUND_ROBIN'), + ('wait', True), + ] + + 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_wait.assert_called_once_with( + status_f=mock.ANY, + res_id='mock_lb_id', + sleep_time=mock.ANY, + status_field='provisioning_status') + class TestPoolShow(TestPool): @@ -175,6 +223,24 @@ class TestPoolSet(TestPool): 'crl_container_ref': new_crl_id, 'tls_enabled': True}}) + @mock.patch('osc_lib.utils.wait_for_status') + def test_pool_set_wait(self, mock_wait): + arglist = [self._po.id, '--name', 'new_name', '--wait'] + verifylist = [ + ('pool', self._po.id), + ('name', 'new_name'), + ('wait', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + self.api_mock.pool_set.assert_called_with( + self._po.id, json={'pool': {'name': 'new_name'}}) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self._po.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + class TestPoolUnset(TestPool): PARAMETERS = ('name', 'description', 'ca_tls_container_ref', @@ -188,6 +254,9 @@ class TestPoolUnset(TestPool): def test_pool_unset_name(self): self._test_pool_unset_param('name') + def test_pool_unset_name_wait(self): + self._test_pool_unset_param_wait('name') + def test_pool_unset_description(self): self._test_pool_unset_param('description') @@ -218,6 +287,28 @@ class TestPoolUnset(TestPool): self.api_mock.pool_set.assert_called_once_with( self._po.id, json=ref_body) + @mock.patch('osc_lib.utils.wait_for_status') + def _test_pool_unset_param_wait(self, param, mock_wait): + self.api_mock.pool_set.reset_mock() + arg_param = param.replace('_', '-') if '_' in param else param + arglist = [self._po.id, '--%s' % arg_param, '--wait'] + ref_body = {'pool': {param: None}} + verifylist = [ + ('pool', self._po.id), + ('wait', True), + ] + for ref_param in self.PARAMETERS: + verifylist.append((ref_param, param == ref_param)) + 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=ref_body) + mock_wait.assert_called_once_with( + status_f=mock.ANY, + res_id=self._po.id, + sleep_time=mock.ANY, + status_field='provisioning_status') + def test_pool_unset_all(self): self.api_mock.pool_set.reset_mock() ref_body = {'pool': {x: None for x in self.PARAMETERS}} diff --git a/releasenotes/notes/add-wait-option-to-CUD-commands-97375387e7762b83.yaml b/releasenotes/notes/add-wait-option-to-CUD-commands-97375387e7762b83.yaml new file mode 100644 index 0000000..273deed --- /dev/null +++ b/releasenotes/notes/add-wait-option-to-CUD-commands-97375387e7762b83.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Commands that can cause loadbalancers to enter an immutable status (Create, + Update, and Delete operations) now have a ``--wait`` argument. If set, the + client will continue to poll until the status is no longer immutable.