Example #1
0
 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)
Example #2
0
 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)
Example #3
0
    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},
        )
Example #4
0
    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},
        )
Example #5
0
    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})
Example #6
0
    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, [])
Example #8
0
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))
Example #9
0
 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
Example #10
0
    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))
Example #11
0
    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))
Example #12
0
 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)
Example #13
0
 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))
Example #14
0
 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))
Example #15
0
 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)
Example #16
0
 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)
Example #17
0
 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)
Example #18
0
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),
            ), ),
        )
Example #19
0
    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,
            }
        )
Example #20
0
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,
    )
Example #21
0
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)
Example #23
0
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)
Example #24
0
 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
Example #25
0
    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,
        )
Example #26
0
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),
            ), ),
        )
Example #28
0
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)