示例#1
0
class RLockTests(LockTests):

    def setUp(self):
        self.lock_class = glocks.RLock
        self.resource = 'rlocktests:resource'
        self.ttl = 2000.
        self.host_info = {
            'host': 'localhost',
            'port': 6379,
            'db': 1
        }
        self.clock = Clock()
        self.lock = self.setup_lock(reactor=self.clock)
        self.is_reentrant = True

    def test_keep_alive_task(self):
        self.lock.acquire()
        self.assertIsInstance(
            self.lock.keep_alive_task,
            glocks.locks.LoopingCall
        )

        self.assertTrue(self.lock.keep_alive_task.running)

        # now test that the progress of time executes the delayed calls
        # that the keep_alive_task generates
        self.assertEqual(
            len(self.clock.getDelayedCalls()),
            1
        )
        delayed_call = self.clock.getDelayedCalls()[0]

        # test that the thing to be called is actually the looping call itself
        self.assertEqual(
            delayed_call.func,
            self.lock.keep_alive_task
        )

        self.assertFalse(delayed_call.called)

        self.clock.advance(1)
        self.assertTrue(delayed_call.called)

        self.assertEqual(
            len(self.clock.getDelayedCalls()),
            1
        )
        new_delayed_call = self.clock.getDelayedCalls()[0]
        self.assertNotEqual(delayed_call, new_delayed_call)
        self.assertFalse(new_delayed_call.called)

    def test_keep_alive_interval(self):
        self.lock.trigger_keep_alive()
        self.assertTrue(
            self.lock.keep_alive_task.interval < (
                float(self.ttl) / float(self.lock.KEEP_ALIVE_FREQUENCY)
            )
        )
        self.assertTrue(self.lock.keep_alive_task.now)
示例#2
0
        def test_inactivity(self):
            clock = Clock()

            self.inactivityReached = False

            def becameInactive():
                self.inactivityReached = True

            id = InactivityDetector(clock, 5, becameInactive)

            # After 3 seconds, not inactive
            clock.advance(3)
            self.assertFalse(self.inactivityReached)

            # Activity happens, pushing out the inactivity threshold
            id.activity()
            clock.advance(3)
            self.assertFalse(self.inactivityReached)

            # Time passes without activity
            clock.advance(3)
            self.assertTrue(self.inactivityReached)

            id.stop()

            # Verify a timeout of 0 does not ever fire
            id = InactivityDetector(clock, 0, becameInactive)
            self.assertEquals(clock.getDelayedCalls(), [])
示例#3
0
    def test_initial_interval(self, random, mean):
        """
        When constructed without a value for ``last_run``,
        ``lease_maintenance_service`` schedules its first run to take place
        after an interval that falls uniformly in range centered on ``mean``
        with a size of ``range``.
        """
        clock = Clock()
        # Construct a range that fits in with the mean
        range_ = timedelta(
            seconds=random.uniform(0, mean.total_seconds()),
        )

        service = lease_maintenance_service(
            dummy_maintain_leases,
            clock,
            FilePath(self.useFixture(TempDir()).join(u"last-run")),
            random,
            mean,
            range_,
        )
        service.startService()
        [maintenance_call] = clock.getDelayedCalls()

        datetime_now = datetime.utcfromtimestamp(clock.seconds())
        low = datetime_now + mean - (range_ / 2)
        high = datetime_now + mean + (range_ / 2)
        self.assertThat(
            datetime.utcfromtimestamp(maintenance_call.getTime()),
            between(low, high),
        )
        def test_inactivity(self):
            clock = Clock()

            self.inactivityReached = False
            def becameInactive():
                self.inactivityReached = True

            id = InactivityDetector(clock, 5, becameInactive)

            # After 3 seconds, not inactive
            clock.advance(3)
            self.assertFalse(self.inactivityReached)

            # Activity happens, pushing out the inactivity threshold
            id.activity()
            clock.advance(3)
            self.assertFalse(self.inactivityReached)

            # Time passes without activity
            clock.advance(3)
            self.assertTrue(self.inactivityReached)

            id.stop()

            # Verify a timeout of 0 does not ever fire
            id = InactivityDetector(clock, 0, becameInactive)
            self.assertEquals(clock.getDelayedCalls(), [])
 def test_clientConnected(self):
     """
     When a client connects, the service keeps a reference to the new
     protocol and resets the delay.
     """
     clock = Clock()
     cq, service = self.makeReconnector(clock=clock)
     awaitingProtocol = service.whenConnected()
     self.assertEqual(clock.getDelayedCalls(), [])
     self.assertIdentical(self.successResultOf(awaitingProtocol),
                          cq.applicationProtocols[0])
示例#6
0
 def test_clientConnected(self):
     """
     When a client connects, the service keeps a reference to the new
     protocol and resets the delay.
     """
     clock = Clock()
     cq, service = self.makeReconnector(clock=clock)
     awaitingProtocol = service.whenConnected()
     self.assertEqual(clock.getDelayedCalls(), [])
     self.assertIdentical(self.successResultOf(awaitingProtocol),
                          cq.applicationProtocols[0])
示例#7
0
class TestPersistenceFileGlue(unittest.TestCase):
    def setUp(self):
        self.__clock = Clock()
        self.__temp_dir = tempfile.mkdtemp(
            prefix='shinysdr_test_persistence_tmp')
        self.__state_name = os.path.join(self.__temp_dir, 'state')
        self.__reset()

    def tearDown(self):
        self.assertFalse(self.__clock.getDelayedCalls())
        shutil.rmtree(self.__temp_dir)

    def __reset(self):
        """Recreate the object for write-then-read tests."""
        self.__root = ValueAndBlockSpecimen()

    def __start(self, **kwargs):
        return PersistenceFileGlue(reactor=self.__clock,
                                   root_object=self.__root,
                                   filename=self.__state_name,
                                   **kwargs)

    def test_no_defaults(self):
        self.__start()
        # It would be surprising if this assertion failed; this test is mainly just to test the initialization succeeds
        self.assertEqual(self.__root.get_value(), 0)

    def test_defaults(self):
        self.__start(get_defaults=lambda _: {u'value': 1})
        self.assertEqual(self.__root.get_value(), 1)

    def test_persistence(self):
        """Test that state persists."""
        pfg = self.__start()
        self.assertEqual(self.__root.get_value(),
                         0)  # check initial assumption
        self.__root.set_value(1)
        advance_until(self.__clock, pfg.sync(), limit=2)
        self.__reset()
        self.__start()
        self.assertEqual(self.__root.get_value(), 1)  # check persistence

    def test_delay_is_present(self):
        """Test that persistence isn't immediate."""
        pfg = self.__start()
        self.assertEqual(self.__root.get_value(),
                         0)  # check initial assumption
        self.__root.set_value(1)
        self.__reset()
        self.__start()
        self.assertEqual(self.__root.get_value(), 0)  # change not persisted
        advance_until(self.__clock, pfg.sync(),
                      limit=2)  # clean up clock for tearDown check
 def test_retryCancelled(self):
     """
     When L{ClientService.stopService} is called while waiting between
     connection attempts, the pending reconnection attempt is cancelled and
     the service is stopped immediately.
     """
     clock = Clock()
     cq, service = self.makeReconnector(fireImmediately=False, clock=clock)
     cq.connectQueue[0].errback(Exception("no connection"))
     d = service.stopService()
     self.assertEqual(clock.getDelayedCalls(), [])
     self.successResultOf(d)
class TestTelemetryStore(unittest.TestCase):
    def setUp(self):
        self.clock = Clock()
        self.clock.advance(1000)
        self.store = TelemetryStore(time_source=self.clock)

    def test_new_object(self):
        self.assertEqual([], self.store.state().keys())
        self.store.receive(Msg('foo', 1000))
        self.assertEqual(['foo'], self.store.state().keys())
        obj = self.store.state()['foo'].get()
        self.assertIsInstance(obj, Obj)

    def test_receive_called(self):
        self.store.receive(Msg('foo', 1000, 1))
        obj = self.store.state()['foo'].get()
        self.assertEquals(obj.last_msg, 1)
        self.store.receive(Msg('foo', 1000, 2))
        self.assertEquals(obj.last_msg, 2)

    def test_drop_old(self):
        self.store.receive(Msg('foo', 1000))
        self.assertEqual(['foo'], self.store.state().keys())

        self.clock.advance(1799.5)
        self.store.receive(Msg('bar', 2799.5))
        self.assertEqual({'bar', 'foo'}, set(self.store.state().keys()))

        self.clock.advance(0.5)
        self.assertEqual({'bar'}, set(self.store.state().keys()))

        self.clock.advance(10000)
        self.assertEqual([], self.store.state().keys())

        # Expect complete cleanup -- that is, even if a TelemetryStore is created, filled, and thrown away, it will eventually be garbage collected when the objects expire.
        self.assertEqual([], self.clock.getDelayedCalls())

    def test_become_interesting(self):
        self.store.receive(Msg('foo', 1000, 'boring'))
        self.assertEqual([], self.store.state().keys())
        self.store.receive(Msg('foo', 1001, 'interesting'))
        self.assertEqual(['foo'], self.store.state().keys())
        # 'become boring' is not implemented, so also not tested yet

    def test_drop_old_boring(self):
        """
        Make sure that dropping a boring object doesn't fail.
        """
        self.store.receive(Msg('foo', 1000, 'boring'))
        self.assertEqual([], self.store.state().keys())
        self.clock.advance(1800)
        self.store.receive(Msg('bar', 2800, 'boring'))
        self.assertEqual([], self.store.state().keys())
示例#10
0
 def test_retryCancelled(self):
     """
     When L{ClientService.stopService} is called while waiting between
     connection attempts, the pending reconnection attempt is cancelled and
     the service is stopped immediately.
     """
     clock = Clock()
     cq, service = self.makeReconnector(fireImmediately=False, clock=clock)
     cq.connectQueue[0].errback(Exception("no connection"))
     d = service.stopService()
     self.assertEqual(clock.getDelayedCalls(), [])
     self.successResultOf(d)
示例#11
0
 def test_build_protocol_custom_parameters(self):
     """Test building AMQClient instances with custom parameters."""
     address = IPv4Address("TCP", "127.0.0.1", 5672)
     spec = "../specs/rabbitmq/amqp0-8.stripped.rabbitmq.xml"
     clock = Clock()
     factory = AMQFactory(spec=spec, clock=clock)
     factory.set_vhost("foo")
     factory.set_heartbeat(1)
     client = factory.buildProtocol(address)
     self.assertEqual("foo", client.vhost)
     self.assertEqual(spec, client.spec.file)
     self.assertEqual(1, client.heartbeatInterval)
     self.assertEqual(1, len(clock.getDelayedCalls()))
示例#12
0
    def test_convergence_stop(self):
        """
        A FSM doing convergence that receives a stop input stops when the
        convergence iteration finishes.
        """
        local_state = NodeState(hostname=u'192.0.2.123')
        configuration = Deployment(nodes=frozenset([to_node(local_state)]))
        state = DeploymentState(nodes=[local_state])

        # Until this Deferred fires the first iteration won't finish:
        action = ControllableAction(result=Deferred())
        # Only one discovery result is configured, so a second attempt at
        # discovery would fail:
        deployer = ControllableDeployer(
            local_state.hostname, [succeed(local_state)],
            [action]
        )
        client = self.make_amp_client([local_state])
        reactor = Clock()
        loop = build_convergence_loop_fsm(reactor, deployer)
        loop.receive(_ClientStatusUpdate(
            client=client, configuration=configuration, state=state))

        # Calculating actions happened, action is run, but waits for
        # Deferred to be fired... Meanwhile a stop input is received!
        loop.receive(ConvergenceLoopInputs.STOP)
        # Action finally finishes:
        action.result.callback(None)
        reactor.advance(1.0)

        # work is scheduled:
        expected = (
            # The actions are calculated
            [(local_state, configuration, state)],
            # And the result is run
            [(NodeStateCommand, dict(state_changes=(local_state,)))],
            # The state machine gets to the desired state.
            ConvergenceLoopStates.STOPPED,
            # And no subsequent work is scheduled to be run.
            [],
        )
        actual = (
            deployer.calculate_inputs,
            client.calls,
            loop.state,
            reactor.getDelayedCalls(),
        )
        self.assertTupleEqual(expected, actual)
示例#13
0
    def test_convergence_stop(self):
        """
        A FSM doing convergence that receives a stop input stops when the
        convergence iteration finishes.
        """
        local_state = NodeState(hostname=u'192.0.2.123')
        configuration = Deployment(nodes=frozenset([to_node(local_state)]))
        state = DeploymentState(nodes=[local_state])

        # Until this Deferred fires the first iteration won't finish:
        action = ControllableAction(result=Deferred())
        # Only one discovery result is configured, so a second attempt at
        # discovery would fail:
        deployer = ControllableDeployer(local_state.hostname,
                                        [succeed(local_state)], [action])
        client = self.make_amp_client([local_state])
        reactor = Clock()
        loop = build_convergence_loop_fsm(reactor, deployer)
        loop.receive(
            _ClientStatusUpdate(client=client,
                                configuration=configuration,
                                state=state))

        # Calculating actions happened, action is run, but waits for
        # Deferred to be fired... Meanwhile a stop input is received!
        loop.receive(ConvergenceLoopInputs.STOP)
        # Action finally finishes:
        action.result.callback(None)
        reactor.advance(1.0)

        # work is scheduled:
        expected = (
            # The actions are calculated
            [(local_state, configuration, state)],
            # And the result is run
            [(NodeStateCommand, dict(state_changes=(local_state, )))],
            # The state machine gets to the desired state.
            ConvergenceLoopStates.STOPPED,
            # And no subsequent work is scheduled to be run.
            [],
        )
        actual = (
            deployer.calculate_inputs,
            client.calls,
            loop.state,
            reactor.getDelayedCalls(),
        )
        self.assertTupleEqual(expected, actual)
示例#14
0
    def ret():
        error = [None]

        clock = Clock()

        d = f(clock)

        @d.addErrback
        def on_error(f):
            error[0] = f

        while True:
            time_to_wait = max([0] + [call.getTime() - clock.seconds() for call in clock.getDelayedCalls()])
            if time_to_wait == 0:
                break
            else:
                clock.advance(time_to_wait)

        if error[0]:
            error[0].raiseException()
