def test_update_health_monitor(self): """ `:func:update_health_monitor` will call ``PUT .../loadbalancers/lb_id/healthmonitor`` with udpated config, succeeds on 202, and retries on pending update for 60 seconds. It does not retry if the error is not PENDING_UPDATE. """ main_treq_args = [ 'put', 'clburl/loadbalancers/12345/healthmonitor', (('{"healthMonitor": {"type": "CONNECT"}}', ), self.expected_kwargs) ] def update(clb, clock): return clb.update_health_monitor(self.rcs, {"type": "CONNECT"}, clock=clock) self.assert_mutate_function_retries_until_success( update, main_treq_args, (Response(202), ""), None) self.assert_mutate_function_retries_until_timeout( update, main_treq_args, 60) self.assert_mutate_function_does_not_retry_if_not_pending_update( update, main_treq_args)
def get_fake_treq_for_delete(self, get_response, del_response=None): """ Return a CLB for use with deleting a CLB - this is different than the one returned by `get_clb` because it requires stubbing out two treq requests. """ del_response = del_response or Response(202) class FakeTreq(object): def delete(cls, _url, *args, **kwargs): # args and kwargs are the same as the get ones self.assertEqual(args, ()) self.assertEqual(kwargs, self.expected_kwargs) self.assertEqual( _url, 'clburl/loadbalancers/{0}'.format(self.clb_id)) return succeed(del_response) def get(cls, _url, *args, **kwargs): cls.delete(_url, *args, **kwargs) return succeed(get_response) def content(cls, resp): return succeed(resp.strbody) def json_content(cls, resp): return succeed(json.loads(resp.strbody)) return FakeTreq()
def assert_mutate_function_does_not_retry_if_not_pending_update( self, mutate_callable, expected_args): """ Assert that some CLB function that mutates the CLB will not retry if the error is not a pending update. :param mutate_callable: a callable which takes a clb argument and a clock argument - this callable should call the CLB's mutate function with the required arguments and return the function's return value. For example: ``lambda clb, clk: clb.update_node(..., clock=clk)`` :param expected_args: What are the expected treq arguments? This should be an array of [method, url, (expected args, expected kwargs)] """ clock = Clock() pending_delete = { "message": ("Load Balancer '12345' has a status of " "'PENDING_DELETE' and is considered immutable."), "code": 422 } clb = self.get_clb( *(expected_args + [Response(422), json.dumps(pending_delete)])) d = mutate_callable(clb, clock) self.failureResultOf(d, UpstreamError)
def get(cls, url, headers, pool): self.get_calls += 1 self.assertIs(self.pool, pool) self.assertEqual(["token"], headers.get('x-auth-token')) self.assertEqual( ['clburl', 'loadbalancers', 'clb_id', 'nodes'], url.split('/')) return succeed(Response(200))
def test_list_nodes(self): """ Listing nodes calls the right endpoint and succeeds on 200. """ clb = self.get_clb('get', 'clburl/loadbalancers/12345/nodes', ((), self.expected_kwargs), Response(200), '{"nodes": []}') d = clb.list_nodes(self.rcs) self.assertEqual({'nodes': []}, self.successResultOf(d))
def setUp(self): """ Set up fake pool, treq, responses, and RCS. """ self.pool = object() self.rcs = object() self.expected_kwargs = {'pool': self.pool} self.delete_treq = get_fake_treq( self, 'DELETE', "/mimic/v1.1/IdentityControlAPI/behaviors/some_event/behavior_id", ((), self.expected_kwargs), (Response(204), "successfully deleted behavior"))
def test_delete_clb_retries_until_timeout(self): """ Deleting a CLB will retry if the state wonky until it times out. """ clock = Clock() self.clb_id = 12345 _treq = self.get_fake_treq_for_delete(Response( 200, strbody='{"loadBalancer": {"status": "PENDING_UPDATE"}}'), del_response=Response(400)) clb = CloudLoadBalancer(pool=self.pool, treq=_treq) clb.clb_id = self.clb_id d = clb.delete(self.rcs, clock=clock) self.assertNoResult(d) timeout = 60 for _ in range((timeout - 1) / 3): clock.pump([3]) self.assertNoResult(d) clock.pump([3]) self.failureResultOf(d, TimedOutError)
def test_delete_clb_does_not_retry_on_get_failure(self): """ Deleting a CLB will retry if the state wonky until it times out. """ clock = Clock() self.clb_id = 12345 _treq = self.get_fake_treq_for_delete( Response(400, strbody="Something is wrong")) clb = CloudLoadBalancer(pool=self.pool, treq=_treq) clb.clb_id = self.clb_id d = clb.delete(self.rcs, clock=clock) self.failureResultOf(d, UpstreamError)
def setUp(self): """ Set up fake pool, treq, responses, and RCS. """ self.pool = object() class FakeRCS(object): endpoints = {'mimic_nova': 'mimicnovaurl'} self.rcs = FakeRCS() self.server_id = 'server_id' self.expected_kwargs = {'pool': self.pool} self.delete_treq = get_fake_treq( self, 'DELETE', "mimicnovaurl/behaviors/some_event/behavior_id", ((), self.expected_kwargs), (Response(204), "successfully deleted behavior"))
def test_change_server_statuses(self): """ Change server statuses calls the right endpoint and succeeds on 201. """ _treq = get_fake_treq( self, 'POST', "mimicnovaurl/attributes", ((json.dumps({'status': { 'id1': 'ERROR', 'id2': 'DELETED' }}), ), self.expected_kwargs), (Response(201), "successful change response")) d = MimicNova(pool=self.pool, treq=_treq).change_server_statuses( self.rcs, { 'id1': 'ERROR', 'id2': 'DELETED' }) self.assertEqual('successful change response', self.successResultOf(d))
def test_sequenced_behaviors(self): """ Cause a sequence of behaviors, and succeeds on 201. When a test case is provided for which a cleanup should be added, delete is added as a cleanup. """ criteria = [{"username": "******"}] behaviors = [{ 'name': "behavior name", 'parameters': { "behavior": "params" } }] _treq = get_fake_treq( self, 'POST', "/mimic/v1.1/IdentityControlAPI/behaviors/some_event", ((json.dumps({ 'criteria': criteria, 'name': "sequence", 'parameters': { "behaviors": behaviors } }), ), self.expected_kwargs), (Response(201), '{"id": "behavior_id"}')) test_case = _get_fake_test_case(_treq, self.delete_treq) mimic_identity = MimicIdentity(pool=self.pool, test_case=test_case, treq=_treq) d = mimic_identity.sequenced_behaviors("/identity/v2.0", criteria, behaviors, event_description="some_event") self.assertEqual("behavior_id", self.successResultOf(d)) self.assertEqual("successfully deleted behavior", self.successResultOf(test_case.cleanup()))
def test_update_node(self): """ Update node calls the right endpoint, succeeds on 202, and retries on pending update for 60 seconds. It does not retry if the error is not PENDING_UPDATE. """ main_treq_args = [ 'put', 'clburl/loadbalancers/12345/nodes/54321', (('{"node": {"weight": 5}}', ), self.expected_kwargs) ] def update(clb, clock): return clb.update_node(self.rcs, 54321, weight=5, clock=clock) self.assert_mutate_function_retries_until_success( update, main_treq_args, (Response(202), ""), "") self.assert_mutate_function_retries_until_timeout( update, main_treq_args, 60) self.assert_mutate_function_does_not_retry_if_not_pending_update( update, main_treq_args)
def test_delete_node(self): """ Deleting one or more nodes calls the right endpoint, succeeds on 202, and retries on pending update for 60 seconds. It does not retry if the error is not PENDING_UPDATE. """ self.expected_kwargs['params'] = [("id", 11111), ("id", 22222)] main_treq_args = [ 'delete', 'clburl/loadbalancers/12345/nodes', ((), self.expected_kwargs) ] def delete(clb, clock): return clb.delete_nodes(self.rcs, (11111, 22222), clock=clock) self.assert_mutate_function_retries_until_success( delete, main_treq_args, (Response(202), ""), "") self.assert_mutate_function_retries_until_timeout( delete, main_treq_args, 60) self.assert_mutate_function_does_not_retry_if_not_pending_update( delete, main_treq_args)
def test_add_node(self): """ Adding one or more nodes calls the right endpoint, succeeds on 202, and retries on pending update for 60 seconds. It does not retry if the error is not PENDING_UPDATE. """ nodes_to_add = { "nodes": [{ "address": "10.2.2.3", "port": 80, "condition": "ENABLED", "type": "PRIMARY" }, { "address": "10.2.2.4", "port": 81, "condition": "ENABLED", "type": "SECONDARY" }] } main_treq_args = [ 'post', 'clburl/loadbalancers/12345/nodes', ((json.dumps(nodes_to_add), ), self.expected_kwargs) ] def add(clb, clock): return clb.add_nodes(self.rcs, nodes_to_add["nodes"], clock=clock) self.assert_mutate_function_retries_until_success( add, main_treq_args, (Response(202), json.dumps(nodes_to_add)), nodes_to_add) self.assert_mutate_function_retries_until_timeout( add, main_treq_args, 60) self.assert_mutate_function_does_not_retry_if_not_pending_update( add, main_treq_args)
def test_delete_clb_retries_until_success(self): """ Deleting a CLB will retry until the CLB is deleted (or in error or suspended mode, in which case it will give up). """ self.clb_id = 12345 success_treqs = [ # All of these particular immutable states count as success. self.get_fake_treq_for_delete(Response( 200, strbody=json.dumps({"loadBalancer": { "status": state }})), del_response=Response(400)) for state in ("PENDING_DELETE", "DELETED", "ERROR", "SUSPENDED") ] + [ # 404 from get-ting the server, meaning it's already gone. self.get_fake_treq_for_delete(Response( 404, strbody=('{"message": "No such load balancer", "code": 404}')), del_response=Response(400)) ] for success_treq in success_treqs: clock = Clock() _treq = self.get_fake_treq_for_delete(Response( 200, strbody='{"loadBalancer": {"status": "PENDING_UPDATE"}}'), del_response=Response(400)) clb = CloudLoadBalancer(pool=self.pool, treq=_treq) clb.clb_id = self.clb_id d = clb.delete(self.rcs, clock=clock) self.assertNoResult(d) clock.pump([3]) self.assertNoResult(d) clb.treq = success_treq clock.pump([3]) self.assertEqual(self.successResultOf(d), None)
from otter.integration.lib.cloud_load_balancer import (CloudLoadBalancer, ContainsAllIPs, ExcludesAllIPs, HasLength) from otter.integration.lib.test_nova import Response, get_fake_treq from otter.util.deferredutils import TimedOutError from otter.util.http import UpstreamError, headers class _FakeRCS(object): endpoints = {'loadbalancers': 'clburl'} token = "token" pending_update_response = [ Response(422), json.dumps({ "message": ("Load Balancer '12345' has a status of " "'PENDING_UPDATE' and is considered immutable."), "code": 422 }) ] class CLBTests(SynchronousTestCase): """ Tests for the :class:`CloudLoadBalancer` API calls. """ def setUp(self): """