EV-Etcd
view release on metacpan or search on metacpan
NAME
EV::Etcd - Async etcd v3 client using native gRPC and EV/libev
SYNOPSIS
use v5.10;
use EV;
use EV::Etcd;
my $client = EV::Etcd->new(
endpoints => ['127.0.0.1:2379'],
);
$client->put('/my/key', 'value', sub {
my ($resp, $err) = @_;
die $err->{message} if $err;
say "Put succeeded, revision: $resp->{header}{revision}";
});
$client->get('/my/key', sub {
my ($resp, $err) = @_;
die $err->{message} if $err;
say "Value: $resp->{kvs}[0]{value}";
});
$client->watch('/my/key', sub {
my ($resp, $err) = @_;
return warn "Watch error: $err->{message}\n" if $err;
for my $event (@{$resp->{events}}) {
say "Event: $event->{type} on $event->{kv}{key}";
}
});
EV::run;
DESCRIPTION
An asynchronous etcd v3 client on the gRPC Core C API and the EV event
loop: a gRPC thread waits for completions and wakes the loop, and
callbacks run in the Perl thread. It needs etcd 3.4 or later
("auth_status" needs 3.5).
Every method takes a callback last; methods with options take them as a
hash reference just before it. Invalid arguments croak. The callback
receives "($response, $error)": the response hash and "undef", or
"undef" and an error hash (see "ERRORS"). Responses carry a "header"
with "cluster_id", "member_id", "revision" and "raft_term" unless noted
otherwise. A kv hash has "key", "value", "create_revision",
"mod_revision", "version" and "lease".
CONSTRUCTOR
new
my $client = EV::Etcd->new(%options);
endpoints => [ 'host:port', ... ]
Default "['127.0.0.1:2379']"; an empty list croaks. An endpoint may
carry the "http://" or "https://" scheme etcd prints; "https://"
turns on "tls".
The client uses one endpoint at a time and moves to the next when it
cannot be reached: a unary call fails with UNAVAILABLE, or with
DEADLINE_EXCEEDED before a connection was made, a stream has to
reconnect because its connection is down, or a keepalive ping goes
unanswered. The failing call still reports its error; retrying it
reaches the next endpoint. Several failures on one endpoint move the
client once.
A member that has lost its leader fails linearizable reads and
writes after etcd's request timeout (7 seconds by default), and
these failures move the client on too, though a shorter "timeout"
ends the call on the client first and does not. Errors a working
member returns during an election, such as "etcdserver: leader
changed", do not move it.
Streams need a leader as well: a member without one refuses new
streams and, after a few election timeouts, ends running watches and
keepalives with UNAVAILABLE "etcdserver: no leader". Streams retry
such answers a second apart until a leader is back, on the next
endpoint when there are several; each restarts the "max_retries"
count. Once a unary call, or a stream that was running, reports
"etcdserver: no leader", pending "lock" and "election_campaign"
calls sent over the same connection fail with UNAVAILABLE, as they
do when a connection that never became ready is abandoned.
An endpoint may also be a gRPC target listing several addresses,
such as "ipv4:10.0.0.1:2379,10.0.0.2:2379". gRPC moves between them
within one connection, so a refused address fails no call, but never
away from a member that has lost its leader: list members as
separate endpoints to leave one.
gRPC connects through a proxy named in "grpc_proxy", "https_proxy"
or "http_proxy", even to a loopback address; list etcd's hosts in
"no_proxy" to connect directly.
timeout => $seconds
RPC timeout in whole seconds, at least 1; default 30. "lock" and
"election_campaign" wait without one, as a timeout could not tell
whether they succeeded.
max_retries => $count
Reconnection attempts for a stream ("watch", "lease_keepalive",
"election_observe") after a connection failure; default 30, 0
disables reconnecting. Attempts are 0.5 seconds further apart each
time, up to 5 seconds, so the default gives up about two minutes
after the endpoints start refusing connections, plus gRPC's
20-second connect timeout for one that accepts them but stays
silent. Lower it to learn sooner that the endpoints are gone.
keepalive_time => $seconds
keepalive_timeout => $seconds
Seconds between pings while calls or streams are open (default 10,
fractions allowed, 0 disables them), and how long a ping may go
unanswered (default 10). An unanswered ping closes the connection:
its calls fail with UNAVAILABLE and the client moves to the next
endpoint. Without pings, an endpoint that goes silent (a hung host,
a partition) goes unnoticed until the operating system gives up on
the connection. etcd closes connections that ping more often than
its "--grpc-keepalive-min-time" (5 seconds by default).
health_interval => $seconds
on_health_change => sub { my ($healthy, $endpoint) = @_; ... }
Every "health_interval" seconds (fractions allowed; default 0, off)
the client checks its connection state without sending a request,
and calls "on_health_change" when health changes. Only a failed
connection attempt counts as unhealthy; with several endpoints it
also moves the client on. Switching endpoints after a failed call is
not reported.
auth_token => $token
A token from an earlier "authenticate", used without authenticating
again.
tls => $bool
Connect with TLS; any "tls_*" option or an "https://" endpoint turns
it on too. Without "tls_ca_file", gRPC uses its default roots, or
the PEM bundle named by "GRPC_DEFAULT_SSL_ROOTS_FILE_PATH".
tls_ca_file => $path
PEM CA certificates to verify the servers with (etcd's
"--trusted-ca-file"), in place of the default roots. Verification
cannot be turned off; for a self-signed server, give its certificate
here.
tls_cert_file => $path
tls_key_file => $path
Client certificate and key, given together, for servers started with
"--client-cert-auth".
tls_server_name => $name
Name to verify the server certificate against and send as SNI, in
place of the endpoint's host.
my $client = EV::Etcd->new(
endpoints => ['https://10.0.0.1:2379', 'https://10.0.0.2:2379'],
tls_ca_file => '/etc/etcd/ca.crt',
tls_cert_file => '/etc/etcd/client.crt',
tls_key_file => '/etc/etcd/client.key',
);
ERRORS
{
code => 14, # gRPC status code
status => 'UNAVAILABLE',
message => 'Connection refused',
source => 'range', # the call that failed
retryable => 1,
}
"source" is the method name, except "range" for "get", "lease_ttl" for
"lease_time_to_live", "keepalive" for "lease_keepalive", "campaign",
"proclaim", "leader", "resign" and "observe" for the "election_*" calls,
and "internal" for a unary response the client could not read.
"retryable" is set for UNAVAILABLE, ABORTED, DEADLINE_EXCEEDED and
RESOURCE_EXHAUSTED "etcdserver: too many requests", but never for a
failed "lock" or "election_campaign". Other RESOURCE_EXHAUSTED errors
last until someone intervenes, such as "etcdserver: mvcc: database space
exceeded" (see "alarm"). Retryable means transient, not without effect:
a write can be applied after its deadline has passed, so retrying
"lease_grant", "lease_revoke" or "delete" can apply it twice or report
what the first attempt did.
Unary calls are not retried. Streams reconnect on their own (see
"max_retries"), to the next endpoint once the current one has failed,
and the count restarts once a stream is working again. A stream reports
an error when reconnecting is disabled or exhausted, at once for a
status a new connection cannot fix (UNAUTHENTICATED, PERMISSION_DENIED,
INVALID_ARGUMENT, NOT_FOUND, ALREADY_EXISTS, FAILED_PRECONDITION,
OUT_OF_RANGE, UNIMPLEMENTED), and for an error the server sends on it,
such as a cancelled or compacted watch or an expired lease, which ends
it.
The server can still clean up after a failed "lock" or
"election_campaign" has reported, deleting the key it made on the lease.
Retry with a fresh lease, and revoke the old one (which deletes all its
keys) or let it expire, as a stale candidate on it can block the new
attempt.
CALLBACKS AND HANDLES
A callback that dies stops neither the loop nor the client: the
exception goes to $EV::DIED, a warning by default.
Callbacks live in C structures Perl cannot see, so a closure that
captures its client or stream handle keeps it alive until the stream is
cancelled or the client destroyed; capture a weakened copy instead.
Dropping a handle does not cancel its stream. Destroying a client
cancels its calls and streams without calling their callbacks. A client
keeps "EV::run" running while it exists: destroy it or call "EV::break"
to leave the loop. Only EV's default loop runs the callbacks, never a
loop from "EV::Loop->new".
use Scalar::Util 'weaken';
weaken(my $weak = $client);
my $watch = $client->watch('/jobs', sub {
my ($resp, $err) = @_;
$weak->put('/seen', 1, sub {}) if $weak && !$err;
});
cancel
$handle->cancel($callback);
"watch", "lease_keepalive" and "election_observe" return a handle
("EV::Etcd::Watch", "EV::Etcd::Keepalive", "EV::Etcd::Observe") whose
"cancel" ends the stream. The callback runs before "cancel" returns,
with an empty hash as the response, and the stream delivers nothing
after it. Cancelling again is safe and does the same.
ENCODING
etcd stores keys and values as bytes and the client does no encoding: a
string with the UTF-8 flag is stored as its UTF-8 bytes, and responses
hold byte strings. Use "encode_utf8" and "decode_utf8" from Encode at
the boundary for character data.
A key or value over 1 MiB croaks before anything is sent. etcd refuses a
request over its "--max-request-bytes" (1.5 MiB by default) with
INVALID_ARGUMENT "etcdserver: request is too large", and gRPC one 512
KiB larger still with RESOURCE_EXHAUSTED.
KEY-VALUE
put
$client->put($key, $value, [\%opts,] $callback);
lease => $lease_id
Attach the key to a lease.
( run in 0.872 second using v1.01-cache-2.11-cpan-16aabcbfbcb )