示例#15
0
class SelfHealTests(SynchronousTestCase):
    """
    Tests for :obj:`SelfHeal`
    """

    def setUp(self):
        self.clock = Clock()
        self.log = mock_log()
        self.patch(sh, "get_groups_to_converge", intent_func("ggtc"))
        self.patch(sh, "check_and_trigger", lambda t, g: t + g)
        self.s = sh.SelfHeal(self.clock, base_dispatcher, "cf", 300.0,
                             self.log)
        self.groups = [
            {"tenantId": "t{}".format(i), "groupId": "g{}".format(i)}
            for i in range(5)]

    def test_setup(self):
        """
        ``self.s.setup()`` will setup convergences to be triggered over
        specified time range
        """
        self.s.dispatcher = SequenceDispatcher(
            [(("ggtc", "cf"), const(self.groups))])
        d = self.s.setup()
        self.successResultOf(d)
        calls = self.clock.getDelayedCalls()
        self.assertEqual(self.s._calls, calls)
        for i, c in enumerate(calls):
            self.assertEqual(c.getTime(), i * 60)
            self.assertEqual(c.func, sh.perform)
            self.assertEqual(c.args,
                             (self.s.dispatcher, "t{}g{}".format(i, i)))

    def test_setup_err(self):
        """
        ``self.s.setup()`` will log any error and return success
        """
        self.s.dispatcher = SequenceDispatcher(
            [(("ggtc", "cf"), conste(ValueError("h")))])
        d = self.s.setup()
        self.successResultOf(d)
        self.log.err.assert_called_once_with(
            CheckFailure(ValueError), "selfheal-setup-err",
            otter_service="selfheal")

    def test_setup_no_groups(self):
        """
        ``self.s.setup()`` gets groups and does nothing if there are no groups
        """
        self.s.dispatcher = SequenceDispatcher([(("ggtc", "cf"), const([]))])
        d = self.s.setup()
        self.successResultOf(d)
        self.assertEqual(self.s._calls, [])
        self.assertEqual(self.clock.getDelayedCalls(), [])

    def test_setup_still_active(self):
        """
        If there are scheduled calls when perform is called, they are
        cancelled and err is logged. Future calls are scheduled as usual
        """
        self.clock.advance(-0.6)
        call1 = self.clock.callLater(1, noop, None)
        call2 = self.clock.callLater(0, noop, None)
        call3 = self.clock.callLater(2, noop, None)
        self.clock.advance(0.6)
        self.s._calls = [call1, call2, call3]
        self.s.dispatcher = SequenceDispatcher(
            [(("ggtc", "cf"), const(self.groups))])
        d = self.s.setup()
        self.successResultOf(d)
        self.log.err.assert_called_once_with(
            matches(IsInstance(RuntimeError)), "selfheal-calls-err", active=2,
            otter_service="selfheal")
        self.assertFalse(call1.active())
        self.assertFalse(call2.active())
示例#16
0
class APITestsMixin(APIAssertionsMixin):
    """
    Helpers for writing tests for the Docker Volume Plugin API.
    """
    NODE_A = uuid4()
    NODE_B = uuid4()

    def initialize(self):
        """
        Create initial objects for the ``VolumePlugin``.
        """
        self.volume_plugin_reactor = Clock()
        self.flocker_client = SimpleCountingProxy(FakeFlockerClient())

    def test_pluginactivate(self):
        """
        ``/Plugins.Activate`` indicates the plugin is a volume driver.
        """
        # Docker 1.8, at least, sends "null" as the body. Our test
        # infrastructure has the opposite bug so just going to send some
        # other garbage as the body (12345) to demonstrate that it's
        # ignored as per the spec which declares no body.
        return self.assertResult(b"POST", b"/Plugin.Activate", 12345, OK,
                                 {u"Implements": [u"VolumeDriver"]})

    def test_remove(self):
        """
        ``/VolumeDriver.Remove`` returns a successful result.
        """
        return self.assertResult(b"POST", b"/VolumeDriver.Remove",
                                 {u"Name": u"vol"}, OK, {u"Err": None})

    def test_unmount(self):
        """
        ``/VolumeDriver.Unmount`` returns a successful result.
        """
        return self.assertResult(b"POST", b"/VolumeDriver.Unmount",
                                 {u"Name": u"vol"}, OK, {u"Err": None})

    def test_create_with_opts(self):
        """
        Calling the ``/VolumerDriver.Create`` API with an ``Opts`` value
        in the request body JSON ignores this parameter and creates
        a volume with the given name.
        """
        name = u"testvolume"
        d = self.assertResult(b"POST", b"/VolumeDriver.Create",
                              {u"Name": name, 'Opts': {'ignored': 'ignored'}},
                              OK, {u"Err": None})
        d.addCallback(
            lambda _: self.flocker_client.list_datasets_configuration())
        d.addCallback(self.assertItemsEqual, [
            Dataset(dataset_id=UUID(dataset_id_from_name(name)),
                    primary=self.NODE_A,
                    maximum_size=DEFAULT_SIZE,
                    metadata={u"name": name})])
        return d

    def create(self, name):
        """
        Call the ``/VolumeDriver.Create`` API to create a volume with the
        given name.

        :param unicode name: The name of the volume to create.

        :return: ``Deferred`` that fires when the volume that was created.
        """
        return self.assertResult(b"POST", b"/VolumeDriver.Create",
                                 {u"Name": name}, OK, {u"Err": None})

    def test_create_creates(self):
        """
        ``/VolumeDriver.Create`` creates a new dataset in the configuration.
        """
        name = u"myvol"
        d = self.create(name)
        d.addCallback(
            lambda _: self.flocker_client.list_datasets_configuration())
        d.addCallback(self.assertItemsEqual, [
            Dataset(dataset_id=UUID(dataset_id_from_name(name)),
                    primary=self.NODE_A,
                    maximum_size=DEFAULT_SIZE,
                    metadata={u"name": name})])
        return d

    def test_create_duplicate_name(self):
        """
        If a dataset with the given name already exists,
        ``/VolumeDriver.Create`` succeeds without create a new volume.
        """
        name = u"thename"
        # Create a dataset out-of-band with matching name but non-matching
        # dataset ID:
        d = self.flocker_client.create_dataset(
            self.NODE_A, DEFAULT_SIZE, metadata={u"name": name})
        d.addCallback(lambda _: self.create(name))
        d.addCallback(
            lambda _: self.flocker_client.list_datasets_configuration())
        d.addCallback(lambda results: self.assertEqual(len(results), 1))
        return d

    def test_create_duplicate_name_race_condition(self):
        """
        If a dataset with the given name is created while the
        ``/VolumeDriver.Create`` call is in flight, the call does not
        result in an error.
        """
        name = u"thename"

        # Create a dataset out-of-band with matching dataset ID and name
        # which the docker plugin won't be able to see.
        def create_after_list():
            # Clean up the patched version:
            del self.flocker_client.list_datasets_configuration
            # But first time we're called, we create dataset and lie about
            # its existence:
            d = self.flocker_client.create_dataset(
                self.NODE_A, DEFAULT_SIZE,
                metadata={u"name": name},
                dataset_id=UUID(dataset_id_from_name(name)))
            d.addCallback(lambda _: [])
            return d
        self.flocker_client.list_datasets_configuration = create_after_list

        return self.create(name)

    def _flush_volume_plugin_reactor_on_endpoint_render(self):
        """
        This method patches ``self.app`` so that after any endpoint is
        rendered, the reactor used by the volume plugin is advanced repeatedly
        until there are no more ``delayedCalls`` pending on the reactor.
        """
        real_execute_endpoint = self.app.execute_endpoint

        def patched_execute_endpoint(*args, **kwargs):
            val = real_execute_endpoint(*args, **kwargs)
            while self.volume_plugin_reactor.getDelayedCalls():
                pending_calls = self.volume_plugin_reactor.getDelayedCalls()
                next_expiration = min(t.getTime() for t in pending_calls)
                now = self.volume_plugin_reactor.seconds()
                self.volume_plugin_reactor.advance(
                    max(0.0, next_expiration - now))
            return val
        self.patch(self.app, 'execute_endpoint', patched_execute_endpoint)

    def test_mount(self):
        """
        ``/VolumeDriver.Mount`` sets the primary of the dataset with matching
        name to the current node and then waits for the dataset to
        actually arrive.
        """
        name = u"myvol"
        dataset_id = UUID(dataset_id_from_name(name))
        # Create dataset on a different node:
        d = self.flocker_client.create_dataset(
            self.NODE_B, DEFAULT_SIZE, metadata={u"name": name},
            dataset_id=dataset_id)

        self._flush_volume_plugin_reactor_on_endpoint_render()

        # Pretend that it takes 5 seconds for the dataset to get established on
        # Node A.
        self.volume_plugin_reactor.callLater(
            5.0, self.flocker_client.synchronize_state)

        d.addCallback(lambda _:
                      self.assertResult(
                          b"POST", b"/VolumeDriver.Mount",
                          {u"Name": name}, OK,
                          {u"Err": None,
                           u"Mountpoint": u"/flocker/{}".format(dataset_id)}))
        d.addCallback(lambda _: self.flocker_client.list_datasets_state())

        def final_assertions(datasets):
            self.assertEqual([self.NODE_A],
                             [d.primary for d in datasets
                              if d.dataset_id == dataset_id])
            # There should be less than 20 calls to list_datasets_state over
            # the course of 5 seconds.
            self.assertLess(
                self.flocker_client.num_calls('list_datasets_state'), 20)
        d.addCallback(final_assertions)

        return d

    def test_mount_timeout(self):
        """
        ``/VolumeDriver.Mount`` sets the primary of the dataset with matching
        name to the current node and then waits for the dataset to
        actually arrive. If it does not arrive within 120 seconds, then it
        returns an error up to docker.
        """
        name = u"myvol"
        dataset_id = UUID(dataset_id_from_name(name))
        # Create dataset on a different node:
        d = self.flocker_client.create_dataset(
            self.NODE_B, DEFAULT_SIZE, metadata={u"name": name},
            dataset_id=dataset_id)

        self._flush_volume_plugin_reactor_on_endpoint_render()

        # Pretend that it takes 500 seconds for the dataset to get established
        # on Node A. This should be longer than the timeout.
        self.volume_plugin_reactor.callLater(
            500.0, self.flocker_client.synchronize_state)

        d.addCallback(lambda _:
                      self.assertResult(
                          b"POST", b"/VolumeDriver.Mount",
                          {u"Name": name}, OK,
                          {u"Err": u"Timed out waiting for dataset to mount.",
                           u"Mountpoint": u""}))
        return d

    def test_mount_already_exists(self):
        """
        ``/VolumeDriver.Mount`` sets the primary of the dataset with matching
        name to the current node and then waits for the dataset to
        actually arrive when used by the volumes that already exist and
        don't have a special dataset ID.
        """
        name = u"myvol"

        d = self.flocker_client.create_dataset(
            self.NODE_A, DEFAULT_SIZE, metadata={u"name": name})

        def created(dataset):
            self.flocker_client.synchronize_state()
            result = self.assertResult(
                b"POST", b"/VolumeDriver.Mount",
                {u"Name": name}, OK,
                {u"Err": None,
                 u"Mountpoint": u"/flocker/{}".format(
                     dataset.dataset_id)})
            result.addCallback(lambda _:
                               self.flocker_client.list_datasets_state())
            result.addCallback(lambda ds: self.assertEqual(
                [self.NODE_A], [d.primary for d in ds
                                if d.dataset_id == dataset.dataset_id]))
            return result
        d.addCallback(created)
        return d

    def test_unknown_mount(self):
        """
        ``/VolumeDriver.Mount`` returns an error when asked to mount a
        non-existent volume.
        """
        name = u"myvol"
        return self.assertResult(
            b"POST", b"/VolumeDriver.Mount",
            {u"Name": name}, OK,
            {u"Err": u"Could not find volume with given name."})

    def test_path(self):
        """
        ``/VolumeDriver.Path`` returns the mount path of the given volume if
        it is currently known.
        """
        name = u"myvol"
        dataset_id = UUID(dataset_id_from_name(name))

        d = self.create(name)
        # The dataset arrives as state:
        d.addCallback(lambda _: self.flocker_client.synchronize_state())

        d.addCallback(lambda _: self.assertResponseCode(
            b"POST", b"/VolumeDriver.Mount", {u"Name": name}, OK))

        d.addCallback(lambda _:
                      self.assertResult(
                          b"POST", b"/VolumeDriver.Path",
                          {u"Name": name}, OK,
                          {u"Err": None,
                           u"Mountpoint": u"/flocker/{}".format(dataset_id)}))
        return d

    def test_path_existing(self):
        """
        ``/VolumeDriver.Path`` returns the mount path of the given volume if
        it is currently known, including for a dataset that was created
        not by the plugin.
        """
        name = u"myvol"

        d = self.flocker_client.create_dataset(
            self.NODE_A, DEFAULT_SIZE, metadata={u"name": name})

        def created(dataset):
            self.flocker_client.synchronize_state()
            return self.assertResult(
                b"POST", b"/VolumeDriver.Path",
                {u"Name": name}, OK,
                {u"Err": None,
                 u"Mountpoint": u"/flocker/{}".format(dataset.dataset_id)})
        d.addCallback(created)
        return d

    def test_unknown_path(self):
        """
        ``/VolumeDriver.Path`` returns an error when asked for the mount path
        of a non-existent volume.
        """
        name = u"myvol"
        return self.assertResult(
            b"POST", b"/VolumeDriver.Path",
            {u"Name": name}, OK,
            {u"Err": u"Could not find volume with given name."})

    def test_non_local_path(self):
        """
        ``/VolumeDriver.Path`` returns an error when asked for the mount path
        of a volume that is not mounted locally.

        This can happen as a result of ``docker inspect`` on a container
        that has been created but is still waiting for its volume to
        arrive from another node. It seems like Docker may also call this
        after ``/VolumeDriver.Create``, so again while waiting for a
        volume to arrive.
        """
        name = u"myvol"
        dataset_id = UUID(dataset_id_from_name(name))

        # Create dataset on node B:
        d = self.flocker_client.create_dataset(
            self.NODE_B, DEFAULT_SIZE, metadata={u"name": name},
            dataset_id=dataset_id)
        d.addCallback(lambda _: self.flocker_client.synchronize_state())

        # Ask for path on node A:
        d.addCallback(lambda _:
                      self.assertResult(
                          b"POST", b"/VolumeDriver.Path",
                          {u"Name": name}, OK,
                          {u"Err": "Volume not available.",
                           u"Mountpoint": u""}))
        return d

    @capture_logging(lambda self, logger:
                     self.assertEqual(
                         len(logger.flushTracebacks(CustomException)), 1))
    def test_unexpected_error_reporting(self, logger):
        """
        If an unexpected error occurs Docker gets back a useful error message.
        """
        def error():
            raise CustomException("I've made a terrible mistake")
        self.patch(self.flocker_client, "list_datasets_configuration",
                   error)
        return self.assertResult(
            b"POST", b"/VolumeDriver.Path",
            {u"Name": u"whatever"}, OK,
            {u"Err": "CustomException: I've made a terrible mistake"})

    @capture_logging(None)
    def test_bad_request(self, logger):
        """
        If a ``BadRequest`` exception is raised it is converted to appropriate
        JSON.
        """
        def error():
            raise make_bad_request(code=423, Err=u"no good")
        self.patch(self.flocker_client, "list_datasets_configuration",
                   error)
        return self.assertResult(
            b"POST", b"/VolumeDriver.Path",
            {u"Name": u"whatever"}, 423,
            {u"Err": "no good"})
