def test_list_servers_details_all_blows_up_if_got_same_link_twice(self): """ :func:`list_servers_details_all` raises an exception if Nova returns the same next link twice in a row. """ bodies = [{ 'servers': ['1', '2'], 'servers_links': [{ 'href': 'doesnt_matter_url?marker=3', 'rel': 'next' }] }, { 'servers': ['3', '4'], 'servers_links': [{ 'href': 'doesnt_matter_url?marker=3', 'rel': 'next' }, { 'href': 'doesnt_matter_url?marker=1', 'rel': 'prev' }] }] resps = [json.dumps(d) for d in bodies] eff = list_servers_details_all({'marker': ['1']}) seq = [ (self._list_server_details_intent({'marker': ['1']}), service_request_eqf(stub_pure_response(resps[0], 200))), (self._list_server_details_log_intent(bodies[0]), lambda _: None), (self._list_server_details_intent({'marker': ['3']}), service_request_eqf(stub_pure_response(resps[1], 200))), (self._list_server_details_log_intent(bodies[1]), lambda _: None) ] self.assertRaises(NovaComputeFaultError, perform_sequence, seq, eff)
def test_change_clb_node_default_type(self): """ Produce a request for modifying a node on a load balancer with the default type, which returns a successful result on 202. """ eff = change_clb_node(lb_id=self.lb_id, node_id='1234', condition="DRAINING", weight=50) expected = service_request(ServiceType.CLOUD_LOAD_BALANCERS, 'PUT', 'loadbalancers/{0}/nodes/1234'.format( self.lb_id), data={ 'node': { 'condition': 'DRAINING', 'weight': 50, 'type': 'PRIMARY' } }, success_pred=has_code(202)) dispatcher = EQFDispatcher([ (expected.intent, service_request_eqf(stub_pure_response('', 202))) ]) self.assertEqual(sync_perform(dispatcher, eff), stub_pure_response(None, 202))
def test_list_servers_details_all_gets_until_no_next_link(self): """ :func:`list_servers_details_all` follows the servers links until there are no more links, and returns a list of servers as the result. It ignores any non-next links. """ bodies = [ {'servers': ['1', '2'], 'servers_links': [{'href': 'doesnt_matter_url?marker=3', 'rel': 'next'}]}, {'servers': ['3', '4'], 'servers_links': [{'href': 'doesnt_matter_url?marker=5', 'rel': 'next'}, {'href': 'doesnt_matter_url?marker=1', 'rel': 'prev'}]}, {'servers': ['5', '6'], 'servers_links': [{'href': 'doesnt_matter_url?marker=3', 'rel': 'prev'}]} ] resps = [json.dumps(d) for d in bodies] eff = list_servers_details_all({'marker': ['1']}) seq = [ (self._list_server_details_intent({'marker': ['1']}), service_request_eqf(stub_pure_response(resps[0], 200))), (self._list_server_details_log_intent(bodies[0]), lambda _: None), (self._list_server_details_intent({'marker': ['3']}), service_request_eqf(stub_pure_response(resps[1], 200))), (self._list_server_details_log_intent(bodies[1]), lambda _: None), (self._list_server_details_intent({'marker': ['5']}), service_request_eqf(stub_pure_response(resps[2], 200))), (self._list_server_details_log_intent(bodies[2]), lambda _: None) ] result = perform_sequence(seq, eff) self.assertEqual(result, ['1', '2', '3', '4', '5', '6'])
def test_list_servers_details_all_blows_up_if_got_same_link_twice(self): """ :func:`list_servers_details_all` raises an exception if Nova returns the same next link twice in a row. """ bodies = [ {'servers': ['1', '2'], 'servers_links': [{'href': 'doesnt_matter_url?marker=3', 'rel': 'next'}]}, {'servers': ['3', '4'], 'servers_links': [{'href': 'doesnt_matter_url?marker=3', 'rel': 'next'}, {'href': 'doesnt_matter_url?marker=1', 'rel': 'prev'}]} ] resps = [json.dumps(d) for d in bodies] eff = list_servers_details_all({'marker': ['1']}) seq = [ (self._list_server_details_intent({'marker': ['1']}), service_request_eqf(stub_pure_response(resps[0], 200))), (self._list_server_details_log_intent(bodies[0]), lambda _: None), (self._list_server_details_intent({'marker': ['3']}), service_request_eqf(stub_pure_response(resps[1], 200))), (self._list_server_details_log_intent(bodies[1]), lambda _: None) ] self.assertRaises(NovaComputeFaultError, perform_sequence, seq, eff)
def test_publish_autoscale_event(self): """ Publish an event to cloudfeeds. Successfully handle non-JSON data. """ _log = object() eff = cf.publish_autoscale_event({'event': 'stuff'}, log=_log) expected = service_request( ServiceType.CLOUD_FEEDS, 'POST', 'autoscale/events', headers={'content-type': ['application/vnd.rackspace.atom+json']}, data={'event': 'stuff'}, log=_log, success_pred=has_code(201), json_response=False) # success dispatcher = EQFDispatcher([( expected.intent, service_request_eqf(stub_pure_response('<this is xml>', 201)))]) resp, body = sync_perform(dispatcher, eff) self.assertEqual(body, '<this is xml>') # Add regression test that 202 should be an API error because this # is a bug in CF dispatcher = EQFDispatcher([( expected.intent, service_request_eqf(stub_pure_response('<this is xml>', 202)))]) self.assertRaises(APIError, sync_perform, dispatcher, eff)
def test_add_clb_nodes(self): """ Produce a request for adding nodes to a load balancer, which returns a successful result on a 202. Parse the common CLB errors, and a :class:`CLBDuplicateNodesError`. """ nodes = [{"address": "1.1.1.1", "port": 80, "condition": "ENABLED"}, {"address": "1.1.1.2", "port": 80, "condition": "ENABLED"}, {"address": "1.1.1.5", "port": 81, "condition": "ENABLED"}] eff = add_clb_nodes(lb_id=self.lb_id, nodes=nodes) expected = service_request( ServiceType.CLOUD_LOAD_BALANCERS, 'POST', 'loadbalancers/{0}/nodes'.format(self.lb_id), data={'nodes': nodes}, success_pred=has_code(202)) # success seq = [ (expected.intent, lambda i: stub_json_response({}, 202, {})), (log_intent('request-add-clb-nodes', {}), lambda _: None)] self.assertEqual(perform_sequence(seq, eff), (StubResponse(202, {}), {})) # CLBDuplicateNodesError failure msg = ("Duplicate nodes detected. One or more nodes already " "configured on load balancer.") duplicate_nodes = stub_pure_response( json.dumps({'message': msg, 'code': 422}), 422) dispatcher = EQFDispatcher([( expected.intent, service_request_eqf(duplicate_nodes))]) with self.assertRaises(CLBDuplicateNodesError) as cm: sync_perform(dispatcher, eff) self.assertEqual( cm.exception, CLBDuplicateNodesError(msg, lb_id=six.text_type(self.lb_id))) # CLBNodeLimitError failure msg = "Nodes must not exceed 25 per load balancer." limit = stub_pure_response( json.dumps({'message': msg, 'code': 413}), 413) dispatcher = EQFDispatcher([( expected.intent, service_request_eqf(limit))]) with self.assertRaises(CLBNodeLimitError) as cm: sync_perform(dispatcher, eff) self.assertEqual( cm.exception, CLBNodeLimitError(msg, lb_id=six.text_type(self.lb_id), node_limit=25)) # all the common failures assert_parses_common_clb_errors(self, expected.intent, eff, "123456")
def test_change_clb_node(self): """ Produce a request for modifying a node on a load balancer, which returns a successful result on 202. Parse the common CLB errors, and :class:`NoSuchCLBNodeError`. """ eff = change_clb_node(lb_id=self.lb_id, node_id='1234', condition="DRAINING", weight=50, _type='SECONDARY') expected = service_request(ServiceType.CLOUD_LOAD_BALANCERS, 'PUT', 'loadbalancers/{0}/nodes/1234'.format( self.lb_id), data={ 'node': { 'condition': 'DRAINING', 'weight': 50, 'type': 'SECONDARY' } }, success_pred=has_code(202)) # success dispatcher = EQFDispatcher([ (expected.intent, service_request_eqf(stub_pure_response('', 202))) ]) self.assertEqual(sync_perform(dispatcher, eff), stub_pure_response(None, 202)) # NoSuchCLBNode failure msg = "Node with id #1234 not found for loadbalancer #{0}".format( self.lb_id) no_such_node = stub_pure_response( json.dumps({ 'message': msg, 'code': 404 }), 404) dispatcher = EQFDispatcher([(expected.intent, service_request_eqf(no_such_node))]) with self.assertRaises(NoSuchCLBNodeError) as cm: sync_perform(dispatcher, eff) self.assertEqual( cm.exception, NoSuchCLBNodeError(msg, lb_id=six.text_type(self.lb_id), node_id=u'1234')) # all the common failures assert_parses_common_clb_errors(self, expected.intent, eff, "123456")
def test_add_json_response(self): """The produced request function results in a parsed data structure.""" response = stub_pure_response('{"a": "b"}', 200) request_ = add_json_response(stub_request(response)) self.assertEqual(resolve_stubs(request_('m', 'u')), (response[0], { 'a': 'b' }))
def assert_handles_nova_rate_limiting(self, intent, effect): """ If the provided intent returns a response consistent with Nova rate-limiting requests, then performing the effect will raise a :class:`NovaRateLimitError`. """ failure_body = { "overLimit": { "code": 413, "message": "OverLimit Retry...", "details": "Error Details...", "retryAfter": "2015-02-27T23:42:27Z" } } dispatcher = EQFDispatcher([ (intent, service_request_eqf( stub_pure_response(json.dumps(failure_body), 413))) ]) with self.assertRaises(NovaRateLimitError) as cm: sync_perform(dispatcher, effect) self.assertEqual(cm.exception, NovaRateLimitError("OverLimit Retry..."))
def test_throttling(self): """ When the throttler function returns a bracketing function, it's used to throttle the request. """ def throttler(stype, method, tid): if (stype == ServiceType.CLOUD_SERVERS and method == 'get' and tid == 1): return bracket bracket = object() svcreq = service_request(ServiceType.CLOUD_SERVERS, 'GET', 'servers').intent response = stub_pure_response({}, 200) seq = SequenceDispatcher([ (_Throttle(bracket=bracket, effect=mock.ANY), nested_sequence([ (Authenticate(authenticator=self.authenticator, tenant_id=1, log=self.log), lambda i: ('token', fake_service_catalog)), (Request(method='GET', url='http://dfw.openstack/servers', headers=headers('token'), log=self.log), lambda i: response), ])), ]) eff = self._concrete(svcreq, throttler=throttler) with seq.consume(): result = sync_perform(seq, eff) self.assertEqual(result, (response[0], {}))
def test_throttling(self): """ When the throttler function returns a bracketing function, it's used to throttle the request. """ def throttler(stype, method, tid): if (stype == ServiceType.CLOUD_SERVERS and method == 'get' and tid == 1): return bracket bracket = object() svcreq = service_request( ServiceType.CLOUD_SERVERS, 'GET', 'servers').intent response = stub_pure_response({}, 200) seq = SequenceDispatcher([ (_Throttle(bracket=bracket, effect=mock.ANY), nested_sequence([ (Authenticate(authenticator=self.authenticator, tenant_id=1, log=self.log), lambda i: ('token', fake_service_catalog)), (Request(method='GET', url='http://dfw.openstack/servers', headers=headers('token'), log=self.log), lambda i: response), ])), ]) eff = self._concrete(svcreq, throttler=throttler) with seq.consume(): result = sync_perform(seq, eff) self.assertEqual(result, (response[0], {}))
def _test_reauth(self, code, reauth_codes=None): reauth_effect = Effect(Constant(None)) def get_auth_headers(): return Effect(Constant(headers("first-token"))) def refresh_auth_info(): return reauth_effect # First we try to make a simple request. kwargs = {} if reauth_codes is not None: kwargs['reauth_codes'] = reauth_codes eff = request("get", "/foo", get_auth_headers=get_auth_headers, refresh_auth_info=refresh_auth_info, **kwargs) # The initial (cached) auth headers are retrieved. eff = resolve_stubs(eff) # when an authentication error is returned from the HTTP server, # the auth info is automitacally refreshed: stub_result = stub_pure_response("badauth!", code=code) reauth_effect_result = resolve_effect(eff, stub_result) self.assertIs(reauth_effect_result.intent, reauth_effect.intent) # And the original auth error HTTP response is still returned. api_error = self.assertRaises(APIError, resolve_stubs, reauth_effect_result) self.assertEqual(api_error.code, code) self.assertEqual(api_error.body, "badauth!") self.assertEqual(api_error.headers, {})
def test_json_response(self): """The JSON response is decoded into Python objects.""" request_ = self._no_reauth_client() eff = request_("get", "/foo") self.assertEqual( resolve_effect(eff, stub_pure_response({"foo": "bar"})), {'foo': 'bar'})
def test_add_effect_on_response(self): """Test the decorator :func:`add_effect_on_response`.""" badauth = stub_pure_response("badauth!", code=401) request_ = add_effect_on_response(self.invalidate_effect, (401, ), stub_request(badauth)) eff = request_('m', 'u') self.assertEqual(resolve_stubs(eff), badauth) self.assertEqual(self.invalidations, [True])
def test_remove_clb_nodes_non_202(self): """Any random HTTP response code is bubbled up as an APIError.""" eff = remove_clb_nodes(self.lb_id, ["1", "2"]) seq = [ (self.expected_node_removal_req().intent, service_request_eqf(stub_pure_response({}, 200))), ] self.assertRaises(APIError, perform_sequence, seq, eff)
def test_add_effect_on_response(self): """Test the decorator :func:`add_effect_on_response`.""" badauth = stub_pure_response("badauth!", code=401) request_ = add_effect_on_response( self.invalidate_effect, (401,), stub_request(badauth)) eff = request_('m', 'u') self.assertEqual(resolve_stubs(eff), badauth) self.assertEqual(self.invalidations, [True])
def test_success(self): """ :func:`check_response` returns the value passed into it if the predicate likes the response. """ pred = lambda _response, _content: True result = stub_pure_response(None) self.assertIdentical(check_response(pred, result), result)
def test_error(self): """ :func:`check_response` raises :class:`APIError` if the predicate doesn't like the response. """ pred = lambda _response, _content: False result = stub_pure_response(None) self.assertRaises(APIError, check_response, pred, result)
def test_error(self): """ :func:`add_error_handling` ostensibly invokes :func:`check_response`. """ response = stub_pure_response("", code=404) request_fn = add_error_handling(has_code(200), stub_request(response)) eff = request_fn('GET', '/xyzzy') self.assertRaises(APIError, resolve_stubs, eff)
def test_empty_json_response(self): """ If the body is empty, it will be turned into :data:`None`, and not passed to the JSON parser. """ response = stub_pure_response('', 204) request_ = add_json_response(stub_request(response)) self.assertEqual(resolve_stubs(request_('m', 'u')), (response[0], None))
def test_invalidate_unnecessary(self): """ The result is returned immediately and the provided effect is not invoked when the HTTP response code is not in ``codes``. """ good = stub_pure_response("okay!", code=200) result = effect_on_response((401, ), self.invalidate_effect, good) self.assertEqual(result, good) self.assertEqual(self.invalidations, [])
def test_invalidate_unnecessary(self): """ The result is returned immediately and the provided effect is not invoked when the HTTP response code is not in ``codes``. """ good = stub_pure_response("okay!", code=200) result = effect_on_response((401,), self.invalidate_effect, good) self.assertEqual(result, good) self.assertEqual(self.invalidations, [])
def test_list_servers_details_all_gets_until_no_next_link(self): """ :func:`list_servers_details_all` follows the servers links until there are no more links, and returns a list of servers as the result. It ignores any non-next links. """ bodies = [{ 'servers': ['1', '2'], 'servers_links': [{ 'href': 'doesnt_matter_url?marker=3', 'rel': 'next' }] }, { 'servers': ['3', '4'], 'servers_links': [{ 'href': 'doesnt_matter_url?marker=5', 'rel': 'next' }, { 'href': 'doesnt_matter_url?marker=1', 'rel': 'prev' }] }, { 'servers': ['5', '6'], 'servers_links': [{ 'href': 'doesnt_matter_url?marker=3', 'rel': 'prev' }] }] resps = [json.dumps(d) for d in bodies] eff = list_servers_details_all({'marker': ['1']}) seq = [ (self._list_server_details_intent({'marker': ['1']}), service_request_eqf(stub_pure_response(resps[0], 200))), (self._list_server_details_log_intent(bodies[0]), lambda _: None), (self._list_server_details_intent({'marker': ['3']}), service_request_eqf(stub_pure_response(resps[1], 200))), (self._list_server_details_log_intent(bodies[1]), lambda _: None), (self._list_server_details_intent({'marker': ['5']}), service_request_eqf(stub_pure_response(resps[2], 200))), (self._list_server_details_log_intent(bodies[2]), lambda _: None) ] result = perform_sequence(seq, eff) self.assertEqual(result, ['1', '2', '3', '4', '5', '6'])
def test_add_event_succeeds_if_request_succeeds(self): """ Adding an event succeeds without retrying if the service request succeeds. Testing what response code causes a service request to succeeds is beyond the scope of this test. """ body = "<some xml>" resp = stub_pure_response(body, 201) response = [lambda _: (resp, body)] self.assertEqual(self._perform_add_event(response), (resp, body))
def test_api_error_default(self): """ APIError is raised when the response code isn't 200, by default. """ request_ = self._no_reauth_client() eff = request_("get", "/foo") self.assertRaises( APIError, resolve_effect, eff, stub_pure_response({"foo": "bar"}, code=404))
def test_invalidate(self): """ :func:`effect_on_response` invokes the provided effect and returns an Effect of the original response. """ badauth = stub_pure_response("badauth!", code=401) eff = effect_on_response((401, ), self.invalidate_effect, badauth) self.assertEqual(eff.intent, self.invalidate_effect.intent) self.assertEqual(resolve_stubs(eff), badauth) self.assertEqual(self.invalidations, [True])
def test_invalidate(self): """ :func:`effect_on_response` invokes the provided effect and returns an Effect of the original response. """ badauth = stub_pure_response("badauth!", code=401) eff = effect_on_response((401,), self.invalidate_effect, badauth) self.assertEqual(eff.intent, self.invalidate_effect.intent) self.assertEqual(resolve_stubs(eff), badauth) self.assertEqual(self.invalidations, [True])
def test_remove_clb_nodes_retry_on_some_invalid_nodes(self): """ When CLB returns an error indicating that some of the nodes are invalid, the request is retried without the offending nodes. """ node_ids = map(str, range(1, 5)) eff = remove_clb_nodes(self.lb_id, node_ids) response = stub_pure_response( {'validationErrors': {'messages': [ 'Node ids 1,3 are not a part of your loadbalancer']}}, 400) response2 = stub_pure_response({}, 202) seq = [ (self.expected_node_removal_req(node_ids).intent, service_request_eqf(response)), (self.expected_node_removal_req(["2", "4"]).intent, service_request_eqf(response2)) ] self.assertIs(perform_sequence(seq, eff), None)
def test_change_clb_node(self): """ Produce a request for modifying a node on a load balancer, which returns a successful result on 202. Parse the common CLB errors, and :class:`NoSuchCLBNodeError`. """ eff = change_clb_node(lb_id=self.lb_id, node_id='1234', condition="DRAINING", weight=50, _type='SECONDARY') expected = service_request( ServiceType.CLOUD_LOAD_BALANCERS, 'PUT', 'loadbalancers/{0}/nodes/1234'.format(self.lb_id), data={'node': {'condition': 'DRAINING', 'weight': 50, 'type': 'SECONDARY'}}, success_pred=has_code(202)) # success dispatcher = EQFDispatcher([( expected.intent, service_request_eqf(stub_pure_response('', 202)))]) self.assertEqual(sync_perform(dispatcher, eff), stub_pure_response(None, 202)) # NoSuchCLBNode failure msg = "Node with id #1234 not found for loadbalancer #{0}".format( self.lb_id) no_such_node = stub_pure_response( json.dumps({'message': msg, 'code': 404}), 404) dispatcher = EQFDispatcher([( expected.intent, service_request_eqf(no_such_node))]) with self.assertRaises(NoSuchCLBNodeError) as cm: sync_perform(dispatcher, eff) self.assertEqual( cm.exception, NoSuchCLBNodeError(msg, lb_id=six.text_type(self.lb_id), node_id=u'1234')) # all the common failures assert_parses_common_clb_errors(self, expected.intent, eff, "123456")
def test_remove_clb_nodes_retry_on_some_invalid_nodes(self): """ When CLB returns an error indicating that some of the nodes are invalid, the request is retried without the offending nodes. """ node_ids = map(str, range(1, 5)) eff = remove_clb_nodes(self.lb_id, node_ids) response = stub_pure_response( { 'validationErrors': { 'messages': ['Node ids 1,3 are not a part of your loadbalancer'] } }, 400) response2 = stub_pure_response({}, 202) seq = [(self.expected_node_removal_req(node_ids).intent, service_request_eqf(response)), (self.expected_node_removal_req(["2", "4"]).intent, service_request_eqf(response2))] self.assertIs(perform_sequence(seq, eff), None)
def test_change_clb_node_default_type(self): """ Produce a request for modifying a node on a load balancer with the default type, which returns a successful result on 202. """ eff = change_clb_node(lb_id=self.lb_id, node_id='1234', condition="DRAINING", weight=50) expected = service_request( ServiceType.CLOUD_LOAD_BALANCERS, 'PUT', 'loadbalancers/{0}/nodes/1234'.format(self.lb_id), data={'node': {'condition': 'DRAINING', 'weight': 50, 'type': 'PRIMARY'}}, success_pred=has_code(202)) dispatcher = EQFDispatcher([( expected.intent, service_request_eqf(stub_pure_response('', 202)))]) self.assertEqual(sync_perform(dispatcher, eff), stub_pure_response(None, 202))
def test_invalidate_on_auth_error_code(self): """ Upon authentication error, the auth cache is invalidated. """ eff = self._concrete(self.svcreq) next_eff = resolve_authenticate(eff) # When the HTTP response is an auth error, the auth cache is # invalidated, by way of the next effect: invalidate_eff = resolve_effect(next_eff, stub_pure_response("", 401)) expected_intent = InvalidateToken(self.authenticator, 1) self.assertEqual(invalidate_eff.intent, expected_intent) self.assertRaises(APIError, resolve_effect, invalidate_eff, None)
def test_check_stack_201(self): """Check stack calls Heat and returns None on a 201.""" stack_name = 'foostack' stack_id = 'foo_id' eff = check_stack(stack_name, stack_id) expected_intent = self._check_stack_intent(stack_name, stack_id) seq = [(expected_intent, service_request_eqf(stub_pure_response(b'', 201))), (log_intent('request-check-stack', b'', False), lambda _: None)] result = perform_sequence(seq, eff) self.assertEqual(result, None)
def test_no_json_response(self): """ ``json_response`` can be set to :data:`False` to get the response object and the plaintext body of the response. """ svcreq = service_request(ServiceType.CLOUD_SERVERS, "GET", "servers", json_response=False).intent eff = self._concrete(svcreq) next_eff = resolve_authenticate(eff) stub_response = stub_pure_response("foo") result = resolve_effect(next_eff, stub_response) self.assertEqual(result, stub_response)
def test_create_stack(self): """Create stack calls Heat and returns the new stack details on 201.""" data = {'data': 'data'} body = {'stack': 'the_stack_json'} eff = create_stack(data) expected_intent = self._create_stack_intent(data) seq = [(expected_intent, service_request_eqf(stub_pure_response(json.dumps(body), 201))), (log_intent('request-create-stack', body), lambda _: None)] result = perform_sequence(seq, eff) self.assertEqual(result, 'the_stack_json')
def test_performs_tenant_scope(self, deferred_lock_run): """ :func:`perform_tenant_scope` performs :obj:`TenantScope`, and uses the default throttler """ # We want to ensure # 1. the TenantScope can be performed # 2. the ServiceRequest is run within a lock, since it matches the # default throttling policy set_config_data({ "cloud_client": { "throttling": { "create_server_delay": 1, "delete_server_delay": 0.4 } } }) self.addCleanup(set_config_data, {}) clock = Clock() authenticator = object() log = object() dispatcher = get_cloud_client_dispatcher(clock, authenticator, log, make_service_configs()) svcreq = service_request(ServiceType.CLOUD_SERVERS, 'POST', 'servers') tscope = TenantScope(tenant_id='111', effect=svcreq) def run(f, *args, **kwargs): result = f(*args, **kwargs) result.addCallback(lambda x: (x[0], assoc(x[1], 'locked', True))) return result deferred_lock_run.side_effect = run response = stub_pure_response({}, 200) seq = SequenceDispatcher([ (Authenticate(authenticator=authenticator, tenant_id='111', log=log), lambda i: ('token', fake_service_catalog)), (Request(method='POST', url='http://dfw.openstack/servers', headers=headers('token'), log=log), lambda i: response), ]) disp = ComposedDispatcher([seq, dispatcher]) with seq.consume(): result = perform(disp, Effect(tscope)) self.assertNoResult(result) clock.advance(1) self.assertEqual(self.successResultOf(result), (response[0], { 'locked': True }))
def test_remove_clb_nodes_success(self): """ A DELETE request is sent, and the Effect returns None if 202 is returned. """ eff = remove_clb_nodes(self.lb_id, ["1", "2"]) seq = [ (self.expected_node_removal_req().intent, service_request_eqf(stub_pure_response({}, 202))), ] result = perform_sequence(seq, eff) self.assertIs(result, None)
def test_create_stack(self): """Create stack calls Heat and returns the new stack details on 201.""" data = {'data': 'data'} body = {'stack': 'the_stack_json'} eff = create_stack(data) expected_intent = self._create_stack_intent(data) seq = [ (expected_intent, service_request_eqf(stub_pure_response(json.dumps(body), 201))), (log_intent('request-create-stack', body), lambda _: None) ] result = perform_sequence(seq, eff) self.assertEqual(result, 'the_stack_json')
def test_list_stacks_all(self): """ :func:`list_stacks_all` returns the JSON response from listing stacks. """ body = {'stacks': ['foo', 'bar']} eff = list_stacks_all() expected_intent = self._list_stacks_all_intent(None) seq = [(expected_intent, service_request_eqf(stub_pure_response(json.dumps(body), 200))), (log_intent('request-list-stacks-all', body), lambda _: None)] stacks = perform_sequence(seq, eff) self.assertEqual(stacks, ['foo', 'bar'])
def test_list_stacks_all(self): """ :func:`list_stacks_all` returns the JSON response from listing stacks. """ body = {'stacks': ['foo', 'bar']} eff = list_stacks_all() expected_intent = self._list_stacks_all_intent(None) seq = [ (expected_intent, service_request_eqf(stub_pure_response(json.dumps(body), 200))), (log_intent('request-list-stacks-all', body), lambda _: None) ] stacks = perform_sequence(seq, eff) self.assertEqual(stacks, ['foo', 'bar'])
def test_check_stack_200(self): """Check stack calls Heat and returns None on a 200.""" stack_name = 'foostack' stack_id = 'foo_id' eff = check_stack(stack_name, stack_id) expected_intent = self._check_stack_intent(stack_name, stack_id) seq = [ (expected_intent, service_request_eqf(stub_pure_response('200!', 200))), (log_intent('request-check-stack', '200!', False), lambda _: None) ] result = perform_sequence(seq, eff) self.assertEqual(result, None)
def test_delete_stack(self): """Delete stack calls Heat and returns None on a 204.""" stack_name = 'foostack' stack_id = 'foo_id' eff = delete_stack(stack_name, stack_id) expected_intent = self._delete_stack_intent(stack_name, stack_id) seq = [ (expected_intent, service_request_eqf(stub_pure_response(b'', 204))), (log_intent('request-delete-stack', b'', False), lambda _: None) ] result = perform_sequence(seq, eff) self.assertEqual(result, None)
def _perform_one_request(intent, effect, response_code, response_body, log_intent=None): """ Perform a request effect using EQFDispatcher, providing the given body and status code. """ seq = [( intent, service_request_eqf( stub_pure_response(response_body, response_code)) )] if log_intent is not None: seq.append((log_intent, lambda _: None)) return perform_sequence(seq, effect)
def test_update_stack(self): """Update stack calls Heat and returns None on a 202.""" stack_name = 'foostack' stack_id = 'foo_id' stack_args = {'stack': 'data'} eff = update_stack(stack_name, stack_id, stack_args) expected_intent = self._update_stack_intent(stack_name, stack_id, stack_args) seq = [(expected_intent, service_request_eqf(stub_pure_response(b'', 202))), (log_intent('request-update-stack', b'', False), lambda _: None)] result = perform_sequence(seq, eff) self.assertEqual(result, None)
def test_update_stack_with_response_body(self): """Update stack succeeds even if there is content in the response.""" stack_name = 'foostack' stack_id = 'foo_id' stack_args = {'stack': 'data'} eff = update_stack(stack_name, stack_id, stack_args) expected_intent = self._update_stack_intent(stack_name, stack_id, stack_args) seq = [(expected_intent, service_request_eqf(stub_pure_response('xyz', 202))), (log_intent('request-update-stack', 'xyz', False), lambda _: None)] result = perform_sequence(seq, eff) self.assertEqual(result, None)
def test_get_server_details_success(self): """ Produce a request getting a Nova server's details, which returns a successful result on 200. """ server_id, expected, real = self._setup_for_get_server_details() body = {"so much": "data"} seq = [(expected.intent, service_request_eqf(stub_pure_response(json.dumps(body), 200))), (log_intent('request-one-server-details', body), lambda _: None)] resp, response_json = perform_sequence(seq, real) self.assertEqual(resp, StubResponse(200, {})) self.assertEqual(response_json, body)
def test_get_server_details_success(self): """ Produce a request getting a Nova server's details, which returns a successful result on 200. """ server_id, expected, real = self._setup_for_get_server_details() body = {"so much": "data"} seq = [ (expected.intent, service_request_eqf(stub_pure_response(json.dumps(body), 200))), (log_intent('request-one-server-details', body), lambda _: None) ] resp, response_json = perform_sequence(seq, real) self.assertEqual(resp, StubResponse(200, {})) self.assertEqual(response_json, body)
def test_remove_clb_nodes_random_400(self): """Random 400s that can't be parsed are bubbled up as an APIError.""" error_bodies = [ {'validationErrors': {'messages': ['bar']}}, {'messages': 'bar'}, {'validationErrors': {'messages': []}}, "random non-json" ] for body in error_bodies: eff = remove_clb_nodes(self.lb_id, ["1", "2"]) seq = [ (self.expected_node_removal_req().intent, service_request_eqf(stub_pure_response(body, 400))), ] self.assertRaises(APIError, perform_sequence, seq, eff)
def test_set_nova_metadata_item_success(self): """ Produce a request setting a metadata item on a Nova server, which returns a successful result on 200. """ server_id, expected, real = self._setup_for_set_nova_metadata_item() body = {"meta": {"k": "v"}} seq = [(expected.intent, service_request_eqf(stub_pure_response(json.dumps(body), 200))), (log_intent('request-set-metadata-item', body), lambda _: None)] resp, response_json = perform_sequence(seq, real) self.assertEqual(resp, StubResponse(200, {})) self.assertEqual(response_json, body)
def _perform_one_request(intent, effect, response_code, response_body, log_intent=None): """ Perform a request effect using EQFDispatcher, providing the given body and status code. """ seq = [ (intent, service_request_eqf(stub_pure_response(response_body, response_code))) ] if log_intent is not None: seq.append((log_intent, lambda _: None)) return perform_sequence(seq, effect)