Skip to main content

Asynchronous client library for etcd3

Project description

Version Docs

etcd is a powerful building block in networked and distributed applications. etcd describes itself as “distributed reliable key-value store for the most critical data of a distributed system”.

Twisted is an advanced network framework to implement networked and distributed applications.

Hence the desire for a fully asynchronous etcd v3 Twisted client with broad feature support: txaioetcd.

txaioetcd currently supports these etcd v3 basic features

  • arbitrary byte strings for keys and values

  • set and get values by key

  • get values by range or prefix

  • delete value (by single key, range and prefix)

and the following advanced features

  • watch key sets with asynchronous callback

  • submit transactions (“multiact”)

  • create, refresh and revoke leases

  • associate key-values with leases

txaioetcd also plans to provide abstractions on top of the etcd3 transaction primitive, like for example:

  • global locks and sequences

  • transactional, multi-consumer-producer queues

Examples

  1. Connecting

  2. Basic Operations (CRUD)

  3. Watching keys

  4. Transactions

  5. Leases

Requirements

etcd version 3.1 or higher is required. etcd 2 will not work. etcd 3.0 also isn’t enough - at least watching keys doesn’t work over the gRPC HTTP gateway yet.

The implementation is pure Python code compatible with both Python 2 and 3, and runs perfect on PyPy.

The library currently requires Twisted, though the API was designed to allow adding asyncio support later (PRs are welcome!) with no breakage.

But other than the underlying network library, there are only small pure Python dependencies.

Installation

To install txaioetcd

pip install txaioetcd

Quick test

If you have Docker on your system, you can do a quick test. First, start a Docker container with etcd:

make docker_etcd

Then, in a second terminal:

make docker_build
make docker_test

You should see all the example programs run against the etcd daemon in the first container.

etcd

Installation

To build and install etcd 3.1

ETCD_VER=v3.1.0
DOWNLOAD_URL=https://github.com/coreos/etcd/releases/download
curl -L ${DOWNLOAD_URL}/${ETCD_VER}/etcd-${ETCD_VER}-linux-amd64.tar.gz \
    -o /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz
sudo mkdir -p /opt/etcd && sudo tar xzvf /tmp/etcd-${ETCD_VER}-linux-amd64.tar.gz \
    -C /opt/etcd --strip-components=1

To verify the installation, check the version

/opt/etcd/etcd --version

Open a console and start etcd

/opt/etcd/etcd

To scratch the etcd database

rm -rf ~/default.etcd/

Test using etcdctl

Get cluster status

ETCDCTL_API=3 /opt/etcd/etcdctl endpoint -w table status

Set a key

ETCDCTL_API=3 /opt/etcd/etcdctl put foo hello

Get a key

ETCDCTL_API=3 /opt/etcd/etcdctl get foo

Watch a key

ETCDCTL_API=3 /opt/etcd/etcdctl watch foo

Test using curl

Get cluster status

curl -L http://localhost:2379/v3alpha/maintenance/status -X POST -d '{}'

Set a key (value “hello” on key “foo” both base64 encoded):

curl -L http://localhost:2379/v3alpha/kv/put -X POST -d '{"key": "Zm9v", "value": "YmFy"}'

Get a key (“foo” base64 encoded)

curl -L http://localhost:2379/v3alpha/kv/range -X POST -d '{"key": "Zm9v"}'

Watch a key (“foo” base64 encoded)

curl -L http://localhost:2379/v3alpha/watch -X POST -d '{"create_request": {"key": "Zm9v"}}'

Usage

Example Client

Here is an example etcd3 client that retrieves the cluster status

from twisted.internet.task import react
from twisted.internet.defer import inlineCallbacks

import txaio
from txaioetcd import Client, KeySet

@inlineCallbacks
def main(reactor):
    etcd = Client(reactor, u'http://localhost:2379')

    status = yield etcd.status()
    print(status)

    # insert one of the snippets below HERE

if __name__ == '__main__':
    txaio.start_logging(level='info')
    react(main)

The following snippets demonstrate the etcd3 features supported by txaioetcd. To run the snippets, use the boilerplate above.

Setting keys

Set a value for some keys

for i in range(10):
    etcd.set('mykey{}'.format(i).encode(), b'foobar')

Note that both keys and values in etcd3 are arbitrary byte strings.

Whether you use UTF-8 encoded strings with leading slash or anything else does not matter to etcd3. Put differently, there is no semantics associated with slashes on sides of etcd3 whatsoever and slash semantics - if any - is fully up to an application.

Getting keys

Get a value by key from etcd

result = yield etcd.get(b'mykey')
if result.kvs:
    kv = result.kvs[0]
    print(kv)
else:
    print('key not found')

Iterate over key range

result = yield etcd.get(KeySet(b'mykey1', b'mykey5'))
for kv in result.kvs:
    print(kv)