示例#17
0
class SelfHealTests(SynchronousTestCase):
    """
    Tests for :obj:`SelfHeal`
    """
    def setUp(self):
        self.clock = Clock()
        self.log = mock_log()
        self.patch(sh, "get_groups_to_converge", intent_func("ggtc"))
        self.patch(sh, "check_and_trigger", lambda t, g: t + g)
        self.s = sh.SelfHeal(self.clock, base_dispatcher, "cf", 300.0,
                             self.log)
        self.groups = [{
            "tenantId": "t{}".format(i),
            "groupId": "g{}".format(i)
        } for i in range(5)]

    def test_setup(self):
        """
        ``self.s.setup()`` will setup convergences to be triggered over
        specified time range
        """
        self.s.dispatcher = SequenceDispatcher([(("ggtc", "cf"),
                                                 const(self.groups))])
        d = self.s.setup()
        self.successResultOf(d)
        calls = self.clock.getDelayedCalls()
        self.assertEqual(self.s._calls, calls)
        for i, c in enumerate(calls):
            self.assertEqual(c.getTime(), i * 60)
            self.assertEqual(c.func, sh.perform)
            self.assertEqual(c.args,
                             (self.s.dispatcher, "t{}g{}".format(i, i)))

    def test_setup_err(self):
        """
        ``self.s.setup()`` will log any error and return success
        """
        self.s.dispatcher = SequenceDispatcher([(("ggtc", "cf"),
                                                 conste(ValueError("h")))])
        d = self.s.setup()
        self.successResultOf(d)
        self.log.err.assert_called_once_with(CheckFailure(ValueError),
                                             "selfheal-setup-err",
                                             otter_service="selfheal")

    def test_setup_no_groups(self):
        """
        ``self.s.setup()`` gets groups and does nothing if there are no groups
        """
        self.s.dispatcher = SequenceDispatcher([(("ggtc", "cf"), const([]))])
        d = self.s.setup()
        self.successResultOf(d)
        self.assertEqual(self.s._calls, [])
        self.assertEqual(self.clock.getDelayedCalls(), [])

    def test_setup_still_active(self):
        """
        If there are scheduled calls when perform is called, they are
        cancelled and err is logged. Future calls are scheduled as usual
        """
        self.clock.advance(-0.6)
        call1 = self.clock.callLater(1, noop, None)
        call2 = self.clock.callLater(0, noop, None)
        call3 = self.clock.callLater(2, noop, None)
        self.clock.advance(0.6)
        self.s._calls = [call1, call2, call3]
        self.s.dispatcher = SequenceDispatcher([(("ggtc", "cf"),
                                                 const(self.groups))])
        d = self.s.setup()
        self.successResultOf(d)
        self.log.err.assert_called_once_with(matches(IsInstance(RuntimeError)),
                                             "selfheal-calls-err",
                                             active=2,
                                             otter_service="selfheal")
        self.assertFalse(call1.active())
        self.assertFalse(call2.active())
示例#18
0
class TestCase(unittest.TestCase):
    _enable_sync_v1: bool
    _enable_sync_v2: bool
    use_memory_storage: bool = USE_MEMORY_STORAGE

    def setUp(self):
        _set_test_mode(TestMode.TEST_ALL_WEIGHT)
        self.tmpdirs = []
        self.clock = Clock()
        self.clock.advance(time.time())
        self.log = logger.new()
        self.reset_peer_id_pool()
        self.rng = Random()

    def tearDown(self):
        self.clean_tmpdirs()

    def reset_peer_id_pool(self) -> None:
        self._free_peer_id_pool = self.new_peer_id_pool()

    def new_peer_id_pool(self) -> List[PeerId]:
        return PEER_ID_POOL.copy()

    def get_random_peer_id_from_pool(self,
                                     pool: Optional[List[PeerId]] = None,
                                     rng: Optional[Random] = None) -> PeerId:
        if pool is None:
            pool = self._free_peer_id_pool
        if not pool:
            raise RuntimeError('no more peer ids on the pool')
        if rng is None:
            rng = self.rng
        peer_id = self.rng.choice(pool)
        pool.remove(peer_id)
        return peer_id

    def _create_test_wallet(self):
        """ Generate a Wallet with a number of keypairs for testing
            :rtype: Wallet
        """
        tmpdir = tempfile.mkdtemp()
        self.tmpdirs.append(tmpdir)

        wallet = Wallet(directory=tmpdir)
        wallet.unlock(b'MYPASS')
        wallet.generate_keys(count=20)
        wallet.lock()
        return wallet

    def create_peer(self,
                    network,
                    peer_id=None,
                    wallet=None,
                    tx_storage=None,
                    unlock_wallet=True,
                    wallet_index=False,
                    capabilities=None,
                    full_verification=True,
                    enable_sync_v1=None,
                    enable_sync_v2=None,
                    checkpoints=None):
        if enable_sync_v1 is None:
            assert hasattr(self, '_enable_sync_v1'), (
                '`_enable_sync_v1` has no default by design, either set one on '
                'the test class or pass `enable_sync_v1` by argument')
            enable_sync_v1 = self._enable_sync_v1
        if enable_sync_v2 is None:
            assert hasattr(self, '_enable_sync_v2'), (
                '`_enable_sync_v2` has no default by design, either set one on '
                'the test class or pass `enable_sync_v2` by argument')
            enable_sync_v2 = self._enable_sync_v2
        assert enable_sync_v1 or enable_sync_v2, 'enable at least one sync version'

        if peer_id is None:
            peer_id = PeerId()
        if not wallet:
            wallet = self._create_test_wallet()
            if unlock_wallet:
                wallet.unlock(b'MYPASS')
        if tx_storage is None:
            if self.use_memory_storage:
                from hathor.transaction.storage.memory_storage import TransactionMemoryStorage
                tx_storage = TransactionMemoryStorage()
            else:
                from hathor.transaction.storage.rocksdb_storage import TransactionRocksDBStorage
                directory = tempfile.mkdtemp()
                self.tmpdirs.append(directory)
                tx_storage = TransactionRocksDBStorage(directory)
        manager = HathorManager(
            self.clock,
            peer_id=peer_id,
            network=network,
            wallet=wallet,
            tx_storage=tx_storage,
            wallet_index=wallet_index,
            capabilities=capabilities,
            rng=self.rng,
            enable_sync_v1=enable_sync_v1,
            enable_sync_v2=enable_sync_v2,
            checkpoints=checkpoints,
        )

        # XXX: just making sure that tests set this up correctly
        if enable_sync_v2:
            assert SyncVersion.V2 in manager.connections._sync_factories
        else:
            assert SyncVersion.V2 not in manager.connections._sync_factories
        if enable_sync_v1:
            assert SyncVersion.V1 in manager.connections._sync_factories
        else:
            assert SyncVersion.V1 not in manager.connections._sync_factories

        manager.avg_time_between_blocks = 0.0001
        manager._full_verification = full_verification
        manager.start()
        self.run_to_completion()
        return manager

    def run_to_completion(self):
        """ This will advance the test's clock until all calls scheduled are done.
        """
        for call in self.clock.getDelayedCalls():
            amount = call.getTime() - self.clock.seconds()
            self.clock.advance(amount)

    def assertTipsEqual(self, manager1, manager2):
        s1 = set(manager1.tx_storage.get_all_tips())
        s2 = set(manager2.tx_storage.get_all_tips())
        self.assertEqual(s1, s2)

        s1 = set(manager1.tx_storage.get_tx_tips())
        s2 = set(manager2.tx_storage.get_tx_tips())
        self.assertEqual(s1, s2)

    def assertTipsNotEqual(self, manager1, manager2):
        s1 = set(manager1.tx_storage.get_all_tips())
        s2 = set(manager2.tx_storage.get_all_tips())
        self.assertNotEqual(s1, s2)

    def assertConsensusEqual(self, manager1, manager2):
        self.assertEqual(manager1.tx_storage.get_count_tx_blocks(),
                         manager2.tx_storage.get_count_tx_blocks())
        for tx1 in manager1.tx_storage.get_all_transactions():
            tx2 = manager2.tx_storage.get_transaction(tx1.hash)
            tx1_meta = tx1.get_metadata()
            tx2_meta = tx2.get_metadata()
            # conflict_with's type is Optional[List[bytes]], so we convert to a set because order does not matter.
            self.assertEqual(set(tx1_meta.conflict_with or []),
                             set(tx2_meta.conflict_with or []))
            # Soft verification
            if tx1_meta.voided_by is None:
                # If tx1 is not voided, then tx2 must be not voided.
                self.assertIsNone(tx2_meta.voided_by)
            else:
                # If tx1 is voided, then tx2 must be voided.
                self.assertGreaterEqual(len(tx1_meta.voided_by), 1)
                self.assertGreaterEqual(len(tx2_meta.voided_by), 1)
            # Hard verification
            # self.assertEqual(tx1_meta.voided_by, tx2_meta.voided_by)

    def assertConsensusValid(self, manager):
        for tx in manager.tx_storage.get_all_transactions():
            if tx.is_block:
                self.assertBlockConsensusValid(tx)
            else:
                self.assertTransactionConsensusValid(tx)

    def assertBlockConsensusValid(self, block):
        self.assertTrue(block.is_block)
        if not block.parents:
            # Genesis
            return
        meta = block.get_metadata()
        if meta.voided_by is None:
            parent = block.get_block_parent()
            parent_meta = parent.get_metadata()
            self.assertIsNone(parent_meta.voided_by)

    def assertTransactionConsensusValid(self, tx):
        self.assertFalse(tx.is_block)
        meta = tx.get_metadata()
        if meta.voided_by and tx.hash in meta.voided_by:
            # If a transaction voids itself, then it must have at
            # least one conflict.
            self.assertTrue(meta.conflict_with)

        is_tx_executed = bool(not meta.voided_by)
        for h in meta.conflict_with or []:
            tx2 = tx.storage.get_transaction(h)
            meta2 = tx2.get_metadata()
            is_tx2_executed = bool(not meta2.voided_by)
            self.assertFalse(is_tx_executed and is_tx2_executed)

        for txin in tx.inputs:
            spent_tx = tx.get_spent_tx(txin)
            spent_meta = spent_tx.get_metadata()

            if spent_meta.voided_by is not None:
                self.assertIsNotNone(meta.voided_by)
                self.assertTrue(spent_meta.voided_by)
                self.assertTrue(meta.voided_by)
                self.assertTrue(spent_meta.voided_by.issubset(meta.voided_by))

        for parent in tx.get_parents():
            parent_meta = parent.get_metadata()
            if parent_meta.voided_by is not None:
                self.assertIsNotNone(meta.voided_by)
                self.assertTrue(parent_meta.voided_by)
                self.assertTrue(meta.voided_by)
                self.assertTrue(parent_meta.voided_by.issubset(meta.voided_by))

    def clean_tmpdirs(self):
        for tmpdir in self.tmpdirs:
            shutil.rmtree(tmpdir)

    def clean_pending(self, required_to_quiesce=True):
        """
        This handy method cleans all pending tasks from the reactor.

        When writing a unit test, consider the following question:

            Is the code that you are testing required to release control once it
            has done its job, so that it is impossible for it to later come around
            (with a delayed reactor task) and do anything further?

        If so, then trial will usefully test that for you -- if the code under
        test leaves any pending tasks on the reactor then trial will fail it.

        On the other hand, some code is *not* required to release control -- some
        code is allowed to continuously maintain control by rescheduling reactor
        tasks in order to do ongoing work.  Trial will incorrectly require that
        code to clean up all its tasks from the reactor.

        Most people think that such code should be amended to have an optional
        "shutdown" operation that releases all control, but on the contrary it is
        good design for some code to *not* have a shutdown operation, but instead
        to have a "crash-only" design in which it recovers from crash on startup.

        If the code under test is of the "long-running" kind, which is *not*
        required to shutdown cleanly in order to pass tests, then you can simply
        call testutil.clean_pending() at the end of the unit test, and trial will
        be satisfied.

        Copy from: https://github.com/zooko/pyutil/blob/master/pyutil/testutil.py#L68
        """
        pending = reactor.getDelayedCalls()
        active = bool(pending)
        for p in pending:
            if p.active():
                p.cancel()
            else:
                print('WEIRDNESS! pending timed call not active!')
        if required_to_quiesce and active:
            self.fail(
                'Reactor was still active when it was required to be quiescent.'
            )

    def get_address(self, index: int) -> Optional[str]:
        """ Generate a fixed HD Wallet and return an address
        """
        from hathor.wallet import HDWallet
        words = (
            'bind daring above film health blush during tiny neck slight clown salmon '
            'wine brown good setup later omit jaguar tourist rescue flip pet salute'
        )

        hd = HDWallet(words=words)
        hd._manually_initialize()

        if index >= hd.gap_limit:
            return None

        return list(hd.keys.keys())[index]
