Example #1
0
 def test_success(self):
     prepare = Prepare(id=3, key='biz', predicate='set', argument='a')
     success = Success(prepare=prepare)
     self.assertEqual(success.to_json(), {
         'status': 'SUCCESS',
         'prepare': prepare.to_json()
     })
Example #2
0
 def test_promise(self):
     promise = Promise()
     self.assertEqual(promise.prepare, None)
     prepare = Prepare(id=3, key='biz', predicate='set', argument='a')
     promise = Promise(prepare=prepare)
     self.assertEqual(promise.prepare, prepare)
     self.assertEqual(promise.to_json(), {'prepare': prepare.to_json()})
Example #3
0
    def test_prepare(self):
        prepare = Prepare(id=3, key='biz', predicate='set', argument='a')
        self.assertEqual(prepare.to_json(), {
            'id': 3,
            'key': 'biz',
            'predicate': 'set',
            'argument': 'a'
        })
        target = Prepare._id
        prepare = Prepare(key='buzz', predicate='a', argument='b')
        self.assertEqual(prepare.to_json(), {
            'id': target,
            'key': 'buzz',
            'predicate': 'a',
            'argument': 'b'
        })
        _ = yield self.assert_send_works(prepare, '/prepare')

        request = tornado.httpclient.HTTPRequest(
            body=json.dumps(prepare.to_json()),
            method='POST',
            headers={'Content-Type': 'application/json'},
            url='/prepare')
        target = Prepare.from_request(request)
        self.assertEqual(target.to_json(), prepare.to_json())
Example #4
0
 def test_accept(self):
     prepare = Prepare(id=3, key='biz', predicate='set', argument='a')
     accept = Accept(prepare=prepare)
     response = mock.Mock()
     response.body = json.dumps(accept.to_json())
     target = Accept.from_response(response)
     self.assertEqual(accept.to_json(), target.to_json())
Example #5
0
 def post(self):
     prepare = Prepare.from_request(self.request)
     in_progress = Promises.current.get(prepare.key)
     last_accepted = Learner.completed_rounds.highest_numbered(prepare.key)
     if in_progress:
         logger.info("Promise in progress already %s", in_progress)
         if in_progress.prepare.id == prepare.id:
             raise Exception("Prepare IDs match.")
         if in_progress.prepare.id > prepare.id:
             # Some replica has issued a higher promise
             # than ours. Abort.
             logger.warning("Existing promise is higher.")
             self.respond(code=400, message=in_progress)
         elif last_accepted is None or (
                 in_progress.prepare.id > last_accepted.prepare.id
         ):  # >= since we could have just learned but not removed the existing process because this is all async
             # Complete the in-progress promise first
             # Possible for the incoming promise to have the same ID as the existing one.
             logger.info("Must complete earlier promise first: %s",
                         in_progress)
             self.respond(code=200, message=in_progress)
         else:
             logger.info("New promise is higher. Issuing promise.")
             self.respond(code=200, message=Promise())
     elif last_accepted is None or prepare.id > last_accepted.prepare.id:
         logger.info("Adding a new promise for prepare %s", prepare)
         Promises.current.add(Promise(prepare=prepare))
         self.respond(code=200, message=Promise())
     else:
         logger.warning(
             "Prepare has a lower ID than the last accepted proposal")
         logger.warning("prepare: %s, last_accepted: %s", prepare,
                        last_accepted)
         self.respond(code=400, message=last_accepted)
Example #6
0
    def test_promises(self):
        prepare1 = Prepare(id=1, key='biz', predicate='pa', argument='a')
        prepare2 = Prepare(id=2, key='biz', predicate='pb', argument='b')
        prepare3 = Prepare(id=3, key='biz', predicate='pc', argument='c')
        prepare4 = Prepare(id=4, key='baz', predicate='pd', argument='d')

        promise1 = Promise(prepare=prepare1)
        promise2 = Promise(prepare=prepare2)
        promise3 = Promise(prepare=prepare3)
        promise4 = Promise(prepare=prepare4)

        promises = Promises([promise1, promise2, promise3, promise4])

        self.assertEqual(promises.highest_numbered().to_json(),
                         promise4.to_json())
        self.assertEqual(
            promises.highest_numbered(key='biz').to_json(), promise3.to_json())
