def assert_log_send(self, logger): """ Assert that sending node state logs an appropriate action. """ transition = assertHasAction(self, logger, LOG_FSM_TRANSITION, True) send = assertHasAction(self, logger, LOG_SEND_TO_CONTROL_SERVICE, True) self.assertIn(send, transition.children)
def test_logging(self, logger): """ ``_send_state_to_connections`` logs a single LOG_SEND_CLUSTER_STATE action and a LOG_SEND_TO_AGENT action for the remote calls to each of its connections. """ control_amp_service = build_control_amp_service(self) agent = FakeAgent() client = AgentAMP(Clock(), agent) server = LoopbackAMPClient(client.locator) control_amp_service.connected(server) control_amp_service._send_state_to_connections(connections=[server]) assertHasAction(self, logger, LOG_SEND_CLUSTER_STATE, succeeded=True, endFields={ "configuration": (control_amp_service.configuration_service.get()), "state": control_amp_service.cluster_state.as_deployment() }) assertHasAction( self, logger, LOG_SEND_TO_AGENT, succeeded=True, startFields={"agent": server}, )
def test_logging(self, logger): """ ``_send_state_to_connections`` logs a single LOG_SEND_CLUSTER_STATE action and a LOG_SEND_TO_AGENT action for the remote calls to each of its connections. """ control_amp_service = build_control_amp_service(self) agent = FakeAgent() client = AgentAMP(Clock(), agent) server = LoopbackAMPClient(client.locator) control_amp_service.connected(server) control_amp_service._send_state_to_connections(connections=[server]) assertHasAction( self, logger, LOG_SEND_CLUSTER_STATE, succeeded=True, endFields={ "configuration": ( control_amp_service.configuration_service.get() ), "state": control_amp_service.cluster_state.as_deployment() } ) assertHasAction( self, logger, LOG_SEND_TO_AGENT, succeeded=True, startFields={"agent": server}, )
def test_logging(self, logger): """ ``_send_state_to_connections`` logs a single LOG_SEND_CLUSTER_STATE action and a LOG_SEND_TO_AGENT action for the remote calls to each of its connections. """ control_amp_service = build_control_amp_service(self) self.patch(control_amp_service, "logger", logger) connection_protocol = ControlAMP(Clock(), control_amp_service) # Patching is bad. # https://clusterhq.atlassian.net/browse/FLOC-1603 connection_protocol.callRemote = lambda *args, **kwargs: succeed({}) control_amp_service._send_state_to_connections(connections=[connection_protocol]) assertHasAction( self, logger, LOG_SEND_CLUSTER_STATE, succeeded=True, startFields={ "configuration": (control_amp_service.configuration_service.get()), "state": control_amp_service.cluster_state.as_deployment(), }, ) assertHasAction(self, logger, LOG_SEND_TO_AGENT, succeeded=True, startFields={"agent": connection_protocol})
def test_logging(self, logger): """ ``_send_state_to_connections`` logs a single LOG_SEND_CLUSTER_STATE action and a LOG_SEND_TO_AGENT action for the remote calls to each of its connections. """ control_amp_service = build_control_amp_service(self) self.patch(control_amp_service, 'logger', logger) connection_protocol = ControlAMP(control_amp_service) connection_protocol.makeConnection(StringTransport()) control_amp_service._send_state_to_connections( connections=[connection_protocol]) assertHasAction(self, logger, LOG_SEND_CLUSTER_STATE, succeeded=True, startFields={ "configuration": (control_amp_service.configuration_service.get()), "state": control_amp_service.cluster_state.as_deployment() }) assertHasAction(self, logger, LOG_SEND_TO_AGENT, succeeded=True, startFields={ "agent": connection_protocol, })
def validate(case, logger): assertHasAction(case, logger, parent_action_type, succeeded=True) # Remember what the docstring said? Ideally this would inspect the # children of the action returned by assertHasAction but the interfaces # don't seem to line up. iptables = LoggedAction.ofType(logger.messages, IPTABLES) case.assertNotEqual(iptables, [])
class GetExternalIPTests(TestCase): """ Tests for ``_get_external_ip``. """ def setUp(self): super(GetExternalIPTests, self).setUp() server = socket.socket() server.bind(('127.0.0.1', 0)) server.listen(5) self.destination_port = server.getsockname()[1] self.addCleanup(server.close) @capture_logging(lambda test, logger: assertHasAction( test, logger, LOG_GET_EXTERNAL_IP, True, { u"host": u"localhost", u"port": test.destination_port }, {u"local_ip": u"127.0.0.1"})) def test_successful_get_external_ip(self, logger): """ A successful external IP lookup returns the local interface's IP. """ class FakeSocket(object): def __init__(self, *args): self.addr = (b"0.0.0.0", 0) def getsockname(self): return self.addr def connect(self, addr): self.addr = (addr[0], 12345) self.patch(socket, "socket", FakeSocket()) self.assertEqual(u"127.0.0.1", _get_external_ip(u"localhost", self.destination_port)) @capture_logging(lambda test, logger: assertHasAction( test, logger, LOG_GET_EXTERNAL_IP, False, { u"host": u"localhost", u"port": test.destination_port }, {u"exception": u"exceptions.RuntimeError"})) def test_failed_get_external_ip(self, logger): """ A failed external IP lookup is retried (and the error logged). """ original_connect = socket.socket.connect def fail_once(*args, **kwargs): socket.socket.connect = original_connect raise RuntimeError() self.patch(socket.socket, "connect", fail_once) self.assertEqual(u"127.0.0.1", _get_external_ip(u"localhost", self.destination_port))
def test_logging(self, logger): """ Successful HTTP requests are logged. """ dataset_id = uuid4() d = self.client.create_dataset(primary=self.node_1.uuid, maximum_size=None, dataset_id=dataset_id) d.addCallback( lambda _: assertHasAction( self, logger, _LOG_HTTP_REQUEST, True, dict( url=b"https://127.0.0.1:{}/v1/configuration/datasets".format(self.port), method=u"POST", request_body=dict(primary=unicode(self.node_1.uuid), metadata={}, dataset_id=unicode(dataset_id)), ), dict( response_body=dict( primary=unicode(self.node_1.uuid), metadata={}, deleted=False, dataset_id=unicode(dataset_id) ) ), ) ) return d
def assert_nested_logging(self, combo, action_type, logger): """ All the underlying ``IStateChange`` will be run in Eliot context in which the sequential ``IStateChange`` is run, even if they are not run immediately. :param combo: ``sequentially`` or ``in_parallel``. :param action_type: ``eliot.ActionType`` we expect to be parent of sub-changes' log entries. :param logger: A ``MemoryLogger`` where messages go. """ actions = [ControllableAction(result=Deferred()), ControllableAction(result=succeed(None))] for action in actions: self.patch(action, "_logger", logger) run_state_change(combo(actions), None) # For sequentially this will ensure second action doesn't # automatically run in context of LOG_ACTION: actions[0].result.callback(None) parent = assertHasAction(self, logger, action_type, succeeded=True) self.assertEqual( dict(messages=parent.children, length=len(parent.children)), dict( messages=LoggedAction.ofType( logger.messages, CONTROLLABLE_ACTION_TYPE), length=2))
def assert_logged_multiple_iterations(self, logger): """ Function passed to ``retry_failure`` is run in the context of a ``LOOP_UNTIL_ACTION``. """ iterations = LoggedMessage.of_type(logger.messages, ITERATION_MESSAGE) loop = assertHasAction(self, logger, LOOP_UNTIL_ACTION, True) self.assertEqual(sorted(iterations, key=lambda m: m.message["iteration"]), loop.children)
def assert_context_preserved(self, logger): """ Logging in the method running in the thread pool is child of caller's Eliot context. """ parent = assertHasAction(self, logger, LOG_IN_CALLER, True, {}) # in-between we expect a eliot:remote_task... self.assertIn(parent.children[0].children[0], LoggedAction.of_type(logger.messages, LOG_IN_SPY))
def assert_full_logging(self, logger): """ A convergence action is logged inside the finite state maching logging. """ transition = assertHasAction(self, logger, LOG_FSM_TRANSITION, True) converge = assertHasAction( self, logger, LOG_CONVERGE, True, { u"cluster_state": self.cluster_state, u"desired_configuration": self.configuration }) self.assertIn(converge, transition.children) send = assertHasAction(self, logger, LOG_SEND_TO_CONTROL_SERVICE, True, {u"local_changes": [self.local_state]}) self.assertIn(send, converge.children) calculate = assertHasMessage(self, logger, LOG_CALCULATED_ACTIONS, {u"calculated_actions": self.action}) self.assertIn(calculate, converge.children)
def assert_full_logging(self, logger): """ A convergence action is logged inside the finite state maching logging. """ transition = assertHasAction(self, logger, LOG_FSM_TRANSITION, True) converge = assertHasAction( self, logger, LOG_CONVERGE, True, {u"cluster_state": self.cluster_state, u"desired_configuration": self.configuration}) self.assertIn(converge, transition.children) send = assertHasAction(self, logger, LOG_SEND_TO_CONTROL_SERVICE, True, {u"local_changes": [self.local_state]}) self.assertIn(send, converge.children) calculate = assertHasMessage( self, logger, LOG_CALCULATED_ACTIONS, {u"calculated_actions": self.action}) self.assertIn(calculate, converge.children)
def assert_logged_multiple_iterations(self, logger): """ Function passed to ``retry_failure`` is run in the context of a ``LOOP_UNTIL_ACTION``. """ iterations = LoggedMessage.of_type(logger.messages, ITERATION_MESSAGE) loop = assertHasAction(self, logger, LOOP_UNTIL_ACTION, True) self.assertEqual( sorted(iterations, key=lambda m: m.message["iteration"]), loop.children)
class LogCallDeferredTests(TestCase): """ Tests for ``log_call_deferred``. """ @capture_logging( lambda self, logger: assertHasAction( self, logger, u"the-action", succeeded=True), ) def test_return_value(self, logger): """ The decorated function's return value is passed through. """ result = object() @log_call_deferred(action_type=u"the-action") def f(): return result self.assertThat(f(), succeeded(Is(result))) @capture_logging( lambda self, logger: assertHasAction( self, logger, u"the-action", succeeded=False), ) def test_raise_exception(self, logger): """ An exception raised by the decorated function is passed through. """ class Result(Exception): pass @log_call_deferred(action_type=u"the-action") def f(): raise Result() self.assertThat( f(), failed(AfterPreprocessing( lambda f: f.value, IsInstance(Result), ), ), )
def test_logging(self, logger): """ ``_send_state_to_connections`` logs a single LOG_SEND_CLUSTER_STATE action and a LOG_SEND_TO_AGENT action for the remote calls to each of its connections. """ control_amp_service = build_control_amp_service(self) self.patch(control_amp_service, 'logger', logger) connection_protocol = ControlAMP(Clock(), control_amp_service) # Patching is bad. # https://clusterhq.atlassian.net/browse/FLOC-1603 connection_protocol.callRemote = lambda *args, **kwargs: succeed({}) control_amp_service._send_state_to_connections( connections=[connection_protocol]) assertHasAction( self, logger, LOG_SEND_CLUSTER_STATE, succeeded=True, startFields={ "configuration": ( control_amp_service.configuration_service.get() ), "state": control_amp_service.cluster_state.as_deployment() } ) assertHasAction( self, logger, LOG_SEND_TO_AGENT, succeeded=True, startFields={ "agent": connection_protocol, } )
def assert_logged_action_contains_messages(testcase, logger, expected_action, expected_fields): action = assertHasAction( testcase, logger, expected_action, True, ) assert_logged_messages_contain_fields( testcase, action.children, expected_fields, )
def assert_expected_action_tree(testcase, logger, expected_action_type, expected_type_tree): logged_action = assertHasAction( testcase, logger, expected_action_type, True, ) type_tree = logged_action.type_tree() testcase.assertEqual( {expected_action_type: expected_type_tree}, type_tree, "Logger had messages:\n{}".format(pformat(logger.messages, indent=4)), )
def assertJSONLogged(test, logger, method, path, request, response, code): """ Assert that the a request with given method and path logged a JSON request and JSON response. :param TestCase test: The current test. :param MemoryLogger logger: The logger being used in the test. :param method: Expected logged HTTP method. :param path: Expected logged HTTP request path. :param request: Expected logged JSON response. :param response: Expected logged JSON response. :param code: Expected HTTP response code. """ parent = _assertRequestLogged(path, method)(test, logger) child = assertHasAction(test, logger, JSON_REQUEST, True, {u"json": request}, {u"json": response, u"code": code}) test.assertIn(child, parent.children)
def assertJSONLogged(test, logger, method, path, request, response, code): """ Assert that the a request with given method and path logged a JSON request and JSON response. :param TestCase test: The current test. :param MemoryLogger logger: The logger being used in the test. :param method: Expected logged HTTP method. :param path: Expected logged HTTP request path. :param request: Expected logged JSON response. :param response: Expected logged JSON response. :param code: Expected HTTP response code. """ parent = _assertRequestLogged(path, method)(test, logger) child = assertHasAction( test, logger, JSON_REQUEST, True, {u"json": request}, {u"json": response, u"code": code}) test.assertIn(child, parent.children)
def test_logging(self, logger): """ Successful HTTP requests are logged. """ dataset_id = uuid4() d = self.client.create_dataset( primary=self.node_1, maximum_size=None, dataset_id=dataset_id) d.addCallback(lambda _: assertHasAction( self, logger, _LOG_HTTP_REQUEST, True, dict( url=b"https://127.0.0.1:{}/v1/configuration/datasets".format( self.port), method=u"POST", request_body=dict(primary=unicode(self.node_1), metadata={}, dataset_id=unicode(dataset_id))), dict(response_body=dict(primary=unicode(self.node_1), metadata={}, deleted=False, dataset_id=unicode(dataset_id))))) return d
def _logs_action_test(self, logger, arg, kwarg, result): """ Call a method ``f`` wrapped in ``log_method`` passing it ``arg`` as an argument, ``kwarg`` as a keyword argument and returning ``result`` as its result. :return: The logged action. """ class Foo(object): @log_method def f(self, foo, bar): return succeed(result) foo = Foo() d = foo.f(arg, bar=kwarg) self.assertEqual(result, self.successResultOf(d)) action_type = self.make_action_type('acceptance:f') return assertHasAction( self, logger, action_type, True, )
class FlockerClientTests(make_clientv1_tests()): """ Interface tests for ``FlockerClient``. """ def create_client(self): """ Create a new ``FlockerClient`` instance pointing at a running control service REST API. :return: ``FlockerClient`` instance. """ clock = Clock() _, self.port = find_free_port() self.persistence_service = ConfigurationPersistenceService( clock, FilePath(self.mktemp())) self.persistence_service.startService() self.cluster_state_service = ClusterStateService(reactor) self.cluster_state_service.startService() self.addCleanup(self.cluster_state_service.stopService) self.addCleanup(self.persistence_service.stopService) credential_set, _ = get_credential_sets() credentials_path = FilePath(self.mktemp()) credentials_path.makedirs() api_service = create_api_service( self.persistence_service, self.cluster_state_service, TCP4ServerEndpoint(reactor, self.port, interface=b"127.0.0.1"), rest_api_context_factory( credential_set.root.credential.certificate, credential_set.control), # Use consistent fake time for API results: clock) api_service.startService() self.addCleanup(api_service.stopService) credential_set.copy_to(credentials_path, user=True) return FlockerClient(reactor, b"127.0.0.1", self.port, credentials_path.child(b"cluster.crt"), credentials_path.child(b"user.crt"), credentials_path.child(b"user.key")) def synchronize_state(self): deployment = self.persistence_service.get() node_states = [NodeState(uuid=node.uuid, hostname=unicode(node.uuid), manifestations=node.manifestations, paths={manifestation.dataset_id: FilePath(b"/flocker").child(bytes( manifestation.dataset_id)) for manifestation in node.manifestations.values()}, devices={}) for node in deployment.nodes] self.cluster_state_service.apply_changes(node_states) @capture_logging(None) def test_logging(self, logger): """ Successful HTTP requests are logged. """ dataset_id = uuid4() d = self.client.create_dataset( primary=self.node_1, maximum_size=None, dataset_id=dataset_id) d.addCallback(lambda _: assertHasAction( self, logger, _LOG_HTTP_REQUEST, True, dict( url=b"https://127.0.0.1:{}/v1/configuration/datasets".format( self.port), method=u"POST", request_body=dict(primary=unicode(self.node_1), metadata={}, dataset_id=unicode(dataset_id))), dict(response_body=dict(primary=unicode(self.node_1), metadata={}, deleted=False, dataset_id=unicode(dataset_id))))) return d @capture_logging(None) def test_cross_process_logging(self, logger): """ Eliot tasks can be traced from the HTTP client to the API server. """ self.patch(rest_api, "_logger", logger) my_action = ActionType("my_action", [], []) with my_action(): d = self.client.create_dataset(primary=self.node_1) def got_response(_): parent = LoggedAction.ofType(logger.messages, my_action)[0] child = LoggedAction.ofType(logger.messages, JSON_REQUEST)[0] self.assertIn(child, list(parent.descendants())) d.addCallback(got_response) return d @capture_logging(lambda self, logger: assertHasAction( self, logger, _LOG_HTTP_REQUEST, False, dict( url=b"https://127.0.0.1:{}/v1/configuration/datasets".format( self.port), method=u"POST", request_body=dict( primary=unicode(self.node_1), maximum_size=u"notint", metadata={})), {u'exception': u'flocker.apiclient._client.ResponseError'})) def test_unexpected_error(self, logger): """ If the ``FlockerClient`` receives an unexpected HTTP response code it returns a ``ResponseError`` failure. """ d = self.client.create_dataset( primary=self.node_1, maximum_size=u"notint") self.assertFailure(d, ResponseError) d.addCallback(lambda exc: self.assertEqual(exc.code, BAD_REQUEST)) return d def test_unset_primary(self): """ If the ``FlockerClient`` receives a dataset state where primary is ``None`` it parses it correctly. """ dataset_id = uuid4() self.cluster_state_service.apply_changes([ NonManifestDatasets(datasets={ unicode(dataset_id): ModelDataset( dataset_id=unicode(dataset_id)), })]) d = self.client.list_datasets_state() d.addCallback(lambda states: self.assertEqual( [DatasetState(dataset_id=dataset_id, primary=None, maximum_size=None, path=None)], states)) return d
class LogCallDeferredTests(TestCase): """ Tests for ``log_call_deferred``. """ @capture_logging( lambda self, logger: assertHasAction( self, logger, u"the-action", succeeded=True), ) def test_return_value(self, logger): """ The decorated function's return value is passed through. """ result = object() @log_call_deferred(action_type=u"the-action") def f(): return result self.assertThat(f(), succeeded(Is(result))) @capture_logging( lambda self, logger: assertHasAction(self, logger, "the-action", succeeded=True, startFields={"thing": "value"}), ) def test_args_logged(self, logger): """ The decorated function's arguments are logged. """ @log_call_deferred(action_type="the-action", include_args=True) def f(self, reactor, thing): pass f(object(), object(), "value") @capture_logging( lambda self, logger: assertHasAction(self, logger, "the-action", succeeded=True, startFields={"thing": "value"}), ) def test_args_logged_explicit(self, logger): """ The decorated function's arguments are logged. """ @log_call_deferred(action_type="the-action", include_args=["thing"]) def f(thing, other): pass f("value", object()) @capture_logging( lambda self, logger: assertHasAction(self, logger, "the-action", succeeded=True, startFields={"thing": "value"}), ) def test_args_logged_inline_callbacks(self, logger): """ The decorated function's arguments are logged. """ @log_inline_callbacks(action_type="the-action", include_args=["thing"]) def f(thing, other): yield f("value", object()) @capture_logging( lambda self, logger: assertHasAction( self, logger, u"the-action", succeeded=False), ) def test_raise_exception(self, logger): """ An exception raised by the decorated function is passed through. """ class Result(Exception): pass @log_call_deferred(action_type=u"the-action") def f(): raise Result() self.assertThat( f(), failed(AfterPreprocessing( lambda f: f.value, IsInstance(Result), ), ), )
class FlockerClientTests(make_clientv1_tests()): """ Interface tests for ``FlockerClient``. """ @skipUnless(platform.isLinux(), "flocker-node-era currently requires Linux.") @skipUnless(which("flocker-node-era"), "flocker-node-era needs to be in $PATH.") def create_client(self): """ Create a new ``FlockerClient`` instance pointing at a running control service REST API. :return: ``FlockerClient`` instance. """ clock = Clock() _, self.port = find_free_port() self.persistence_service = ConfigurationPersistenceService( clock, FilePath(self.mktemp())) self.persistence_service.startService() self.cluster_state_service = ClusterStateService(reactor) self.cluster_state_service.startService() source = ChangeSource() # Prevent nodes being deleted by the state wiper. source.set_last_activity(reactor.seconds()) self.era = UUID(check_output(["flocker-node-era"])) self.cluster_state_service.apply_changes_from_source( source=source, changes=[UpdateNodeStateEra(era=self.era, uuid=self.node_1.uuid)] + [ NodeState(uuid=node.uuid, hostname=node.public_address) for node in [self.node_1, self.node_2] ], ) self.addCleanup(self.cluster_state_service.stopService) self.addCleanup(self.persistence_service.stopService) credential_set, _ = get_credential_sets() credentials_path = FilePath(self.mktemp()) credentials_path.makedirs() api_service = create_api_service( self.persistence_service, self.cluster_state_service, TCP4ServerEndpoint(reactor, self.port, interface=b"127.0.0.1"), rest_api_context_factory( credential_set.root.credential.certificate, credential_set.control), # Use consistent fake time for API results: clock) api_service.startService() self.addCleanup(api_service.stopService) credential_set.copy_to(credentials_path, user=True) return FlockerClient(reactor, b"127.0.0.1", self.port, credentials_path.child(b"cluster.crt"), credentials_path.child(b"user.crt"), credentials_path.child(b"user.key")) def synchronize_state(self): deployment = self.persistence_service.get() # No IP address known, so use UUID for hostname node_states = [ NodeState(uuid=node.uuid, hostname=unicode(node.uuid), applications=node.applications, manifestations=node.manifestations, paths={ manifestation.dataset_id: FilePath(b"/flocker").child( bytes(manifestation.dataset_id)) for manifestation in node.manifestations.values() }, devices={}) for node in deployment.nodes ] self.cluster_state_service.apply_changes(node_states) def get_configuration_tag(self): return self.persistence_service.configuration_hash() @capture_logging(None) def test_logging(self, logger): """ Successful HTTP requests are logged. """ dataset_id = uuid4() d = self.client.create_dataset(primary=self.node_1.uuid, maximum_size=None, dataset_id=dataset_id) d.addCallback(lambda _: assertHasAction( self, logger, _LOG_HTTP_REQUEST, True, dict(url=b"https://127.0.0.1:{}/v1/configuration/datasets".format( self.port), method=u"POST", request_body=dict(primary=unicode(self.node_1.uuid), metadata={}, dataset_id=unicode(dataset_id))), dict(response_body=dict(primary=unicode(self.node_1.uuid), metadata={}, deleted=False, dataset_id=unicode(dataset_id))))) return d @capture_logging(None) def test_cross_process_logging(self, logger): """ Eliot tasks can be traced from the HTTP client to the API server. """ self.patch(rest_api, "_logger", logger) my_action = ActionType("my_action", [], []) with my_action(): d = self.client.create_dataset(primary=self.node_1.uuid) def got_response(_): parent = LoggedAction.ofType(logger.messages, my_action)[0] child = LoggedAction.ofType(logger.messages, REQUEST)[0] self.assertIn(child, list(parent.descendants())) d.addCallback(got_response) return d @capture_logging(lambda self, logger: assertHasAction( self, logger, _LOG_HTTP_REQUEST, False, dict(url=b"https://127.0.0.1:{}/v1/configuration/datasets".format( self.port), method=u"POST", request_body=dict(primary=unicode(self.node_1.uuid), maximum_size=u"notint", metadata={})), {u'exception': u'flocker.apiclient._client.ResponseError'})) def test_unexpected_error(self, logger): """ If the ``FlockerClient`` receives an unexpected HTTP response code it returns a ``ResponseError`` failure. """ d = self.client.create_dataset(primary=self.node_1.uuid, maximum_size=u"notint") self.assertFailure(d, ResponseError) d.addCallback(lambda exc: self.assertEqual(exc.code, BAD_REQUEST)) return d def test_unset_primary(self): """ If the ``FlockerClient`` receives a dataset state where primary is ``None`` it parses it correctly. """ dataset_id = uuid4() self.cluster_state_service.apply_changes([ NonManifestDatasets( datasets={ unicode(dataset_id): ModelDataset(dataset_id=unicode(dataset_id)), }) ]) d = self.client.list_datasets_state() d.addCallback(lambda states: self.assertEqual([ DatasetState(dataset_id=dataset_id, primary=None, maximum_size=None, path=None) ], states)) return d def test_this_node_uuid_retry(self): """ ``this_node_uuid`` retries if the node UUID is unknown. """ # Pretend that the era for node 1 is something else; first try at # getting node UUID for real era will therefore fail: self.cluster_state_service.apply_changes( [UpdateNodeStateEra(era=uuid4(), uuid=self.node_1.uuid)]) # When we lookup the DeploymentState the first time we'll set the # value to the correct one, so second try should succeed: def as_deployment(original=self.cluster_state_service.as_deployment): result = original() self.cluster_state_service.apply_changes(changes=[ UpdateNodeStateEra(era=self.era, uuid=self.node_1.uuid) ]) return result self.patch(self.cluster_state_service, "as_deployment", as_deployment) d = self.client.this_node_uuid() d.addCallback(self.assertEqual, self.node_1.uuid) return d def test_this_node_uuid_no_retry_on_other_responses(self): """ ``this_node_uuid`` doesn't retry on unexpected responses. """ # Cause 500 errors to be raised by the API endpoint: self.patch(self.cluster_state_service, "as_deployment", lambda: 1 / 0) return self.assertFailure(self.client.this_node_uuid(), ResponseError)