示例#19
0
class APITestsMixin(APIAssertionsMixin):
    """
    Helpers for writing tests for the Docker Volume Plugin API.
    """
    NODE_A = uuid4()
    NODE_B = uuid4()

    def initialize(self):
        """
        Create initial objects for the ``VolumePlugin``.
        """
        self.volume_plugin_reactor = Clock()
        self.flocker_client = SimpleCountingProxy(FakeFlockerClient())
        # The conditional_create operation used by the plugin relies on
        # the passage of time... so make sure time passes! We still use a
        # fake clock since some tests want to skip ahead.
        self.looping = LoopingCall(
            lambda: self.volume_plugin_reactor.advance(0.001))
        self.looping.start(0.001)
        self.addCleanup(self.looping.stop)

    def test_pluginactivate(self):
        """
        ``/Plugins.Activate`` indicates the plugin is a volume driver.
        """
        # Docker 1.8, at least, sends "null" as the body. Our test
        # infrastructure has the opposite bug so just going to send some
        # other garbage as the body (12345) to demonstrate that it's
        # ignored as per the spec which declares no body.
        return self.assertResult(b"POST", b"/Plugin.Activate", 12345, OK,
                                 {u"Implements": [u"VolumeDriver"]})

    def test_remove(self):
        """
        ``/VolumeDriver.Remove`` returns a successful result.
        """
        return self.assertResult(b"POST", b"/VolumeDriver.Remove",
                                 {u"Name": u"vol"}, OK, {u"Err": None})

    def test_unmount(self):
        """
        ``/VolumeDriver.Unmount`` returns a successful result.
        """
        return self.assertResult(b"POST", b"/VolumeDriver.Unmount",
                                 {u"Name": u"vol"}, OK, {u"Err": None})

    def test_create_with_profile(self):
        """
        Calling the ``/VolumerDriver.Create`` API with an ``Opts`` value
        of "profile=[gold,silver,bronze] in the request body JSON create a
        volume with a given name with [gold,silver,bronze] profile.
        """
        profile = sampled_from(["gold", "silver", "bronze"]).example()
        name = random_name(self)
        d = self.assertResult(b"POST", b"/VolumeDriver.Create", {
            u"Name": name,
            'Opts': {
                u"profile": profile
            }
        }, OK, {u"Err": None})
        d.addCallback(
            lambda _: self.flocker_client.list_datasets_configuration())
        d.addCallback(list)
        d.addCallback(
            lambda result: self.assertItemsEqual(result, [
                Dataset(dataset_id=result[0].dataset_id,
                        primary=self.NODE_A,
                        maximum_size=int(DEFAULT_SIZE.to_Byte()),
                        metadata={
                            u"name": name,
                            u"clusterhq:flocker:profile": unicode(profile)
                        })
            ]))
        return d

    def test_create_with_size(self):
        """
        Calling the ``/VolumerDriver.Create`` API with an ``Opts`` value
        of "size=<somesize> in the request body JSON create a volume
        with a given name and random size between 1-100G
        """
        name = random_name(self)
        size = integers(min_value=1, max_value=75).example()
        expression = volume_expression.example()
        size_opt = "".join(str(size)) + expression
        d = self.assertResult(b"POST", b"/VolumeDriver.Create", {
            u"Name": name,
            'Opts': {
                u"size": size_opt
            }
        }, OK, {u"Err": None})

        real_size = int(parse_num(size_opt).to_Byte())
        d.addCallback(
            lambda _: self.flocker_client.list_datasets_configuration())
        d.addCallback(list)
        d.addCallback(
            lambda result: self.assertItemsEqual(result, [
                Dataset(dataset_id=result[0].dataset_id,
                        primary=self.NODE_A,
                        maximum_size=real_size,
                        metadata={
                            u"name": name,
                            u"maximum_size": unicode(real_size)
                        })
            ]))
        return d

    @given(expr=volume_expression, size=integers(min_value=75, max_value=100))
    def test_parsenum_size(self, expr, size):
        """
        Send different forms of size expressions
        to ``parse_num``, we expect G(Gigabyte) size results.

        :param expr str: A string representing the size expression
        :param size int: A string representing the volume size
        """
        expected_size = int(GiB(size).to_Byte())
        return self.assertEqual(expected_size,
                                int(parse_num(str(size) + expr).to_Byte()))

    @given(expr=sampled_from(["KB", "MB", "GB", "TB", ""]),
           size=integers(min_value=1, max_value=100))
    def test_parsenum_all_sizes(self, expr, size):
        """
        Send standard size expressions to ``parse_num`` in
        many sizes, we expect to get correct size results.

        :param expr str: A string representing the size expression
        :param size int: A string representing the volume size
        """
        if expr is "KB":
            expected_size = int(KiB(size).to_Byte())
        elif expr is "MB":
            expected_size = int(MiB(size).to_Byte())
        elif expr is "GB":
            expected_size = int(GiB(size).to_Byte())
        elif expr is "TB":
            expected_size = int(TiB(size).to_Byte())
        else:
            expected_size = int(Byte(size).to_Byte())
        return self.assertEqual(expected_size,
                                int(parse_num(str(size) + expr).to_Byte()))

    @given(size=sampled_from(
        [u"foo10Gb", u"10bar10", "10foogib", "10Gfoo", "GIB", "bar10foo"]))
    def test_parsenum_bad_size(self, size):
        """
        Send unacceptable size expressions, upon error
        users should expect to receive Flocker's ``DEFAULT_SIZE``

        :param size str: A string representing the bad volume size
        """
        return self.assertEqual(int(DEFAULT_SIZE.to_Byte()),
                                int(parse_num(size).to_Byte()))

    def create(self, name):
        """
        Call the ``/VolumeDriver.Create`` API to create a volume with the
        given name.

        :param unicode name: The name of the volume to create.

        :return: ``Deferred`` that fires when the volume that was created.
        """
        return self.assertResult(b"POST", b"/VolumeDriver.Create",
                                 {u"Name": name}, OK, {u"Err": None})

    def test_create_creates(self):
        """
        ``/VolumeDriver.Create`` creates a new dataset in the configuration.
        """
        name = u"myvol"
        d = self.create(name)
        d.addCallback(
            lambda _: self.flocker_client.list_datasets_configuration())
        d.addCallback(list)
        d.addCallback(
            lambda result: self.assertItemsEqual(result, [
                Dataset(dataset_id=result[0].dataset_id,
                        primary=self.NODE_A,
                        maximum_size=int(DEFAULT_SIZE.to_Byte()),
                        metadata={u"name": name})
            ]))
        return d

    def test_create_duplicate_name(self):
        """
        If a dataset with the given name already exists,
        ``/VolumeDriver.Create`` succeeds without create a new volume.
        """
        name = u"thename"
        # Create a dataset out-of-band with matching name but non-matching
        # dataset ID:
        d = self.flocker_client.create_dataset(self.NODE_A,
                                               int(DEFAULT_SIZE.to_Byte()),
                                               metadata={u"name": name})
        d.addCallback(lambda _: self.create(name))
        d.addCallback(
            lambda _: self.flocker_client.list_datasets_configuration())
        d.addCallback(lambda results: self.assertEqual(len(list(results)), 1))
        return d

    def test_create_duplicate_name_race_condition(self):
        """
        If a dataset with the given name is created while the
        ``/VolumeDriver.Create`` call is in flight, the call does not
        result in an error.
        """
        name = u"thename"

        # Create a dataset out-of-band with matching dataset ID and name
        # which the docker plugin won't be able to see.
        def create_after_list():
            # Clean up the patched version:
            del self.flocker_client.list_datasets_configuration
            # But first time we're called, we create dataset and lie about
            # its existence:
            d = self.flocker_client.create_dataset(self.NODE_A,
                                                   int(DEFAULT_SIZE.to_Byte()),
                                                   metadata={u"name": name})
            d.addCallback(
                lambda _: DatasetsConfiguration(tag=u"1234", datasets={}))
            return d

        self.flocker_client.list_datasets_configuration = create_after_list

        return self.create(name)

    def _flush_volume_plugin_reactor_on_endpoint_render(self):
        """
        This method patches ``self.app`` so that after any endpoint is
        rendered, the reactor used by the volume plugin is advanced repeatedly
        until there are no more ``delayedCalls`` pending on the reactor.
        """
        real_execute_endpoint = self.app.execute_endpoint

        def patched_execute_endpoint(*args, **kwargs):
            val = real_execute_endpoint(*args, **kwargs)
            while self.volume_plugin_reactor.getDelayedCalls():
                pending_calls = self.volume_plugin_reactor.getDelayedCalls()
                next_expiration = min(t.getTime() for t in pending_calls)
                now = self.volume_plugin_reactor.seconds()
                self.volume_plugin_reactor.advance(
                    max(0.0, next_expiration - now))
            return val

        self.patch(self.app, 'execute_endpoint', patched_execute_endpoint)

    def test_mount(self):
        """
        ``/VolumeDriver.Mount`` sets the primary of the dataset with matching
        name to the current node and then waits for the dataset to
        actually arrive.
        """
        name = u"myvol"
        dataset_id = uuid4()

        # Create dataset on a different node:
        d = self.flocker_client.create_dataset(self.NODE_B,
                                               int(DEFAULT_SIZE.to_Byte()),
                                               metadata={u"name": name},
                                               dataset_id=dataset_id)

        self._flush_volume_plugin_reactor_on_endpoint_render()

        # Pretend that it takes 5 seconds for the dataset to get established on
        # Node A.
        self.volume_plugin_reactor.callLater(
            5.0, self.flocker_client.synchronize_state)

        d.addCallback(lambda _: self.assertResult(
            b"POST", b"/VolumeDriver.Mount", {u"Name": name}, OK, {
                u"Err": None,
                u"Mountpoint": u"/flocker/{}".format(dataset_id)
            }))
        d.addCallback(lambda _: self.flocker_client.list_datasets_state())

        def final_assertions(datasets):
            self.assertEqual(
                [self.NODE_A],
                [d.primary for d in datasets if d.dataset_id == dataset_id])
            # There should be less than 20 calls to list_datasets_state over
            # the course of 5 seconds.
            self.assertLess(
                self.flocker_client.num_calls('list_datasets_state'), 20)

        d.addCallback(final_assertions)

        return d

    def test_mount_timeout(self):
        """
        ``/VolumeDriver.Mount`` sets the primary of the dataset with matching
        name to the current node and then waits for the dataset to
        actually arrive. If it does not arrive within 120 seconds, then it
        returns an error up to docker.
        """
        name = u"myvol"
        dataset_id = uuid4()
        # Create dataset on a different node:
        d = self.flocker_client.create_dataset(self.NODE_B,
                                               int(DEFAULT_SIZE.to_Byte()),
                                               metadata={u"name": name},
                                               dataset_id=dataset_id)

        self._flush_volume_plugin_reactor_on_endpoint_render()

        # Pretend that it takes 500 seconds for the dataset to get established
        # on Node A. This should be longer than the timeout.
        self.volume_plugin_reactor.callLater(
            500.0, self.flocker_client.synchronize_state)

        d.addCallback(lambda _: self.assertResult(
            b"POST", b"/VolumeDriver.Mount", {u"Name": name}, OK, {
                u"Err": u"Timed out waiting for dataset to mount.",
                u"Mountpoint": u""
            }))
        return d

    def test_mount_already_exists(self):
        """
        ``/VolumeDriver.Mount`` sets the primary of the dataset with matching
        name to the current node and then waits for the dataset to
        actually arrive when used by the volumes that already exist and
        don't have a special dataset ID.
        """
        name = u"myvol"

        d = self.flocker_client.create_dataset(self.NODE_A,
                                               int(DEFAULT_SIZE.to_Byte()),
                                               metadata={u"name": name})

        def created(dataset):
            self.flocker_client.synchronize_state()
            result = self.assertResult(
                b"POST", b"/VolumeDriver.Mount", {u"Name": name}, OK, {
                    u"Err": None,
                    u"Mountpoint": u"/flocker/{}".format(dataset.dataset_id)
                })
            result.addCallback(
                lambda _: self.flocker_client.list_datasets_state())
            result.addCallback(lambda ds: self.assertEqual([self.NODE_A], [
                d.primary for d in ds if d.dataset_id == dataset.dataset_id
            ]))
            return result

        d.addCallback(created)
        return d

    def test_unknown_mount(self):
        """
        ``/VolumeDriver.Mount`` returns an error when asked to mount a
        non-existent volume.
        """
        name = u"myvol"
        return self.assertResult(
            b"POST", b"/VolumeDriver.Mount", {u"Name": name}, OK,
            {u"Err": u"Could not find volume with given name."})

    def test_path(self):
        """
        ``/VolumeDriver.Path`` returns the mount path of the given volume if
        it is currently known.
        """
        name = u"myvol"

        d = self.create(name)
        # The dataset arrives as state:
        d.addCallback(lambda _: self.flocker_client.synchronize_state())

        d.addCallback(lambda _: self.assertResponseCode(
            b"POST", b"/VolumeDriver.Mount", {u"Name": name}, OK))
        d.addCallback(
            lambda _: self.flocker_client.list_datasets_configuration())
        d.addCallback(lambda datasets_config: self.assertResult(
            b"POST", b"/VolumeDriver.Path", {u"Name": name}, OK, {
                u"Err":
                None,
                u"Mountpoint":
                u"/flocker/{}".format(datasets_config.datasets.keys()[0])
            }))
        return d

    def test_path_existing(self):
        """
        ``/VolumeDriver.Path`` returns the mount path of the given volume if
        it is currently known, including for a dataset that was created
        not by the plugin.
        """
        name = u"myvol"

        d = self.flocker_client.create_dataset(self.NODE_A,
                                               int(DEFAULT_SIZE.to_Byte()),
                                               metadata={u"name": name})

        def created(dataset):
            self.flocker_client.synchronize_state()
            return self.assertResult(
                b"POST", b"/VolumeDriver.Path", {u"Name": name}, OK, {
                    u"Err": None,
                    u"Mountpoint": u"/flocker/{}".format(dataset.dataset_id)
                })

        d.addCallback(created)
        return d

    def test_unknown_path(self):
        """
        ``/VolumeDriver.Path`` returns an error when asked for the mount path
        of a non-existent volume.
        """
        name = u"myvol"
        return self.assertResult(
            b"POST", b"/VolumeDriver.Path", {u"Name": name}, OK,
            {u"Err": u"Could not find volume with given name."})

    def test_non_local_path(self):
        """
        ``/VolumeDriver.Path`` returns an error when asked for the mount path
        of a volume that is not mounted locally.

        This can happen as a result of ``docker inspect`` on a container
        that has been created but is still waiting for its volume to
        arrive from another node. It seems like Docker may also call this
        after ``/VolumeDriver.Create``, so again while waiting for a
        volume to arrive.
        """
        name = u"myvol"
        dataset_id = uuid4()

        # Create dataset on node B:
        d = self.flocker_client.create_dataset(self.NODE_B,
                                               int(DEFAULT_SIZE.to_Byte()),
                                               metadata={u"name": name},
                                               dataset_id=dataset_id)
        d.addCallback(lambda _: self.flocker_client.synchronize_state())

        # Ask for path on node A:
        d.addCallback(lambda _: self.assertResult(
            b"POST", b"/VolumeDriver.Path", {u"Name": name}, OK, {
                u"Err": "Volume not available.",
                u"Mountpoint": u""
            }))
        return d

    @capture_logging(lambda self, logger: self.assertEqual(
        len(logger.flushTracebacks(CustomException)), 1))
    def test_unexpected_error_reporting(self, logger):
        """
        If an unexpected error occurs Docker gets back a useful error message.
        """
        def error():
            raise CustomException("I've made a terrible mistake")

        self.patch(self.flocker_client, "list_datasets_configuration", error)
        return self.assertResult(
            b"POST", b"/VolumeDriver.Path", {u"Name": u"whatever"}, OK,
            {u"Err": "CustomException: I've made a terrible mistake"})

    @capture_logging(None)
    def test_bad_request(self, logger):
        """
        If a ``BadRequest`` exception is raised it is converted to appropriate
        JSON.
        """
        def error():
            raise make_bad_request(code=423, Err=u"no good")

        self.patch(self.flocker_client, "list_datasets_configuration", error)
        return self.assertResult(b"POST", b"/VolumeDriver.Path",
                                 {u"Name": u"whatever"}, 423,
                                 {u"Err": "no good"})

    def test_unsupported_method(self):
        """
        If an unsupported method is requested the 405 Not Allowed response
        code is returned.
        """
        return self.assertResponseCode(b"BAD_METHOD", b"/VolumeDriver.Path",
                                       None, NOT_ALLOWED)

    def test_unknown_uri(self):
        """
        If an unknown URI path is requested the 404 Not Found response code is
        returned.
        """
        return self.assertResponseCode(b"BAD_METHOD", b"/xxxnotthere", None,
                                       NOT_FOUND)

    def test_empty_host(self):
        """
        If an empty host header is sent to the Docker plugin it does not blow
        up, instead operating normally. E.g. for ``Plugin.Activate`` call
        returns the ``Implements`` response.
        """
        return self.assertResult(b"POST",
                                 b"/Plugin.Activate",
                                 12345,
                                 OK, {u"Implements": [u"VolumeDriver"]},
                                 additional_headers={b"Host": [""]})