Example #7
0
 def test_learn(self):
     prepare = Prepare(id=3, key='biz', predicate='set', argument='a')
     learn = Learn(prepare=prepare)
     request = tornado.httpclient.HTTPRequest(
         body=json.dumps(learn.to_json()),
         method='POST',
         headers={'Content-Type': 'application/json'},
         url='/learn')
     self.assertEqual(learn.to_json(),
                      Learn.from_request(request).to_json())
Example #8
0
 def test_to_json(self):
     prepare = Prepare(id=1, key='foo', predicate='incr', argument=1)
     phase = Phase(prepare=prepare)
     self.assertEqual(phase.to_json(), {
         'prepare': {
             'id': 1,
             'key': 'foo',
             'predicate': 'incr',
             'argument': 1
         }
     })
Example #9
0
    def test_propose(self):
        prepare = Prepare(id=3, key='biz', predicate='set', argument='a')
        propose = Propose(prepare=prepare)
        self.assert_send_works(propose, '/propose')

        request = tornado.httpclient.HTTPRequest(
            body=json.dumps(propose.to_json()),
            method='POST',
            headers={'Content-Type': 'application/json'},
            url='/propose')
        target = Propose.from_request(request)
        self.assertEqual(propose.to_json(), target.to_json())
Example #10
0
    def test_returns_lower_numbered_in_progress_promises(self):
        lower_prepare = Prepare(id=0, key='foo', predicate='set', argument='a')
        higher_prepare = Prepare(id=1,
                                 key='foo',
                                 predicate='set',
                                 argument='b')

        success = self.post('/prepare', lower_prepare.to_json())
        self.assertEqual(success.code, 200)
        self.assertEqual(
            Promise.from_response(success).to_json(), {'prepare': None})
        self.assertEqual(Promises.current.highest_numbered().to_json(),
                         {'prepare': lower_prepare.to_json()})

        failure = self.post('/prepare', higher_prepare.to_json())
        self.assertEqual(failure.code, 200)
        target = Promise.from_response(failure)
        self.assertEqual(target.to_json(),
                         {'prepare': lower_prepare.to_json()})
Example #11
0
    def test_send(self):
        prepare = Prepare(id=1, key='foo', predicate='incr', argument=1)
        phase = Phase(prepare=prepare)

        fut = tornado.concurrent.Future()
        response = mock.Mock()
        response.body = json.dumps(phase.to_json())
        fut.set_result(response)

        phase.endpoint = '/testing'

        client = mock.Mock()
        client.fetch = mock.Mock()
        client.fetch.return_value = fut
        with mock.patch('tornado.httpclient.AsyncHTTPClient',
                        return_value=client):
            responses, issued, conflicting = yield phase.send(agents.quorum())
            self.assertEqual(len(responses), len(agents.quorum()))
Example #12
0
    def test_fanout(self):
        prepare = Prepare(id=1, key='foo', predicate='incr', argument=1)
        phase = Phase(prepare=prepare)

        fut = tornado.concurrent.Future()
        response = mock.Mock()
        response.body = json.dumps(phase.to_json())
        fut.set_result(response)

        phase.endpoint = '/testing'

        client = mock.Mock()
        client.fetch = mock.Mock()
        client.fetch.return_value = fut
        with mock.patch('tornado.httpclient.AsyncHTTPClient',
                        return_value=client):
            successes = yield phase.fanout(expected=Success)
            self.assertEqual(len(successes), len(agents.all()))