Iterate over keys with given prefix

result = yield etcd.get(KeySet(b'mykey', prefix=True))
for kv in result.kvs:
    print(kv)

Deleting keys

Delete a (single) key

etcd.delete(b'mykey3')

Delete set of keys in given range

etcd.delete(KeySet(b'mykey3', b'mykey7'))

Delete set of keys with given prefix and return previous key-value pairs

deleted = yield etcd.delete(KeySet(b'mykey3'), return_previous=True)
print('deleted key-value pairs: {}'.format(deleted.previous))

Watching keys

Watch keys for changes

# callback invoked for every change
def on_change(kv):
    print('on_change: {}'.format(kv))

# start watching on set of keys with given prefix
d = etcd.watch([KeySet(b'mykey', prefix=True)], on_change)
print('watching ..')

# stop after 60 seconds
yield txaio.sleep(60)
d.cancel()

Transactions

txn = Transaction(
    compare=[
        CompValue(b'mykey1', '==', b'val1')
    ],
    success=[
        OpSet(b'mykey1', b'val2'),
        OpSet(b'mykey2', b'success')
    ],
    failure=[
        OpSet(b'mykey2', b'failure'),
        OpGet(b'mykey1')
    ]
)

try:
    result = yield etcd.submit(txn)
except Failed as failed:
    print('transaction FAILED:')
    for response in failed.responses:
        print(response)
else:
    print('transaction SUCCESS:')
    for response in result.responses:
        print(response)

Leases

Write me. For now, please see the lease.py example in the examples folder.

Locks

NO YET IMPLEMENTED (JUST A POSSIBLE SKETCH).

Create or wait to acquire a named lock

lock = yield etcd.lock(b'mylock')

# now do something on the exclusively locked resource
# or whatever the lock stands for or is associated with

lock.release()

Create or wait to acquire, but with a timeout

try:
    lock = yield etcd.lock(b'mylock', timeout=10)
except Timeout:
    print('could not acquire lock: timeout')
else:

    # operate on the locked resource

    lock.release()

Importer and Exporter

The txaio-etcd package contains two command line tools:

  • etcd-exporter

  • etcd-importer

These can be used to export and import data from and to etcd.

The tools support various options for key/value types and input/output format, eg the exporter:

(cpy362_1) oberstet@thinkpad-t430s:~$ etcd-export --help
usage: etcd-export [-h] [-a ADDRESS] [-k {utf8,binary}]
                   [-v {json,binary,utf8}] [-f {json,csv}] [-o OUTPUT_FILE]

Utility to dump etcd database to a file.