class TimeoutTests(TestCase):
    """
    Tests for :py:func:`timeout`.
    """

    def setUp(self):
        """
        Initialize testing helper variables.
        """
        super(TimeoutTests, self).setUp()
        self.deferred = Deferred()
        self.timeout = 10.0
        self.clock = Clock()

    def test_doesnt_time_out_early(self):
        """
        A deferred that has not fired by some short while prior to the timeout
        interval is not made to fire with a timeout failure.
        """
        deferred = timeout(self.clock, self.deferred, self.timeout)
        self.clock.advance(self.timeout - 1.0)
        self.assertNoResult(deferred)

    def test_times_out(self):
        """
        A deferred that does not fire within the timeout interval is made to
        fire with ``CancelledError`` once the timeout interval elapses.
        """
        deferred = timeout(self.clock, self.deferred, self.timeout)
        self.clock.advance(self.timeout)
        self.failureResultOf(deferred, CancelledError)

    def test_times_out_with_reason(self):
        """
        If a custom reason is passed to ``timeout`` and the Deferred does not
        fire within the timeout interval, it is made to fire with the custom
        reason once the timeout interval elapses.
        """
        reason = CustomException(self.id())
        deferred = timeout(self.clock, self.deferred, self.timeout, reason)
        self.clock.advance(self.timeout)
        self.assertEqual(
            reason,
            self.failureResultOf(deferred, CustomException).value,
        )

    def test_doesnt_time_out(self):
        """
        A deferred that fires before the timeout is not cancelled by the
        timeout.
        """
        deferred = timeout(self.clock, self.deferred, self.timeout)
        self.clock.advance(self.timeout - 1.0)
        self.deferred.callback('Success')
        self.assertEqual(self.successResultOf(deferred), 'Success')

    def test_doesnt_time_out_failure(self):
        """
        A Deferred that fails before the timeout is not cancelled by the
        timeout.
        """
        deferred = timeout(self.clock, self.deferred, self.timeout)
        self.clock.advance(self.timeout - 1.0)
        self.deferred.errback(CustomException(self.id()))
        self.failureResultOf(deferred, CustomException)

    def test_doesnt_time_out_failure_custom_reason(self):
        """
        A Deferred that fails before the timeout is not cancelled by the
        timeout.
        """
        deferred = timeout(
            self.clock, self.deferred, self.timeout,
            ValueError("This should not appear.")
        )
        self.clock.advance(self.timeout - 1.0)
        self.deferred.errback(CustomException(self.id()))
        self.failureResultOf(deferred, CustomException)

    def test_advancing_after_success(self):
        """
        A Deferred that fires before the timeout continues to succeed after the
        timeout has elapsed.
        """
        deferred = succeed('Success')
        timeout(self.clock, deferred, self.timeout)
        self.clock.advance(self.timeout)
        self.assertEqual(self.successResultOf(deferred), 'Success')

    def test_advancing_after_failure(self):
        """
        A Deferred that fires with a failure before the timeout continues to
        fail after the timeout has elapsed.
        """
        deferred = fail(CustomException(self.id()))
        timeout(self.clock, deferred, self.timeout)
        self.clock.advance(self.timeout)
        self.failureResultOf(deferred, CustomException)

    def test_timeout_cleaned_up_on_success(self):
        """
        If the deferred is successfully completed before the timeout, the
        timeout is not still pending on the reactor.
        """
        timeout(self.clock, self.deferred, self.timeout)
        self.deferred.callback('Success')
        self.assertEqual(self.clock.getDelayedCalls(), [])

    def test_timeout_cleaned_up_on_failure(self):
        """
        If the deferred is failed before the timeout, the timeout is not still
        pending on the reactor.
        """
        timeout(self.clock, self.deferred, self.timeout)
        self.deferred.errback(CustomException(self.id()))
        # We need to handle the errback so that Trial and/or testtools don't
        # fail with unhandled errors.
        self.addCleanup(self.deferred.addErrback, lambda _: None)
        self.assertEqual(self.clock.getDelayedCalls(), [])
示例#21
0
class FakeReactor(MemoryReactor):
    """A fake reactor to be used in tests. This reactor doesn't actually do
    much that's useful yet. It accepts TCP connection setup attempts, but
    they will never succeed.

    Examples:
        >>> import sys
        >>> from twisted.internet.abstract import FileDescriptor
        >>> from twisted.internet.fdesc import readFromFD, setNonBlocking
        >>>
        >>> # reactor = proto_helpers.FakeReactor()
        >>> reactor = FakeReactor()
        >>> f = FileDescriptor(reactor)
        >>> f.fileno = sys.__stdout__.fileno
        >>> fd = f.fileno()
        >>> setNonBlocking(fd)
        >>> readFromFD(fd, print)
    """
    _DELAY = 1

    def __init__(self):
        super(FakeReactor, self).__init__()
        self._clock = Clock()
        reactor.fake = True
        msg = 'Attention! Running fake reactor'
        logger.debug('%s. Some deferreds may not work as intended.' % msg)
        self.running = False

    def resolve(self, *args, **kwargs):
        """Return a L{twisted.internet.defer.Deferred} that will resolve a hostname.
        """
        pass

    def run(self):
        """Fake L{IReactorCore.run}.
        """
        self.running = True

    def stop(self):
        """Fake L{IReactorCore.stop}.
        """
        self.running = False

    def crash(self):
        """Fake L{IReactorCore.crash}.
        """
        self.running = False

    def iterate(self, *args, **kwargs):
        """Fake L{IReactorCore.iterate}.
        """
        pass

    def fireSystemEvent(self, *args, **kwargs):
        """Fake L{IReactorCore.fireSystemEvent}.
        """
        pass

    def addSystemEventTrigger(self, *args, **kwargs):
        """Fake L{IReactorCore.addSystemEventTrigger}.
        """
        pass

    def removeSystemEventTrigger(self, *args, **kwargs):
        """Fake L{IReactorCore.removeSystemEventTrigger}.
        """
        pass

    def callWhenRunning(self, *args, **kwargs):
        """Fake L{IReactorCore.callWhenRunning}.
        """
        pass

    def getDelayedCalls(self):
        """Return all the outstanding delayed calls in the system.
        """
        return self._clock.getDelayedCalls()

    def callLater(self, when, what, *args, **kwargs):
        """Schedule a unit of work to be done later.
        """
        delayed = self._clock.callLater(when, what, *args, **kwargs)
        self.pump()
        return delayed

    def pump(self):
        """Perform scheduled work
        """
        self._clock.advance(self._DELAY)
示例#22
0
class TestPersistenceFileGlue(unittest.TestCase):
    def setUp(self):
        self.__clock = Clock()
        self.__temp_dir = tempfile.mkdtemp(
            prefix='shinysdr_test_persistence_tmp')
        self.__state_name = os.path.join(self.__temp_dir, 'state')
        self.__reset()

    def tearDown(self):
        self.assertFalse(self.__clock.getDelayedCalls())
        shutil.rmtree(self.__temp_dir)

    def __reset(self):
        """Recreate the object for write-then-read tests."""
        self.__root = ValueAndBlockSpecimen(value='initial')

    def __start(self, get_defaults=lambda _: {'value': 'default'}, **kwargs):
        return PersistenceFileGlue(reactor=self.__clock,
                                   root_object=self.__root,
                                   filename=self.__state_name,
                                   get_defaults=get_defaults,
                                   **kwargs)

    def test_no_defaults(self):
        self.__start(get_defaults=lambda _: {})
        # It would be surprising if this assertion failed; this test is mainly just to test the initialization succeeds
        self.assertEqual(self.__root.get_value(), 'initial')

    def test_defaults(self):
        self.__start()
        self.assertEqual(self.__root.get_value(), 'default')

    def test_no_persistence(self):
        self.__state_name = None
        self.__start()
        self.assertEqual(self.__root.get_value(), 'default')

    def test_persistence(self):
        """Test that state persists."""
        pfg = self.__start()
        self.__root.set_value('set')
        advance_until(self.__clock, pfg.sync(), limit=2)
        self.__reset()
        self.__start()
        self.assertEqual(self.__root.get_value(), 'set')  # check persistence

    def test_delay_is_present(self):
        """Test that persistence isn't immediate."""
        pfg = self.__start()
        self.__root.set_value('set')
        self.__reset()
        self.__start()
        self.assertEqual(self.__root.get_value(),
                         'default')  # change not persisted
        advance_until(self.__clock, pfg.sync(),
                      limit=2)  # clean up clock for tearDown check

    def test_broken_state_recovery(self):
        pfg = self.__start()
        self.__root.set_value(ObjectWhichCannotBePersisted())
        try:
            advance_until(self.__clock, pfg.sync(), limit=2)
        except TypeError:  # expected error
            pass
        self.__reset()
        self.__start()
        # now we should be back to the default value
        self.assertEqual(self.__root.get_value(), 'default')

    def test_unparseable_file_recovery(self):
        with open(self.__state_name, 'w'):
            pass  # write empty file
        self.__start(_suppress_error_for_test=True)
        self.assertEqual(self.__root.get_value(), 'default')
示例#23
0
文件: mock.py 项目: nerevu/riko
class FakeReactor(MemoryReactor):
    """A fake reactor to be used in tests. This reactor doesn't actually do
    much that's useful yet. It accepts TCP connection setup attempts, but
    they will never succeed.

    Examples:
        >>> import sys
        >>> from twisted.internet.abstract import FileDescriptor
        >>> from twisted.internet.fdesc import readFromFD, setNonBlocking
        >>>
        >>> # reactor = proto_helpers.FakeReactor()
        >>> reactor = FakeReactor()
        >>> f = FileDescriptor(reactor)
        >>> f.fileno = sys.__stdout__.fileno
        >>> fd = f.fileno()
        >>> setNonBlocking(fd)
        >>> readFromFD(fd, print)
    """
    _DELAY = 1

    def __init__(self):
        super(FakeReactor, self).__init__()
        self._clock = Clock()
        reactor.fake = True
        msg = 'Attention! Running fake reactor'
        logger.debug('%s. Some deferreds may not work as intended.' % msg)
        self.running = False

    def resolve(self, *args, **kwargs):
        """Return a L{twisted.internet.defer.Deferred} that will resolve a hostname.
        """
        pass

    def run(self):
        """Fake L{IReactorCore.run}.
        """
        self.running = True

    def stop(self):
        """Fake L{IReactorCore.stop}.
        """
        self.running = False

    def crash(self):
        """Fake L{IReactorCore.crash}.
        """
        self.running = False

    def iterate(self, *args, **kwargs):
        """Fake L{IReactorCore.iterate}.
        """
        pass

    def fireSystemEvent(self, *args, **kwargs):
        """Fake L{IReactorCore.fireSystemEvent}.
        """
        pass

    def addSystemEventTrigger(self, *args, **kwargs):
        """Fake L{IReactorCore.addSystemEventTrigger}.
        """
        pass

    def removeSystemEventTrigger(self, *args, **kwargs):
        """Fake L{IReactorCore.removeSystemEventTrigger}.
        """
        pass

    def callWhenRunning(self, *args, **kwargs):
        """Fake L{IReactorCore.callWhenRunning}.
        """
        pass

    def getDelayedCalls(self):
        """Return all the outstanding delayed calls in the system.
        """
        return self._clock.getDelayedCalls()

    def callLater(self, when, what, *args, **kwargs):
        """Schedule a unit of work to be done later.
        """
        delayed = self._clock.callLater(when, what, *args, **kwargs)
        self.pump()
        return delayed

    def pump(self):
        """Perform scheduled work
        """
        self._clock.advance(self._DELAY)
示例#24
0
class TimeoutTests(SynchronousTestCase):
    """
    Tests for :py:func:`timeout`.
    """

    def setUp(self):
        """Initialize testing helper variables."""
        self._deferred = Deferred()
        self._timeout = 1.0
        self._clock = Clock()

    def _execute_timeout(self):
        """Execute the timeout."""
        timeout(self._clock, self._deferred, self._timeout)

    def test_times_out(self):
        """
        A deferred that never fires is timed out at the correct time using the
        timeout function, and concludes with a CancelledError failure.
        """
        self._execute_timeout()
        self._clock.advance(self._timeout - 0.1)
        self.assertFalse(self._deferred.called)
        self._clock.advance(0.1)
        self.assertTrue(self._deferred.called)
        self.failureResultOf(self._deferred, CancelledError)

    def test_doesnt_time_out(self):
        """
        A deferred that fires before the timeout is not cancelled by the
        timeout.
        """
        self._execute_timeout()
        self._clock.advance(self._timeout - 0.1)
        self.assertFalse(self._deferred.called)
        self._deferred.callback('Success')
        self.assertTrue(self._deferred.called)
        self.assertEqual(self._deferred.result, 'Success')
        self._clock.advance(0.1)
        self.assertTrue(self._deferred.called)
        self.assertEqual(self._deferred.result, 'Success')

    def test_timeout_cleaned_up_on_success(self):
        """
        If the deferred is successfully completed before the timeout, the
        timeout is not still pending on the reactor.
        """
        self._execute_timeout()
        self._clock.advance(self._timeout - 0.1)
        self._deferred.callback('Success')
        self.assertEqual(self._clock.getDelayedCalls(), [])
        self.assertEqual(self._deferred.result, 'Success')

    def test_timeout_cleaned_up_on_failure(self):
        """
        If the deferred is failed before the timeout, the timeout is not still
        pending on the reactor.
        """
        self._execute_timeout()
        self._clock.advance(self._timeout - 0.1)
        self._deferred.errback(Exception('ErrorXYZ'))
        self.assertEqual(self._clock.getDelayedCalls(), [])
        self.assertEqual(self._deferred.result.getErrorMessage(), 'ErrorXYZ')
        self.failureResultOf(self._deferred, Exception)
示例#25
0
class TransportTestCase(object):
    """PT client and server connect over a string transport.

    We bypass the communication between client and server and intercept the
    messages sent over the string transport.
    """

    def setUp(self):
        """Set the reactor's callLater to our clock's callLater function
        and build the protocols.
        """
        self.clock = Clock()
        reactor.callLater = self.clock.callLater
        self.dump = []
        self.proto_client = self._build_protocol(const.CLIENT)
        self.proto_server = self._build_protocol(const.SERVER)
        self.pt_client = self.proto_client.circuit.transport
        self.pt_server = self.proto_server.circuit.transport
        self._proxy(self.proto_client, self.proto_server)
        self._bypass_connection(self.proto_client, self.proto_server)

    def _proxy(self, client, server):
        """Proxy the communication between client and server and dump
        intercepted data into a dictionary.
        """
        def decorate_intercept(end):
            old_rcv_f = end.circuit.transport.receivedDownstream
            old_snd_f = end.circuit.transport.sendDownstream
            def intercept(old_f, direction):
                def new_f(data):
                    msgs = old_f(data)
                    end.history[direction].append((self.clock.seconds(), msgs))
                return new_f
            end.circuit.transport.receivedDownstream = intercept(old_rcv_f, 'rcv')
            end.circuit.transport.sendDownstream = intercept(old_snd_f, 'snd')
        decorate_intercept(client)
        decorate_intercept(server)

    def _bypass_connection(self, client, server):
        """Instead of requiring TCP connections between client and server
        transports, we directly pass the data written from one end to the
        received function at the other.
        """
        def curry_bypass_connection(up, down, direction):
            old_write = up.circuit.downstream.write
            def write(data):
                old_write(data)
                down.dataReceived(data)
                self.dump.append((self.clock.seconds(), direction * len(data)))
            return write
        client.circuit.downstream.write = curry_bypass_connection(client, server, const.OUT)
        server.circuit.downstream.write = curry_bypass_connection(server, client, const.IN)

    def _build_protocol(self, mode):
        """Build client and server protocols for an end point."""
        addr_tuple = (HOST, str(PORT))
        address = IPv4Address('TCP', HOST, PORT)
        pt_config = self._build_transport_configuration(mode)
        transport_class = self._configure_transport_class(mode, pt_config)
        f_server = net.StaticDestinationServerFactory(addr_tuple, mode, transport_class, pt_config)
        protocol_server = self._set_protocol(f_server, address)
        f_client = net.StaticDestinationClientFactory(protocol_server.circuit, const.CLIENT)
        protocol_client = self._set_protocol(f_client, address)
        if mode == const.CLIENT:
            return protocol_client
        elif mode == const.SERVER:
            return protocol_server
        else:
            raise ValueError("Transport mode '%s' not recognized." % mode)

    def _set_protocol(self, factory, address):
        """Make protocol connection with a Twisted string transport."""
        protocol = factory.buildProtocol(address)
        protocol.makeConnection(proto_helpers.StringTransport())
        protocol.history = {'rcv': [], 'snd': []}
        return protocol

    def _build_transport_configuration(self, mode):
        """Configure transport as a managed transport."""
        pt_config = transport_config.TransportConfig()
        pt_config.setStateLocation(const.TEMP_DIR)
        pt_config.setObfsproxyMode("managed")
        pt_config.setListenerMode(mode)
        return pt_config

    def _configure_transport_class(self, mode, pt_config):
        """Use the global arguments to configure the trasnport."""
        transport_args = [mode, ADDR, "--dest=%s" % ADDR] + self.args
        sys.argv = [sys.argv[0],
                "--log-file", join(const.TEMP_DIR, "%s.log" % mode),
                "--log-min-severity", "debug"]
        sys.argv.append("wfpad")  # use wfpad transport
        sys.argv += transport_args
        parser = set_up_cli_parsing()
        consider_cli_args(parser.parse_args())
        transport_class = get_transport_class(self.transport, mode)
        transport_class.setup(pt_config)
        p = ArgumentParser()
        transport_class.register_external_mode_cli(p)
        args = p.parse_args(transport_args)
        transport_class.validate_external_mode_cli(args)
        return transport_class

    def _lose_protocol_connection(self, protocol):
        """Disconnect client and server transports."""
        protocol.circuit.upstream.transport.loseConnection()
        protocol.circuit.downstream.transport.loseConnection()

    def advance_next_delayed_call(self):
        """Advance clock to first delayed call in reactor."""
        first_delayed_call = self.clock.getDelayedCalls()[0]
        self.clock.advance(first_delayed_call.getTime() - self.clock.seconds())

    def is_timeout(self, call):
        """Check if the call has actually timed out."""
        return isinstance(call.args[0], TimeoutError)

    def advance_delayed_calls(self, max_dcalls=NUM_DCALLS, no_timeout=True):
        """Advance clock to the point all delayed calls up to that moment have
        been called.
        """
        i, timeouts = 0, []
        while len(self.clock.getDelayedCalls()) > 0 and i < max_dcalls:
            i += 1
            dcall = self.clock.getDelayedCalls()[0]
            if no_timeout:
                if len(dcall.args) > 0 and self.is_timeout(dcall):
                    if dcall in timeouts:
                        break
                    self._queue_first_call()
                    timeouts.append(dcall)
                    continue
            self.advance_next_delayed_call()

    def _queue_first_call(self, delay=10000.0):
        """Put the first delayed call to the last position."""
        timeout = self.clock.calls.pop(0)
        timeout.time = delay
        self.clock.calls.append(timeout)

    def tearDown(self):
        """Close connections and advance all delayed calls."""
        # Need to wait a bit beacuse obfsproxy network.Circuit.circuitCompleted
        # defers 0.02s a dummy call to dataReceived to flush connection.
        self._lose_protocol_connection(self.proto_client)
        self._lose_protocol_connection(self.proto_server)
        self.advance_delayed_calls()