Example #13
0
    def test_allows_non_conflicting_writes(self):
        prepare = Prepare(id=0, key='foo', predicate='set', argument='a')
        promise = Promise()
        prepare_success = mock.Mock()
        prepare_success.code = 200
        prepare_success.body = json.dumps(promise.to_json())
        fut = tornado.concurrent.Future()
        fut.set_result(
            tuple([[prepare_success, prepare_success],
                   [prepare_success, prepare_success], []]))

        propose_success = mock.Mock()
        propose_success.code = 200
        propose_success.body = json.dumps(Promise(prepare=prepare).to_json())
        propose_fut = tornado.concurrent.Future()
        propose_fut.set_result(
            tuple([[propose_success, propose_success],
                   [propose_success, propose_success], []]))

        learn_success = mock.Mock()
        learn_success.code = 200
        learn_success.body = ''
        learn_fut = tornado.concurrent.Future()
        learn_fut.set_result(
            tuple([[learn_success, learn_success],
                   [learn_success, learn_success], []]))
        with mock.patch('paxos.models.Prepare.send', return_value=fut):
            with mock.patch('paxos.models.Propose.send',
                            return_value=propose_fut):
                with mock.patch('paxos.models.Learn.fanout',
                                return_value=learn_fut):
                    response = self.post('/write',
                                         body={
                                             'key': 'foo',
                                             'predicate': 'set',
                                             'argument': 'a'
                                         })

        self.assertEqual(response.code, 200)
Example #14
0
    def post(self):
        """
        {
            key: <str>,
            predicate: <str>,
            argument: <str|int>
        }
        """
        successes = []
        request = json.loads(self.request.body)
        prepare = Prepare(**request)
        prepares = collections.deque([prepare])
        Promises.current.add(Promise(prepare=prepare))
        quorum = agents.quorum(excluding=options.port)
        while prepares:  # TODO: Timeout here.
            prepare = prepares.popleft()
            logging.info("Sending prepare for %s", prepare)
            send_response = yield prepare.send(quorum)
            responses, issued, conflicting = send_response
            logger.info("Got %s issued and %s conflicting", len(issued),
                        len(conflicting))
            logger.info("Response codes: %s",
                        ", ".join([str(r.code) for r in responses]))
            if conflicting:  # Issue another promise.
                logger.warning(
                    "%s was pre-empted by a higher ballot. retrying.".format(
                        prepare.id))
                prepares.append(
                    Prepare(key=prepare.key,
                            predicate=prepare.predicate,
                            argument=prepare.argument))
                continue
            elif len(issued) != len(quorum):
                raise tornado.web.HTTPError(
                    status_code=500,
                    log_message='FAILED to acquire quorum on Promise')
            promises = Promises.from_responses(responses)
            earlier_promise = promises.highest_numbered()
            if earlier_promise and earlier_promise not in Promises.current:  # Repair.
                prepares.append(prepare)
                prepare = earlier_promise.prepare

            # Now we have a promise.
            responses, issued, conflicting = yield Propose(
                prepare=prepare).send(quorum)
            if len(issued) == len(quorum):
                logger.info("Got success for propose %s. Learning...", prepare)
                successes = yield Learn(prepare).fanout(expected=Success)
            elif conflicting:
                logger.error("Conflicting promise detected. Will re-issue.")
            else:
                raise tornado.web.HTTPError(
                    status_code=500,
                    log_message='Failed to acquire quorum on Accept')

        if len(successes) == len(agents.all()):
            Promises.current.remove(prepare)
            self.respond(Success(prepare))
        else:
            logger.error("Got %s successes with a required quorum of %s",
                         len(successes), len(agents.all()))
            raise tornado.web.HTTPError(
                status_code=500,
                log_message='Failed to acquire quorum on Learn')
Example #15
0
 def test_fanout_raises_not_implemented(self):
     prepare = Prepare(id=1, key='foo', predicate='incr', argument=1)
     phase = Phase(prepare=prepare)
     with self.assertRaises(NotImplementedError):
         _ = yield phase.fanout()
Example #16
0
 def get_prepare(cls):
     return Prepare(id=1, key='foo', predicate='set', argument='a')