示例#1
0
    def transaction(self, compare, success=None, failure=None):
        """
        Perform a transaction.

        Example usage:

        .. code-block:: python

            etcd.transaction(
                compare=[
                    etcd.transactions.value('/doot/testing') == 'doot',
                    etcd.transactions.version('/doot/testing') > 0,
                ],
                success=[
                    etcd.transactions.put('/doot/testing', 'success'),
                ],
                failure=[
                    etcd.transactions.put('/doot/testing', 'failure'),
                ]
            )

        :param compare: A list of comparisons to make
        :param success: A list of operations to perform if all the comparisons
                        are true
        :param failure: A list of operations to perform if any of the
                        comparisons are false
        :return: A tuple of (operation status, responses)
        """
        compare = [c.build_message() for c in compare]

        success_ops = self._ops_to_requests(success)
        failure_ops = self._ops_to_requests(failure)

        transaction_request = etcdrpc.TxnRequest(compare=compare,
                                                 success=success_ops,
                                                 failure=failure_ops)
        txn_response = self.kvstub.Txn(
            transaction_request,
            self.timeout,
            credentials=self.call_credentials,
            metadata=self.metadata
        )

        responses = []
        for response in txn_response.responses:
            response_type = response.WhichOneof('response')
            if response_type in ['response_put', 'response_delete_range',
                                 'response_txn']:
                responses.append(response)

            elif response_type == 'response_range':
                range_kvs = []
                for kv in response.response_range.kvs:
                    range_kvs.append((kv.value,
                                      KVMetadata(kv, txn_response.header)))

                responses.append(range_kvs)

        return txn_response.succeeded, responses
示例#2
0
    def transaction(self, compare, success=None, failure=None):
        """
        Perform a transaction.

        Example usage:

        .. code-block:: python

            etcd.transaction(
                compare=[
                    etcd.transactions.value('/doot/testing') == 'doot',
                    etcd.transactions.version('/doot/testing') > 0,
                ],
                success=[
                    etcd.transactions.put('/doot/testing', 'success'),
                ],
                failure=[
                    etcd.transactions.put('/doot/testing', 'failure'),
                ]
            )

        :param compare: A list of comparisons to make
        :param success: A list of operations to perform if all the comparisons
                        are true
        :param failure: A list of operations to perform if any of the
                        comparisons are false
        """
        compare = [c.build_message() for c in compare]

        success_ops = self._ops_to_requests(success)
        failure_ops = self._ops_to_requests(failure)

        transaction_request = etcdrpc.TxnRequest(compare=compare,
                                                 success=success_ops,
                                                 failure=failure_ops)
        txn_response = self.kvstub.Txn(transaction_request)

        responses = []
        for response in txn_response.responses:
            response_type = response.WhichOneof('response')
            if response_type == 'response_put':
                responses.append(None)

            elif response_type == 'response_range':
                range_kvs = []
                for kv in response.response_range.kvs:
                    range_kvs.append((kv.key, kv.value))

                responses.append(range_kvs)

        return txn_response.succeeded, responses
示例#3
0
    def _ops_to_requests(self, ops):
        """
        Return a list of grpc requests.

        Returns list from an input list of etcd3.transactions.{Put, Get,
        Delete, Txn} objects.
        """
        request_ops = []
        for op in ops:
            if isinstance(op, transactions.Put):
                request = self._build_put_request(op.key, op.value,
                                                  op.lease, op.prev_kv)
                request_op = etcdrpc.RequestOp(request_put=request)
                request_ops.append(request_op)

            elif isinstance(op, transactions.Get):
                request = self._build_get_range_request(op.key, op.range_end)
                request_op = etcdrpc.RequestOp(request_range=request)
                request_ops.append(request_op)

            elif isinstance(op, transactions.Delete):
                request = self._build_delete_request(op.key, op.range_end,
                                                     op.prev_kv)
                request_op = etcdrpc.RequestOp(request_delete_range=request)
                request_ops.append(request_op)

            elif isinstance(op, transactions.Txn):
                compare = [c.build_message() for c in op.compare]
                success_ops = self._ops_to_requests(op.success)
                failure_ops = self._ops_to_requests(op.failure)
                request = etcdrpc.TxnRequest(compare=compare,
                                             success=success_ops,
                                             failure=failure_ops)
                request_op = etcdrpc.RequestOp(request_txn=request)
                request_ops.append(request_op)

            else:
                raise Exception(
                    'Unknown request class {}'.format(op.__class__))
        return request_ops
示例#4
0
    def push(self,
             kvs: List[Dict[str, any]],
             ks_delete: List[str] = None,
             ttl: int = None) -> bool:
        """
        Method to submit a list of key-value pairs and delete a list of keys from the server as a single transaction
        :param kvs: List of KV pair
        :param ks_delete: List of keys to delete before the push of the new ones. Note that each key is read as a folder
        :param ttl: time to leave of the keys pushed, once expired the keys will be deleted
        :return: True if successful
        """
        logger.debug(f"Calling push...")
        logger.debug(f"Preparing the transaction statement")
        ops = []

        # check if we need to request a lease for the ttl
        if ttl:
            try:
                lease = self._lease(ttl)
            except EngineException:
                raise EngineException("Not able to push keys")

        # first delete the keys requested
        if ks_delete is not None and len(ks_delete) != 0:
            for kd in ks_delete:
                # every key is deleted with prefix=True
                range_end = self._incr_last_byte(kd)
                delete = self._server._build_delete_request(kd, range_end)
                request_op = etcdrpc.RequestOp(request_delete_range=delete)
                ops.append(request_op)

        # Prepare the transaction with a put operation for each KV pair
        for kv in kvs:
            k = kv["key"]
            v = kv["value"]
            put = self._server._build_put_request(k, v)
            if ttl:
                put.lease = lease
            request_op = etcdrpc.RequestOp(request_put=put)
            ops.append(request_op)

        transaction_request = etcdrpc.TxnRequest(success=ops)

        # commit transaction
        logger.debug(f"Committing the transaction statement: {ops}")
        try_again = True
        while try_again:
            try:
                try_again = False
                txn_response = self._server.kvstub.Txn(
                    transaction_request,
                    self._server.timeout,
                    credentials=self._server.call_credentials,
                    metadata=self._server.metadata)
            except grpc._channel._InactiveRpcError as e:
                if e._state.code.name == "UNAUTHENTICATED":
                    # it seems that sometimes the token expires, so re-init the server and try again
                    try_again = True
                    logger.debug(f"Error {e}, trying again", exc_info=True)
                    self._initialise_server()
                else:
                    raise e
        assert txn_response.succeeded, f'Not able to execute the transaction'
        logger.debug(f"Transaction completed")
        # read the header
        if hasattr(txn_response, 'header'):
            h = txn_response.header
            rev = int(h.revision)
            logger.debug(f"New server revision {rev}")

        return True