示例#26
0
class TestCase(unittest.TestCase):
    def setUp(self):
        self.tmpdirs = []
        self.clock = Clock()
        self.clock.advance(time.time())
        self.log = logger.new()

    def tearDown(self):
        self.clean_tmpdirs()

    def _create_test_wallet(self):
        """ Generate a Wallet with a number of keypairs for testing
            :rtype: Wallet
        """
        tmpdir = tempfile.mkdtemp()
        self.tmpdirs.append(tmpdir)

        wallet = Wallet(directory=tmpdir)
        wallet.unlock(b'MYPASS')
        wallet.generate_keys(count=20)
        wallet.lock()
        return wallet

    def create_peer(self,
                    network,
                    peer_id=None,
                    wallet=None,
                    tx_storage=None,
                    unlock_wallet=True,
                    wallet_index=False,
                    capabilities=None):
        if peer_id is None:
            peer_id = PeerId()
        if not wallet:
            wallet = self._create_test_wallet()
            if unlock_wallet:
                wallet.unlock(b'MYPASS')
        manager = HathorManager(
            self.clock,
            peer_id=peer_id,
            network=network,
            wallet=wallet,
            tx_storage=tx_storage,
            wallet_index=wallet_index,
            capabilities=capabilities,
        )
        manager.avg_time_between_blocks = 0.0001
        manager.test_mode = TestMode.TEST_ALL_WEIGHT
        manager._full_verification = True
        manager.start()
        self.run_to_completion()
        return manager

    def run_to_completion(self):
        """ This will advance the test's clock until all calls scheduled are done.
        """
        for call in self.clock.getDelayedCalls():
            amount = call.getTime() - self.clock.seconds()
            self.clock.advance(amount)

    def set_random_seed(self, seed=None):
        if seed is None:
            seed = numpy.random.randint(2**32)
        self.random_seed = seed
        random.seed(self.random_seed)
        numpy.random.seed(self.random_seed)

    def assertTipsEqual(self, manager1, manager2):
        s1 = set(manager1.tx_storage.get_all_tips())
        s2 = set(manager2.tx_storage.get_all_tips())
        self.assertEqual(s1, s2)

        s1 = set(manager1.tx_storage.get_tx_tips())
        s2 = set(manager2.tx_storage.get_tx_tips())
        self.assertEqual(s1, s2)

    def assertTipsNotEqual(self, manager1, manager2):
        s1 = set(manager1.tx_storage.get_all_tips())
        s2 = set(manager2.tx_storage.get_all_tips())
        self.assertNotEqual(s1, s2)

    def assertConsensusEqual(self, manager1, manager2):
        self.assertEqual(manager1.tx_storage.get_count_tx_blocks(),
                         manager2.tx_storage.get_count_tx_blocks())
        for tx1 in manager1.tx_storage.get_all_transactions():
            tx2 = manager2.tx_storage.get_transaction(tx1.hash)
            tx1_meta = tx1.get_metadata()
            tx2_meta = tx2.get_metadata()
            self.assertEqual(tx1_meta.conflict_with, tx2_meta.conflict_with)
            # Soft verification
            if tx1_meta.voided_by is None:
                # If tx1 is not voided, then tx2 must be not voided.
                self.assertIsNone(tx2_meta.voided_by)
            else:
                # If tx1 is voided, then tx2 must be voided.
                self.assertGreaterEqual(len(tx1_meta.voided_by), 1)
                self.assertGreaterEqual(len(tx2_meta.voided_by), 1)
            # Hard verification
            # self.assertEqual(tx1_meta.voided_by, tx2_meta.voided_by)

    def assertConsensusValid(self, manager):
        for tx in manager.tx_storage.get_all_transactions():
            if tx.is_block:
                self.assertBlockConsensusValid(tx)
            else:
                self.assertTransactionConsensusValid(tx)

    def assertBlockConsensusValid(self, block):
        self.assertTrue(block.is_block)
        if not block.parents:
            # Genesis
            return
        meta = block.get_metadata()
        if meta.voided_by is None:
            parent = block.get_block_parent()
            parent_meta = parent.get_metadata()
            self.assertIsNone(parent_meta.voided_by)

    def assertTransactionConsensusValid(self, tx):
        self.assertFalse(tx.is_block)
        meta = tx.get_metadata()
        if meta.voided_by and tx.hash in meta.voided_by:
            # If a transaction voids itself, then it must have at
            # least one conflict.
            self.assertTrue(meta.conflict_with)

        for txin in tx.inputs:
            spent_tx = tx.get_spent_tx(txin)
            spent_meta = spent_tx.get_metadata()

            if spent_meta.voided_by is not None:
                self.assertIsNotNone(meta.voided_by)
                self.assertTrue(spent_meta.voided_by.issubset(meta.voided_by))

        for parent in tx.get_parents():
            parent_meta = parent.get_metadata()
            if parent_meta.voided_by is not None:
                self.assertIsNotNone(meta.voided_by)
                self.assertTrue(parent_meta.voided_by.issubset(meta.voided_by))

    def clean_tmpdirs(self):
        for tmpdir in self.tmpdirs:
            shutil.rmtree(tmpdir)

    def clean_pending(self, required_to_quiesce=True):
        """
        This handy method cleans all pending tasks from the reactor.

        When writing a unit test, consider the following question:

            Is the code that you are testing required to release control once it
            has done its job, so that it is impossible for it to later come around
            (with a delayed reactor task) and do anything further?

        If so, then trial will usefully test that for you -- if the code under
        test leaves any pending tasks on the reactor then trial will fail it.

        On the other hand, some code is *not* required to release control -- some
        code is allowed to continuously maintain control by rescheduling reactor
        tasks in order to do ongoing work.  Trial will incorrectly require that
        code to clean up all its tasks from the reactor.

        Most people think that such code should be amended to have an optional
        "shutdown" operation that releases all control, but on the contrary it is
        good design for some code to *not* have a shutdown operation, but instead
        to have a "crash-only" design in which it recovers from crash on startup.

        If the code under test is of the "long-running" kind, which is *not*
        required to shutdown cleanly in order to pass tests, then you can simply
        call testutil.clean_pending() at the end of the unit test, and trial will
        be satisfied.

        Copy from: https://github.com/zooko/pyutil/blob/master/pyutil/testutil.py#L68
        """
        pending = reactor.getDelayedCalls()
        active = bool(pending)
        for p in pending:
            if p.active():
                p.cancel()
            else:
                print('WEIRDNESS! pending timed call not active!')
        if required_to_quiesce and active:
            self.fail(
                'Reactor was still active when it was required to be quiescent.'
            )

    def get_address(self, index: int) -> Optional[str]:
        """ Generate a fixed HD Wallet and return an address
        """
        from hathor.wallet import HDWallet
        words = (
            'bind daring above film health blush during tiny neck slight clown salmon '
            'wine brown good setup later omit jaguar tourist rescue flip pet salute'
        )

        hd = HDWallet(words=words)
        hd._manually_initialize()

        if index >= hd.gap_limit:
            return None

        return list(hd.keys.keys())[index]
示例#27
0
class TimeoutTests(TestCase):
    """
    Tests for :py:func:`timeout`.
    """
    def setUp(self):
        """
        Initialize testing helper variables.
        """
        super(TimeoutTests, self).setUp()
        self.deferred = Deferred()
        self.timeout = 10.0
        self.clock = Clock()

    def test_doesnt_time_out_early(self):
        """
        A deferred that has not fired by some short while prior to the timeout
        interval is not made to fire with a timeout failure.
        """
        deferred = timeout(self.clock, self.deferred, self.timeout)
        self.clock.advance(self.timeout - 1.0)
        self.assertNoResult(deferred)

    def test_times_out(self):
        """
        A deferred that does not fire within the timeout interval is made to
        fire with ``CancelledError`` once the timeout interval elapses.
        """
        deferred = timeout(self.clock, self.deferred, self.timeout)
        self.clock.advance(self.timeout)
        self.failureResultOf(deferred, CancelledError)

    def test_times_out_with_reason(self):
        """
        If a custom reason is passed to ``timeout`` and the Deferred does not
        fire within the timeout interval, it is made to fire with the custom
        reason once the timeout interval elapses.
        """
        reason = CustomException(self.id())
        deferred = timeout(self.clock, self.deferred, self.timeout, reason)
        self.clock.advance(self.timeout)
        self.assertEqual(
            reason,
            self.failureResultOf(deferred, CustomException).value,
        )

    def test_doesnt_time_out(self):
        """
        A deferred that fires before the timeout is not cancelled by the
        timeout.
        """
        deferred = timeout(self.clock, self.deferred, self.timeout)
        self.clock.advance(self.timeout - 1.0)
        self.deferred.callback('Success')
        self.assertEqual(self.successResultOf(deferred), 'Success')

    def test_doesnt_time_out_failure(self):
        """
        A Deferred that fails before the timeout is not cancelled by the
        timeout.
        """
        deferred = timeout(self.clock, self.deferred, self.timeout)
        self.clock.advance(self.timeout - 1.0)
        self.deferred.errback(CustomException(self.id()))
        self.failureResultOf(deferred, CustomException)

    def test_doesnt_time_out_failure_custom_reason(self):
        """
        A Deferred that fails before the timeout is not cancelled by the
        timeout.
        """
        deferred = timeout(self.clock, self.deferred, self.timeout,
                           ValueError("This should not appear."))
        self.clock.advance(self.timeout - 1.0)
        self.deferred.errback(CustomException(self.id()))
        self.failureResultOf(deferred, CustomException)

    def test_advancing_after_success(self):
        """
        A Deferred that fires before the timeout continues to succeed after the
        timeout has elapsed.
        """
        deferred = succeed('Success')
        timeout(self.clock, deferred, self.timeout)
        self.clock.advance(self.timeout)
        self.assertEqual(self.successResultOf(deferred), 'Success')

    def test_advancing_after_failure(self):
        """
        A Deferred that fires with a failure before the timeout continues to
        fail after the timeout has elapsed.
        """
        deferred = fail(CustomException(self.id()))
        timeout(self.clock, deferred, self.timeout)
        self.clock.advance(self.timeout)
        self.failureResultOf(deferred, CustomException)

    def test_timeout_cleaned_up_on_success(self):
        """
        If the deferred is successfully completed before the timeout, the
        timeout is not still pending on the reactor.
        """
        timeout(self.clock, self.deferred, self.timeout)
        self.deferred.callback('Success')
        self.assertEqual(self.clock.getDelayedCalls(), [])

    def test_timeout_cleaned_up_on_failure(self):
        """
        If the deferred is failed before the timeout, the timeout is not still
        pending on the reactor.
        """
        timeout(self.clock, self.deferred, self.timeout)
        self.deferred.errback(CustomException(self.id()))
        # We need to handle the errback so that Trial and/or testtools don't
        # fail with unhandled errors.
        self.addCleanup(self.deferred.addErrback, lambda _: None)
        self.assertEqual(self.clock.getDelayedCalls(), [])
