Asynchronous client library for etcd3
Project description
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
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file txaioetcd-0.4.0.tar.gz
.
File metadata
- Download URL: txaioetcd-0.4.0.tar.gz
- Upload date:
- Size: 34.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | a0ee5822cabdbfb84edc3408506e3895706c7d02781bd90ba3fb9959b4d1d7c8 |
|
MD5 | 3eadd922491ebf39cbae4bd35f935594 |
|
BLAKE2b-256 | 49c4edffc86a89ee5735d3601d195ca0ce53076f17c19a828760fdf0c43e3e5a |
File details
Details for the file txaioetcd-0.4.0-py2.py3-none-any.whl
.
File metadata
- Download URL: txaioetcd-0.4.0-py2.py3-none-any.whl
- Upload date:
- Size: 42.4 kB
- Tags: Python 2, Python 3
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 9573165c1df5d8cfd37a1015633464421df858b5921b1576c6dcde08c04b32f7 |
|
MD5 | 05758203cb53e16cefd48b2316adacf6 |
|
BLAKE2b-256 | 8c64269fa0f80cff2a4e8f56b849c13315474abd5f95546cc12ebeb43cc85803 |