optional arguments:
  -h, --help            show this help message and exit
  -a ADDRESS, --address ADDRESS
                        Address(with port number) of the etcd daemon (default:
                        http://localhost:2379)
  -k {utf8,binary}, --key-type {utf8,binary}
                        The key type in the etcd database (default: utf8).
  -v {json,binary,utf8}, --value-type {json,binary,utf8}
                        The value type in the etcd database (default: json).
  -f {json,csv}, --output-format {json,csv}
                        The output format for the database dump (default:
                        json).
  -o OUTPUT_FILE, --output-file OUTPUT_FILE
                        Path for the output file. When unset, output goes to
                        stdout.

and the importer:

(cpy362_1) oberstet@thinkpad-t430s:~$ etcd-import --help
usage: etcd-import [-h] [-a ADDRESS] [-k {utf8,binary}]
                   [-v {json,binary,utf8}] [-f {json,csv}] [-d]
                   [-o DRY_OUTPUT] [--verbosity {silent,compact,verbose}]
                   input_file

Utility to import external file to etcd database.

positional arguments:
  input_file            Path for the input file.

optional arguments:
  -h, --help            show this help message and exit
  -a ADDRESS, --address ADDRESS
                        Address(with port number) of the etcd daemon (default:
                        http://localhost:2379)
  -k {utf8,binary}, --key-type {utf8,binary}
                        The key type in the etcd database (default: utf8).
  -v {json,binary,utf8}, --value-type {json,binary,utf8}
                        The value type in the etcd database (default: json).
  -f {json,csv}, --input-format {json,csv}
                        The input format for the database file (default:
                        json).
  -d, --dry-run         Print the potential changes to import.
  -o DRY_OUTPUT, --dry-output DRY_OUTPUT
                        The file to put the result of dry run (default:
                        stdout).
  --verbosity {silent,compact,verbose}
                        Set the verbosity level.

Design Goals

We want etcd3 support because of the extended, useful functionality and semantics offered.

Supporting etcd2 using a restricted parallel API or by hiding away the differences between etcd2 and etcd3 seems ugly and we didn’t needed etcd2 support anyway. So etcd2 support is a non-goal.

The implementation must be fully non-blocking and asynchronous, and must run on Twisted in particular. Supporting asyncio, or even a Python 3.5+ syntax for Twisted etc etc seems possible to add later without affecting the API.

The implementation must run fast on PyPy, which rules out using native code wrapped using cpyext. We also want to avoid native code in general, as it introduces security and memory-leak worries, and PyPy’s JIT produces very fast code anyway.

Implementation

The library uses the gRPC HTTP gateway within etcd3 and talks regular HTTP/1.1 with efficient long-polling for watching keys.

Twisted Web agent and treq is used for HTTP, and both use a configurable Twisted Web HTTP connection pool.

Current limitations

Missing asyncio support

The API of txaioetcd was designed not leaking anything from Twisted other than Deferreds. This is similar to and in line with the approach that txaio takes.

The approach will allow us to add an asyncio implementation under the hood without affecting existing application code, but make the library run over either Twisted or asyncio, similar to txaio.

Further, Twisted wants to support the new Python 3.5+ async/await syntax on Twisted Deferreds, and that in turn would make it possible to write applications on top of txaioetcd that work either using native Twisted or asyncio without changing the app code.

Note that this is neither the same as running a Twisted reactor on top of an asyncio loop nor vice versa. The app is still running under Twisted or asyncio, but selecting the framework might even be a user settable command line option to the app.

Missing native protocol support

The implementation talks HTTP/1.1 to the gRPC HTTP gateway of etcd3, and the binary payload is transmitted JSON with string values that Base64 encode the binary values of the etcd3 API.

Likely more effienct would be talk the native protocol of etcd3, which is HTTP/2 and gRPC/protobuf based. The former requires a HTTP/2 Twisted client. The latter requires a pure Python implementation of protobuf messages used and gRPC. So this is definitely some work, and probably premature optimization. The gateway is just way simpler to integrate with as it uses the least common or invasive thing, namely HTTP/REST and long polling. Certainly not the most efficient, that is also true.

But is seems recommended to run a local etcd proxy on each host, and this means we’re talking the (ineffcient) HTTP protocol over loopback TCP, and hence it is primarily a question of burning some additional CPU cycles.

Missing dynamic watches

The HTTP/2 etcd3 native protocol allows to change a created watch on the fly. Maybe the gRPC HTTP gateway also allows that.

But I couldn’t get a streaming request working with neither Twisted Web agent nor treq. A streaming response works of course, as in fact this is how the watch feature in txaioetcd is implemented.

And further, the API of txaioetcd doesn’t expose it either. A watch is created, started and a Twisted Deferred (or possibly asyncio Future) is returned. The watch can be stopped by canceling the Deferred (Future) previously returned - but that is it. A watch cannot be changed after the fact.

Regarding the public API of txaioetcd, I think there will be a way that would allow adding dynamic watches that is upward compatible and hence wouldn’t break any app code. So it also can be done later.

Asynchronous Iterators

When a larger set of keys and/or values is fetched, it might be beneficial to apply the asynchronous iterator pattern.

This might come in handy on newer Pythons with syntax for that.

Note that a full blown consumer-producer (flow-controller) pattern is probably overkill, as etcd3 isn’t for large blobs or media files.

Asynchronous Context Managers

This would be a nice and robust idiom to write app code in:

async with etcd.lock(b'mylock') as lock:
    # whatever the way this block finishes,
    # the lock will be unlocked

No etcd admin API support

etcd has a large number of administrative procedures as part of the API like list, add, remove etc cluster members and other things.

These API parts of etcd are currently not exposed in txaioetcd - and I am not completely convinced it would necessary given there is etcdctl or even desirable from a security perspective, as it exposes sensitive API at the app level.

But yes, it is missing completely.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

txaioetcd-0.4.1.tar.gz (34.5 kB view details)

Uploaded Source

Built Distribution

txaioetcd-0.4.1-py2.py3-none-any.whl (36.3 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file txaioetcd-0.4.1.tar.gz.

File metadata

  • Download URL: txaioetcd-0.4.1.tar.gz
  • Upload date:
  • Size: 34.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No

File hashes

Hashes for txaioetcd-0.4.1.tar.gz
Algorithm Hash digest
SHA256 eba1d4c28dea98c0e0185189eef46bfd9a8aebd27834934e5ff6a749e1ebcc4f
MD5 cea291462403402461dfe8c79b4bb64d
BLAKE2b-256 571b0629d73b91b389112e3f7c41c8e25ad4333c31c00e87240792bf65188f89

See more details on using hashes here.

File details

Details for the file txaioetcd-0.4.1-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for txaioetcd-0.4.1-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 824eb0d127f05b0775601b756aff703cf9ab7ed96410bd532d064434eb8afe15
MD5 79f7bf318c7e74c615793b6f6af0d585
BLAKE2b-256 481b091fffbd7aad063110466ea9e8950c1d43d58064a10a08472cfe7ad021c2

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page