class TransportTestCase(object):
    """PT client and server connect over a string transport.

    We bypass the communication between client and server and intercept the
    messages sent over the string transport.
    """
    def setUp(self):
        """Set the reactor's callLater to our clock's callLater function
        and build the protocols.
        """
        self.clock = Clock()
        reactor.callLater = self.clock.callLater
        self.dump = []
        self.proto_client = self._build_protocol(const.CLIENT)
        self.proto_server = self._build_protocol(const.SERVER)
        self.pt_client = self.proto_client.circuit.transport
        self.pt_server = self.proto_server.circuit.transport
        self._proxy(self.proto_client, self.proto_server)
        self._bypass_connection(self.proto_client, self.proto_server)

    def _proxy(self, client, server):
        """Proxy the communication between client and server and dump
        intercepted data into a dictionary.
        """
        def decorate_intercept(end):
            old_rcv_f = end.circuit.transport.receivedDownstream
            old_snd_f = end.circuit.transport.sendDownstream

            def intercept(old_f, direction):
                def new_f(data):
                    msgs = old_f(data)
                    end.history[direction].append((self.clock.seconds(), msgs))

                return new_f

            end.circuit.transport.receivedDownstream = intercept(
                old_rcv_f, 'rcv')
            end.circuit.transport.sendDownstream = intercept(old_snd_f, 'snd')

        decorate_intercept(client)
        decorate_intercept(server)

    def _bypass_connection(self, client, server):
        """Instead of requiring TCP connections between client and server
        transports, we directly pass the data written from one end to the
        received function at the other.
        """
        def curry_bypass_connection(up, down, direction):
            old_write = up.circuit.downstream.write

            def write(data):
                old_write(data)
                down.dataReceived(data)
                self.dump.append((self.clock.seconds(), direction * len(data)))

            return write

        client.circuit.downstream.write = curry_bypass_connection(
            client, server, const.OUT)
        server.circuit.downstream.write = curry_bypass_connection(
            server, client, const.IN)

    def _build_protocol(self, mode):
        """Build client and server protocols for an end point."""
        addr_tuple = (HOST, str(PORT))
        address = IPv4Address('TCP', HOST, PORT)
        pt_config = self._build_transport_configuration(mode)
        transport_class = self._configure_transport_class(mode, pt_config)
        f_server = net.StaticDestinationServerFactory(addr_tuple, mode,
                                                      transport_class,
                                                      pt_config)
        protocol_server = self._set_protocol(f_server, address)
        f_client = net.StaticDestinationClientFactory(protocol_server.circuit,
                                                      const.CLIENT)
        protocol_client = self._set_protocol(f_client, address)
        if mode == const.CLIENT:
            return protocol_client
        elif mode == const.SERVER:
            return protocol_server
        else:
            raise ValueError("Transport mode '%s' not recognized." % mode)

    def _set_protocol(self, factory, address):
        """Make protocol connection with a Twisted string transport."""
        protocol = factory.buildProtocol(address)
        protocol.makeConnection(proto_helpers.StringTransport())
        protocol.history = {'rcv': [], 'snd': []}
        return protocol

    def _build_transport_configuration(self, mode):
        """Configure transport as a managed transport."""
        pt_config = transport_config.TransportConfig()
        pt_config.setStateLocation(const.TEMP_DIR)
        pt_config.setObfsproxyMode("managed")
        pt_config.setListenerMode(mode)
        return pt_config

    def _configure_transport_class(self, mode, pt_config):
        """Use the global arguments to configure the trasnport."""
        transport_args = [mode, ADDR, "--dest=%s" % ADDR] + self.args
        sys.argv = [
            sys.argv[0], "--log-file",
            join(const.TEMP_DIR, "%s.log" % mode), "--log-min-severity",
            "debug"
        ]
        sys.argv.append("wfpad")  # use wfpad transport
        sys.argv += transport_args
        parser = set_up_cli_parsing()
        consider_cli_args(parser.parse_args())
        transport_class = get_transport_class(self.transport, mode)
        transport_class.setup(pt_config)
        p = ArgumentParser()
        transport_class.register_external_mode_cli(p)
        args = p.parse_args(transport_args)
        transport_class.validate_external_mode_cli(args)
        return transport_class

    def _lose_protocol_connection(self, protocol):
        """Disconnect client and server transports."""
        protocol.circuit.upstream.transport.loseConnection()
        protocol.circuit.downstream.transport.loseConnection()

    def advance_next_delayed_call(self):
        """Advance clock to first delayed call in reactor."""
        first_delayed_call = self.clock.getDelayedCalls()[0]
        self.clock.advance(first_delayed_call.getTime() - self.clock.seconds())

    def is_timeout(self, call):
        """Check if the call has actually timed out."""
        return isinstance(call.args[0], TimeoutError)

    def advance_delayed_calls(self, max_dcalls=NUM_DCALLS, no_timeout=True):
        """Advance clock to the point all delayed calls up to that moment have
        been called.
        """
        i, timeouts = 0, []
        while len(self.clock.getDelayedCalls()) > 0 and i < max_dcalls:
            i += 1
            dcall = self.clock.getDelayedCalls()[0]
            if no_timeout:
                if len(dcall.args) > 0 and self.is_timeout(dcall):
                    if dcall in timeouts:
                        break
                    self._queue_first_call()
                    timeouts.append(dcall)
                    continue
            self.advance_next_delayed_call()

    def _queue_first_call(self, delay=10000.0):
        """Put the first delayed call to the last position."""
        timeout = self.clock.calls.pop(0)
        timeout.time = delay
        self.clock.calls.append(timeout)

    def tearDown(self):
        """Close connections and advance all delayed calls."""
        # Need to wait a bit beacuse obfsproxy network.Circuit.circuitCompleted
        # defers 0.02s a dummy call to dataReceived to flush connection.
        self._lose_protocol_connection(self.proto_client)
        self._lose_protocol_connection(self.proto_server)
        self.advance_delayed_calls()
示例#29
0
class APITestsMixin(APIAssertionsMixin):
    """
    Helpers for writing tests for the Docker Volume Plugin API.
    """

    NODE_A = uuid4()
    NODE_B = uuid4()

    def initialize(self):
        """
        Create initial objects for the ``VolumePlugin``.
        """
        self.volume_plugin_reactor = Clock()
        self.flocker_client = SimpleCountingProxy(FakeFlockerClient())
        # The conditional_create operation used by the plugin relies on
        # the passage of time... so make sure time passes! We still use a
        # fake clock since some tests want to skip ahead.
        self.looping = LoopingCall(lambda: self.volume_plugin_reactor.advance(0.001))
        self.looping.start(0.001)
        self.addCleanup(self.looping.stop)

    def test_pluginactivate(self):
        """
        ``/Plugins.Activate`` indicates the plugin is a volume driver.
        """
        # Docker 1.8, at least, sends "null" as the body. Our test
        # infrastructure has the opposite bug so just going to send some
        # other garbage as the body (12345) to demonstrate that it's
        # ignored as per the spec which declares no body.
        return self.assertResult(b"POST", b"/Plugin.Activate", 12345, OK, {u"Implements": [u"VolumeDriver"]})

    def test_remove(self):
        """
        ``/VolumeDriver.Remove`` returns a successful result.
        """
        return self.assertResult(b"POST", b"/VolumeDriver.Remove", {u"Name": u"vol"}, OK, {u"Err": u""})

    def test_unmount(self):
        """
        ``/VolumeDriver.Unmount`` returns a successful result.
        """
        unmount_id = "".join(random.choice("0123456789abcdef") for n in xrange(64))
        return self.assertResult(
            b"POST", b"/VolumeDriver.Unmount", {u"Name": u"vol", u"ID": unicode(unmount_id)}, OK, {u"Err": u""}
        )

    def test_unmount_no_id(self):
        """
        ``/VolumeDriver.Unmount`` returns a successful result.

        No ID for backward compatability with Docker < 1.12
        """
        return self.assertResult(b"POST", b"/VolumeDriver.Unmount", {u"Name": u"vol"}, OK, {u"Err": u""})

    def test_create_with_profile(self):
        """
        Calling the ``/VolumerDriver.Create`` API with an ``Opts`` value
        of "profile=[gold,silver,bronze] in the request body JSON create a
        volume with a given name with [gold,silver,bronze] profile.
        """
        profile = sampled_from(["gold", "silver", "bronze"]).example()
        name = random_name(self)
        d = self.assertResult(
            b"POST", b"/VolumeDriver.Create", {u"Name": name, "Opts": {u"profile": profile}}, OK, {u"Err": u""}
        )
        d.addCallback(lambda _: self.flocker_client.list_datasets_configuration())
        d.addCallback(list)
        d.addCallback(
            lambda result: self.assertItemsEqual(
                result,
                [
                    Dataset(
                        dataset_id=result[0].dataset_id,
                        primary=self.NODE_A,
                        maximum_size=int(DEFAULT_SIZE.to_Byte()),
                        metadata={NAME_FIELD: name, u"clusterhq:flocker:profile": unicode(profile)},
                    )
                ],
            )
        )
        return d

    def test_create_with_size(self):
        """
        Calling the ``/VolumerDriver.Create`` API with an ``Opts`` value
        of "size=<somesize> in the request body JSON create a volume
        with a given name and random size between 1-100G
        """
        name = random_name(self)
        size = integers(min_value=1, max_value=75).example()
        expression = volume_expression.example()
        size_opt = "".join(str(size)) + expression
        d = self.assertResult(
            b"POST", b"/VolumeDriver.Create", {u"Name": name, "Opts": {u"size": size_opt}}, OK, {u"Err": u""}
        )

        real_size = int(parse_num(size_opt).to_Byte())
        d.addCallback(lambda _: self.flocker_client.list_datasets_configuration())
        d.addCallback(list)
        d.addCallback(
            lambda result: self.assertItemsEqual(
                result,
                [
                    Dataset(
                        dataset_id=result[0].dataset_id,
                        primary=self.NODE_A,
                        maximum_size=real_size,
                        metadata={NAME_FIELD: name, u"maximum_size": unicode(real_size)},
                    )
                ],
            )
        )
        return d

    @given(expr=volume_expression, size=integers(min_value=75, max_value=100))
    def test_parsenum_size(self, expr, size):
        """
        Send different forms of size expressions
        to ``parse_num``, we expect G(Gigabyte) size results.

        :param expr str: A string representing the size expression
        :param size int: A string representing the volume size
        """
        expected_size = int(GiB(size).to_Byte())
        return self.assertEqual(expected_size, int(parse_num(str(size) + expr).to_Byte()))

    @given(expr=sampled_from(["KB", "MB", "GB", "TB", ""]), size=integers(min_value=1, max_value=100))
    def test_parsenum_all_sizes(self, expr, size):
        """
        Send standard size expressions to ``parse_num`` in
        many sizes, we expect to get correct size results.

        :param expr str: A string representing the size expression
        :param size int: A string representing the volume size
        """
        if expr is "KB":
            expected_size = int(KiB(size).to_Byte())
        elif expr is "MB":
            expected_size = int(MiB(size).to_Byte())
        elif expr is "GB":
            expected_size = int(GiB(size).to_Byte())
        elif expr is "TB":
            expected_size = int(TiB(size).to_Byte())
        else:
            expected_size = int(Byte(size).to_Byte())
        return self.assertEqual(expected_size, int(parse_num(str(size) + expr).to_Byte()))

    @given(size=sampled_from([u"foo10Gb", u"10bar10", "10foogib", "10Gfoo", "GIB", "bar10foo"]))
    def test_parsenum_bad_size(self, size):
        """
        Send unacceptable size expressions, upon error
        users should expect to receive Flocker's ``DEFAULT_SIZE``

        :param size str: A string representing the bad volume size
        """
        return self.assertEqual(int(DEFAULT_SIZE.to_Byte()), int(parse_num(size).to_Byte()))

    def create(self, name):
        """
        Call the ``/VolumeDriver.Create`` API to create a volume with the
        given name.

        :param unicode name: The name of the volume to create.

        :return: ``Deferred`` that fires when the volume that was created.
        """
        return self.assertResult(b"POST", b"/VolumeDriver.Create", {u"Name": name}, OK, {u"Err": u""})

    def test_create_creates(self):
        """
        ``/VolumeDriver.Create`` creates a new dataset in the configuration.
        """
        name = u"myvol"
        d = self.create(name)
        d.addCallback(lambda _: self.flocker_client.list_datasets_configuration())
        d.addCallback(list)
        d.addCallback(
            lambda result: self.assertItemsEqual(
                result,
                [
                    Dataset(
                        dataset_id=result[0].dataset_id,
                        primary=self.NODE_A,
                        maximum_size=int(DEFAULT_SIZE.to_Byte()),
                        metadata={NAME_FIELD: name},
                    )
                ],
            )
        )
        return d

    def test_create_duplicate_name(self):
        """
        If a dataset with the given name already exists,
        ``/VolumeDriver.Create`` succeeds without create a new volume.
        """
        name = u"thename"
        # Create a dataset out-of-band with matching name but non-matching
        # dataset ID:
        d = self.flocker_client.create_dataset(self.NODE_A, int(DEFAULT_SIZE.to_Byte()), metadata={NAME_FIELD: name})
        d.addCallback(lambda _: self.create(name))
        d.addCallback(lambda _: self.flocker_client.list_datasets_configuration())
        d.addCallback(lambda results: self.assertEqual(len(list(results)), 1))
        return d

    def test_create_duplicate_name_race_condition(self):
        """
        If a dataset with the given name is created while the
        ``/VolumeDriver.Create`` call is in flight, the call does not
        result in an error.
        """
        name = u"thename"

        # Create a dataset out-of-band with matching dataset ID and name
        # which the docker plugin won't be able to see.
        def create_after_list():
            # Clean up the patched version:
            del self.flocker_client.list_datasets_configuration
            # But first time we're called, we create dataset and lie about
            # its existence:
            d = self.flocker_client.create_dataset(
                self.NODE_A, int(DEFAULT_SIZE.to_Byte()), metadata={NAME_FIELD: name}
            )
            d.addCallback(lambda _: DatasetsConfiguration(tag=u"1234", datasets={}))
            return d

        self.flocker_client.list_datasets_configuration = create_after_list

        return self.create(name)

    def _flush_volume_plugin_reactor_on_endpoint_render(self):
        """
        This method patches ``self.app`` so that after any endpoint is
        rendered, the reactor used by the volume plugin is advanced repeatedly
        until there are no more ``delayedCalls`` pending on the reactor.
        """
        real_execute_endpoint = self.app.execute_endpoint

        def patched_execute_endpoint(*args, **kwargs):
            val = real_execute_endpoint(*args, **kwargs)
            while self.volume_plugin_reactor.getDelayedCalls():
                pending_calls = self.volume_plugin_reactor.getDelayedCalls()
                next_expiration = min(t.getTime() for t in pending_calls)
                now = self.volume_plugin_reactor.seconds()
                self.volume_plugin_reactor.advance(max(0.0, next_expiration - now))
            return val

        self.patch(self.app, "execute_endpoint", patched_execute_endpoint)

    def test_mount(self):
        """
        ``/VolumeDriver.Mount`` sets the primary of the dataset with matching
        name to the current node and then waits for the dataset to
        actually arrive.
        """
        name = u"myvol"
        dataset_id = uuid4()
        mount_id = "".join(random.choice("0123456789abcdef") for n in xrange(64))

        # Create dataset on a different node:
        d = self.flocker_client.create_dataset(
            self.NODE_B, int(DEFAULT_SIZE.to_Byte()), metadata={NAME_FIELD: name}, dataset_id=dataset_id
        )

        self._flush_volume_plugin_reactor_on_endpoint_render()

        # Pretend that it takes 5 seconds for the dataset to get established on
        # Node A.
        self.volume_plugin_reactor.callLater(5.0, self.flocker_client.synchronize_state)

        d.addCallback(
            lambda _: self.assertResult(
                b"POST",
                b"/VolumeDriver.Mount",
                {u"Name": name, u"ID": unicode(mount_id)},
                OK,
                {u"Err": u"", u"Mountpoint": u"/flocker/{}".format(dataset_id)},
            )
        )
        d.addCallback(lambda _: self.flocker_client.list_datasets_state())

        def final_assertions(datasets):
            self.assertEqual([self.NODE_A], [d.primary for d in datasets if d.dataset_id == dataset_id])
            # There should be less than 20 calls to list_datasets_state over
            # the course of 5 seconds.
            self.assertLess(self.flocker_client.num_calls("list_datasets_state"), 20)

        d.addCallback(final_assertions)

        return d

    def test_mount_no_id(self):
        """
        ``/VolumeDriver.Mount`` sets the primary of the dataset with matching
        name to the current node and then waits for the dataset to
        actually arrive.

        No ID for backward compatability with Docker < 1.12
        """
        name = u"myvol"
        dataset_id = uuid4()

        # Create dataset on a different node:
        d = self.flocker_client.create_dataset(
            self.NODE_B, int(DEFAULT_SIZE.to_Byte()), metadata={NAME_FIELD: name}, dataset_id=dataset_id
        )

        self._flush_volume_plugin_reactor_on_endpoint_render()

        # Pretend that it takes 5 seconds for the dataset to get established on
        # Node A.
        self.volume_plugin_reactor.callLater(5.0, self.flocker_client.synchronize_state)

        d.addCallback(
            lambda _: self.assertResult(
                b"POST",
                b"/VolumeDriver.Mount",
                {u"Name": name},
                OK,
                {u"Err": u"", u"Mountpoint": u"/flocker/{}".format(dataset_id)},
            )
        )
        d.addCallback(lambda _: self.flocker_client.list_datasets_state())

        def final_assertions(datasets):
            self.assertEqual([self.NODE_A], [d.primary for d in datasets if d.dataset_id == dataset_id])
            # There should be less than 20 calls to list_datasets_state over
            # the course of 5 seconds.
            self.assertLess(self.flocker_client.num_calls("list_datasets_state"), 20)

        d.addCallback(final_assertions)

        return d

    def test_mount_timeout(self):
        """
        ``/VolumeDriver.Mount`` sets the primary of the dataset with matching
        name to the current node and then waits for the dataset to
        actually arrive. If it does not arrive within 120 seconds, then it
        returns an error up to docker.
        """
        name = u"myvol"
        dataset_id = uuid4()
        mount_id = "".join(random.choice("0123456789abcdef") for n in xrange(64))
        # Create dataset on a different node:
        d = self.flocker_client.create_dataset(
            self.NODE_B, int(DEFAULT_SIZE.to_Byte()), metadata={NAME_FIELD: name}, dataset_id=dataset_id
        )

        self._flush_volume_plugin_reactor_on_endpoint_render()

        # Pretend that it takes 500 seconds for the dataset to get established
        # on Node A. This should be longer than the timeout.
        self.volume_plugin_reactor.callLater(500.0, self.flocker_client.synchronize_state)

        d.addCallback(
            lambda _: self.assertResult(
                b"POST",
                b"/VolumeDriver.Mount",
                {u"Name": name, u"ID": unicode(mount_id)},
                OK,
                {u"Err": u"Timed out waiting for dataset to mount.", u"Mountpoint": u""},
            )
        )
        return d

    def test_mount_already_exists(self):
        """
        ``/VolumeDriver.Mount`` sets the primary of the dataset with matching
        name to the current node and then waits for the dataset to
        actually arrive when used by the volumes that already exist and
        don't have a special dataset ID.
        """
        name = u"myvol"
        mount_id = "".join(random.choice("0123456789abcdef") for n in xrange(64))

        d = self.flocker_client.create_dataset(self.NODE_A, int(DEFAULT_SIZE.to_Byte()), metadata={NAME_FIELD: name})

        def created(dataset):
            self.flocker_client.synchronize_state()
            result = self.assertResult(
                b"POST",
                b"/VolumeDriver.Mount",
                {u"Name": name, u"ID": unicode(mount_id)},
                OK,
                {u"Err": u"", u"Mountpoint": u"/flocker/{}".format(dataset.dataset_id)},
            )
            result.addCallback(lambda _: self.flocker_client.list_datasets_state())
            result.addCallback(
                lambda ds: self.assertEqual(
                    [self.NODE_A], [d.primary for d in ds if d.dataset_id == dataset.dataset_id]
                )
            )
            return result

        d.addCallback(created)
        return d

    def test_unknown_mount(self):
        """
        ``/VolumeDriver.Mount`` returns an error when asked to mount a
        non-existent volume.
        """
        name = u"myvol"
        mount_id = "".join(random.choice("0123456789abcdef") for n in xrange(64))
        return self.assertResult(
            b"POST",
            b"/VolumeDriver.Mount",
            {u"Name": name, u"ID": unicode(mount_id)},
            OK,
            {u"Err": u"Could not find volume with given name."},
        )

    def test_path(self):
        """
        ``/VolumeDriver.Path`` returns the mount path of the given volume if
        it is currently known.
        """
        name = u"myvol"

        d = self.create(name)
        # The dataset arrives as state:
        d.addCallback(lambda _: self.flocker_client.synchronize_state())

        d.addCallback(lambda _: self.assertResponseCode(b"POST", b"/VolumeDriver.Mount", {u"Name": name}, OK))
        d.addCallback(lambda _: self.flocker_client.list_datasets_configuration())
        d.addCallback(
            lambda datasets_config: self.assertResult(
                b"POST",
                b"/VolumeDriver.Path",
                {u"Name": name},
                OK,
                {u"Err": u"", u"Mountpoint": u"/flocker/{}".format(datasets_config.datasets.keys()[0])},
            )
        )
        return d

    def test_path_existing(self):
        """
        ``/VolumeDriver.Path`` returns the mount path of the given volume if
        it is currently known, including for a dataset that was created
        not by the plugin.
        """
        name = u"myvol"

        d = self.flocker_client.create_dataset(self.NODE_A, int(DEFAULT_SIZE.to_Byte()), metadata={NAME_FIELD: name})

        def created(dataset):
            self.flocker_client.synchronize_state()
            return self.assertResult(
                b"POST",
                b"/VolumeDriver.Path",
                {u"Name": name},
                OK,
                {u"Err": u"", u"Mountpoint": u"/flocker/{}".format(dataset.dataset_id)},
            )

        d.addCallback(created)
        return d

    def test_unknown_path(self):
        """
        ``/VolumeDriver.Path`` returns an error when asked for the mount path
        of a non-existent volume.
        """
        name = u"myvol"
        return self.assertResult(
            b"POST", b"/VolumeDriver.Path", {u"Name": name}, OK, {u"Err": u"Could not find volume with given name."}
        )

    def test_non_local_path(self):
        """
        ``/VolumeDriver.Path`` returns an error when asked for the mount path
        of a volume that is not mounted locally.

        This can happen as a result of ``docker inspect`` on a container
        that has been created but is still waiting for its volume to
        arrive from another node. It seems like Docker may also call this
        after ``/VolumeDriver.Create``, so again while waiting for a
        volume to arrive.
        """
        name = u"myvol"
        dataset_id = uuid4()

        # Create dataset on node B:
        d = self.flocker_client.create_dataset(
            self.NODE_B, int(DEFAULT_SIZE.to_Byte()), metadata={NAME_FIELD: name}, dataset_id=dataset_id
        )
        d.addCallback(lambda _: self.flocker_client.synchronize_state())

        # Ask for path on node A:
        d.addCallback(
            lambda _: self.assertResult(
                b"POST",
                b"/VolumeDriver.Path",
                {u"Name": name},
                OK,
                {u"Err": "Volume not available.", u"Mountpoint": u""},
            )
        )
        return d

    @capture_logging(lambda self, logger: self.assertEqual(len(logger.flushTracebacks(CustomException)), 1))
    def test_unexpected_error_reporting(self, logger):
        """
        If an unexpected error occurs Docker gets back a useful error message.
        """

        def error():
            raise CustomException("I've made a terrible mistake")

        self.patch(self.flocker_client, "list_datasets_configuration", error)
        return self.assertResult(
            b"POST",
            b"/VolumeDriver.Path",
            {u"Name": u"whatever"},
            OK,
            {u"Err": "CustomException: I've made a terrible mistake"},
        )

    @capture_logging(None)
    def test_bad_request(self, logger):
        """
        If a ``BadRequest`` exception is raised it is converted to appropriate
        JSON.
        """

        def error():
            raise make_bad_request(code=423, Err=u"no good")

        self.patch(self.flocker_client, "list_datasets_configuration", error)
        return self.assertResult(b"POST", b"/VolumeDriver.Path", {u"Name": u"whatever"}, 423, {u"Err": "no good"})

    def test_unsupported_method(self):
        """
        If an unsupported method is requested the 405 Not Allowed response
        code is returned.
        """
        return self.assertResponseCode(b"BAD_METHOD", b"/VolumeDriver.Path", None, NOT_ALLOWED)

    def test_unknown_uri(self):
        """
        If an unknown URI path is requested the 404 Not Found response code is
        returned.
        """
        return self.assertResponseCode(b"BAD_METHOD", b"/xxxnotthere", None, NOT_FOUND)

    def test_empty_host(self):
        """
        If an empty host header is sent to the Docker plugin it does not blow
        up, instead operating normally. E.g. for ``Plugin.Activate`` call
        returns the ``Implements`` response.
        """
        return self.assertResult(
            b"POST",
            b"/Plugin.Activate",
            12345,
            OK,
            {u"Implements": [u"VolumeDriver"]},
            additional_headers={b"Host": [""]},
        )

    def test_get(self):
        """
        ``/VolumeDriver.Get`` returns the mount path of the given volume if
        it is currently known.
        """
        name = u"myvol"

        d = self.create(name)
        # The dataset arrives as state:
        d.addCallback(lambda _: self.flocker_client.synchronize_state())

        d.addCallback(lambda _: self.assertResponseCode(b"POST", b"/VolumeDriver.Mount", {u"Name": name}, OK))
        d.addCallback(lambda _: self.flocker_client.list_datasets_configuration())
        d.addCallback(
            lambda datasets_config: self.assertResult(
                b"POST",
                b"/VolumeDriver.Get",
                {u"Name": name},
                OK,
                {
                    u"Err": u"",
                    u"Volume": {
                        u"Name": name,
                        u"Mountpoint": u"/flocker/{}".format(datasets_config.datasets.keys()[0]),
                    },
                },
            )
        )
        return d

    def test_get_existing(self):
        """
        ``/VolumeDriver.Get`` returns the mount path of the given volume if
        it is currently known, including for a dataset that was created
        not by the plugin.
        """
        name = u"myvol"

        d = self.flocker_client.create_dataset(self.NODE_A, int(DEFAULT_SIZE.to_Byte()), metadata={NAME_FIELD: name})

        def created(dataset):
            self.flocker_client.synchronize_state()
            return self.assertResult(
                b"POST",
                b"/VolumeDriver.Get",
                {u"Name": name},
                OK,
                {u"Err": u"", u"Volume": {u"Name": name, u"Mountpoint": u"/flocker/{}".format(dataset.dataset_id)}},
            )

        d.addCallback(created)
        return d

    def test_unknown_get(self):
        """
        ``/VolumeDriver.Get`` returns an error when asked for the mount path
        of a non-existent volume.
        """
        name = u"myvol"
        return self.assertResult(
            b"POST", b"/VolumeDriver.Get", {u"Name": name}, OK, {u"Err": u"Could not find volume with given name."}
        )

    def test_non_local_get(self):
        """
        ``/VolumeDriver.Get`` returns an empty mount point when asked about a
        volume that is not mounted locally.
        """
        name = u"myvol"
        dataset_id = uuid4()

        # Create dataset on node B:
        d = self.flocker_client.create_dataset(
            self.NODE_B, int(DEFAULT_SIZE.to_Byte()), metadata={NAME_FIELD: name}, dataset_id=dataset_id
        )
        d.addCallback(lambda _: self.flocker_client.synchronize_state())

        # Ask for path on node A:
        d.addCallback(
            lambda _: self.assertResult(
                b"POST",
                b"/VolumeDriver.Get",
                {u"Name": name},
                OK,
                {u"Err": u"", u"Volume": {u"Name": name, u"Mountpoint": u""}},
            )
        )
        return d

    def test_list(self):
        """
        ``/VolumeDriver.List`` returns the mount path of the given volume if
        it is currently known and an empty mount point for non-local
        volumes.
        """
        name = u"myvol"
        remote_name = u"myvol3"

        d = gatherResults(
            [
                self.flocker_client.create_dataset(
                    self.NODE_A, int(DEFAULT_SIZE.to_Byte()), metadata={NAME_FIELD: name}
                ),
                self.flocker_client.create_dataset(
                    self.NODE_B, int(DEFAULT_SIZE.to_Byte()), metadata={NAME_FIELD: remote_name}
                ),
            ]
        )

        # The datasets arrive as state:
        d.addCallback(lambda _: self.flocker_client.synchronize_state())
        d.addCallback(lambda _: self.flocker_client.list_datasets_configuration())
        d.addCallback(
            lambda datasets_config: self.assertResult(
                b"POST",
                b"/VolumeDriver.List",
                {},
                OK,
                {
                    u"Err": u"",
                    u"Volumes": sorted(
                        [
                            {
                                u"Name": name,
                                u"Mountpoint": u"/flocker/{}".format(
                                    [
                                        key
                                        for (key, value) in datasets_config.datasets.items()
                                        if value.metadata["name"] == name
                                    ][0]
                                ),
                            },
                            {u"Name": remote_name, u"Mountpoint": u""},
                        ]
                    ),
                },
            )
        )
        return d

    def test_list_no_metadata_name(self):
        """
        ``/VolumeDriver.List`` omits volumes that don't have a metadata field
        for their name.
        """
        d = self.flocker_client.create_dataset(self.NODE_A, int(DEFAULT_SIZE.to_Byte()), metadata={})
        d.addCallback(
            lambda _: self.assertResult(b"POST", b"/VolumeDriver.List", {}, OK, {u"Err": u"", u"Volumes": []})
        )
        return d
示例#30
0
class TestPersistenceFileGlue(unittest.TestCase):
    def setUp(self):
        self.__clock = Clock()
        self.__temp_dir = tempfile.mkdtemp(prefix='shinysdr_test_persistence_tmp')
        self.__state_name = os.path.join(self.__temp_dir, 'state')
        self.__reset()
    
    def tearDown(self):
        self.assertFalse(self.__clock.getDelayedCalls())
        shutil.rmtree(self.__temp_dir)
    
    def __reset(self):
        """Recreate the object for write-then-read tests."""
        self.__root = ValueAndBlockSpecimen(value='initial')
    
    def __start(self,
            get_defaults=lambda _: {'value': 'default'},
            **kwargs):
        return PersistenceFileGlue(
            reactor=self.__clock,
            root_object=self.__root,
            filename=self.__state_name,
            get_defaults=get_defaults,
            **kwargs)
    
    def test_no_defaults(self):
        self.__start(get_defaults=lambda _: {})
        # It would be surprising if this assertion failed; this test is mainly just to test the initialization succeeds
        self.assertEqual(self.__root.get_value(), 'initial')
    
    def test_defaults(self):
        self.__start()
        self.assertEqual(self.__root.get_value(), 'default')

    def test_no_persistence(self):
        self.__state_name = None
        self.__start()
        self.assertEqual(self.__root.get_value(), 'default')

    def test_persistence(self):
        """Test that state persists."""
        pfg = self.__start()
        self.__root.set_value('set')
        advance_until(self.__clock, pfg.sync(), limit=2)
        self.__reset()
        self.__start()
        self.assertEqual(self.__root.get_value(), 'set')  # check persistence
    
    def test_delay_is_present(self):
        """Test that persistence isn't immediate."""
        pfg = self.__start()
        self.__root.set_value('set')
        self.__reset()
        self.__start()
        self.assertEqual(self.__root.get_value(), 'default')  # change not persisted
        advance_until(self.__clock, pfg.sync(), limit=2)  # clean up clock for tearDown check
    
    def test_broken_state_recovery(self):
        pfg = self.__start()
        self.__root.set_value(ObjectWhichCannotBePersisted())
        try:
            advance_until(self.__clock, pfg.sync(), limit=2)
        except TypeError:  # expected error
            pass
        self.__reset()
        self.__start()
        # now we should be back to the default value
        self.assertEqual(self.__root.get_value(), 'default')
    
    def test_unparseable_file_recovery(self):
        with open(self.__state_name, 'w'):
            pass  # write empty file
        self.__start(_suppress_error_for_test=True)
        self.assertEqual(self.__root.get_value(), 'default')