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'],
);
# Async put
$client->put('/my/key', 'value', sub {
my ($resp, $err) = @_;
die $err->{message} if $err;
say "Put succeeded, revision: $resp->{header}{revision}";
});
# Async get
$client->get('/my/key', sub {
my ($resp, $err) = @_;
die $err->{message} if $err;
say "Value: $resp->{kvs}[0]{value}";
});
# Watch
$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
EV::Etcd provides a high-performance async client for etcd v3 using native gRPC Core C API integrated with the EV event loop.
METHODS
new
my $client = EV::Etcd->new(%options);
Options:
- endpoints
-
ArrayRef of etcd endpoints (host:port). Optional; defaults to
['127.0.0.1:2379']. When more than one is provided, the client uses the first endpoint and rotates to subsequent endpoints on connection failure. - timeout
-
RPC timeout in seconds. Default is 30 seconds. Minimum value is 1 second.
- max_retries
-
Maximum number of reconnection attempts for streaming operations (watch, lease_keepalive, election_observe) after a connection failure. Default is 3. Set to 0 to disable automatic reconnection.
- health_interval
-
Interval in seconds for health monitoring. Default is 0 (disabled). When enabled, the client periodically checks the gRPC channel connectivity state and calls the on_health_change callback when the connection state changes.
- on_health_change
-
Callback called when the connection health status changes. Receives two arguments: a boolean indicating health status (1=healthy, 0=unhealthy) and the current endpoint string.
my $client = EV::Etcd->new( endpoints => ['127.0.0.1:2379'], health_interval => 5, on_health_change => sub { my ($is_healthy, $endpoint) = @_; warn $is_healthy ? "Connected to $endpoint" : "Disconnected from $endpoint"; }, ); - auth_token
-
Pre-set authentication token. Use this to create an authenticated client without calling authenticate() first. Useful when you already have a valid token from a previous session.
my $client = EV::Etcd->new( endpoints => ['127.0.0.1:2379'], auth_token => $saved_token, );
ENCODING
Keys and values are stored by etcd as raw bytes; this module does not perform any character encoding. If you pass a Perl string with the UTF-8 flag set (e.g. a literal containing non-ASCII characters under use utf8), the UTF-8 byte representation is what gets stored. Values returned by get are byte strings without the UTF-8 flag — string-equality with the original literal will fail unless you decode explicitly.
For character data, encode/decode at the boundary using Encode:
use Encode qw(encode_utf8 decode_utf8);
$client->put($key, encode_utf8($value), sub { ... });
$client->get($key, sub {
my ($resp) = @_;
my $value = decode_utf8($resp->{kvs}[0]{value});
});
ERROR HANDLING
Errors are returned as hash references with the following structure:
{
code => 14, # gRPC status code (integer)
status => "UNAVAILABLE", # gRPC status name (string)
message => "Connection refused", # Error message
source => "get", # Which operation failed
retryable => 1, # Whether the error is retryable
}
The retryable field indicates whether the error is transient (status codes: UNAVAILABLE, RESOURCE_EXHAUSTED, ABORTED, DEADLINE_EXCEEDED). Streaming operations (watch, keepalive, observe) automatically reconnect on transient failures according to the max_retries configuration. Unary RPCs (get, put, delete, etc.) do not retry automatically; use the retryable field to implement application-level retry logic.
KEY-VALUE OPERATIONS
put
$client->put($key, $value, $callback);
$client->put($key, $value, \%opts, $callback);
Put a key-value pair into etcd.
Options:
- lease
-
Lease ID to associate with the key.
- prev_kv
-
If true, returns the previous key-value pair in the response.
- ignore_value
-
If true, updates the lease without changing the value.
- ignore_lease
-
If true, updates the value without changing the lease.
The callback receives ($response, $error). Response keys: header, plus prev_kv (a kv hashref) when the prev_kv option is set.
get
$client->get($key, $callback);
$client->get($key, \%opts, $callback);
Get key(s) from etcd.
Options:
- prefix
-
If true, returns all keys with the given prefix.
- range_end
-
End of the key range to query.
- limit
-
Maximum number of keys to return.
- revision
-
Get keys at a specific revision.
- keys_only
-
If true, returns only keys without values.
- count_only
-
If true, returns only the count of keys.
- sort_order
-
Sort order: "ascend" or "descend".
- sort_target
-
What to sort by: "key", "version", "create", "mod", or "value".
- serializable
-
If true, use serializable (faster but possibly stale) reads.
- min_mod_revision, max_mod_revision
-
Filter keys by modification revision.
- min_create_revision, max_create_revision
-
Filter keys by creation revision.
The callback receives ($response, $error). Response keys:
- kvs
-
Array reference of key-value hashrefs (
key,value,create_revision,mod_revision,version,lease). - count
-
Total number of keys matched (may exceed
scalar @{$resp-{kvs}}> whenlimitis in effect). - more
-
True if more keys exist beyond the returned
kvs(limittruncated the result). - header
-
Cluster response header (see "ERROR HANDLING").
delete
$client->delete($key, $callback);
$client->delete($key, \%opts, $callback);
Delete key(s) from etcd.
Options:
- prefix
-
If true, deletes all keys with the given prefix.
- range_end
-
End of the key range to delete.
- prev_kv
-
If true, returns the deleted key-value pairs in the response.
The callback receives ($response, $error). Response keys: header, deleted (count of keys deleted), plus prev_kvs (array of kv hashrefs) when the prev_kv option is set.
WATCH SERVICE
watch
my $watch = $client->watch($key, $callback);
my $watch = $client->watch($key, \%opts, $callback);
Create a watch on a key or key range. Returns an EV::Etcd::Watch object.
The callback is invoked with ($response, $error) for each watch message. Response keys:
- events
-
Array reference of event hashrefs (
type= "PUT" or "DELETE",kv, and optionallyprev_kvwhen theprev_kvoption is set). - watch_id
-
Server-assigned watch identifier. Stable for the lifetime of the watch (including across auto-reconnect).
- created
-
True on the first message after the watch is established.
- header
-
Cluster response header.
Server-side cancellation (including compaction-induced) is delivered through the error callback path, not as a success response — $err-{source}> will be "watch" and $err-{message}> will contain the server's reason (typically including the compact revision when relevant).
Options:
- prefix
-
If true, watches all keys with the given prefix.
- range_end
-
End of the key range to watch.
- start_revision
-
Revision to start watching from. If not specified, watches from the current revision. Use this to resume watching after a reconnect.
- prev_kv
-
If true, the response will include the previous key-value pair for UPDATE and DELETE events.
- progress_notify
-
If true, the server will periodically send progress notifications even when there are no events, allowing the client to know the current revision.
- watch_id
-
Optional explicit watch ID. If not specified, the server assigns one.
- auto_reconnect
-
If true, the watch will automatically reconnect after a connection failure, resuming from the last seen revision. Default is true. This is useful for long-running watches that should survive network interruptions.
my $watch = $client->watch('/my/key', { auto_reconnect => 1, progress_notify => 1, # Helps track revision even during inactivity }, sub { my ($event, $err) = @_; if ($err) { warn "Watch error: $err->{message}"; return; } # Process events... });
EV::Etcd::Watch Methods
cancel
$watch->cancel($callback);
Cancel the watch. The callback receives ($response, $error) when cancellation is complete. The response is an empty hash reference on success.
Calling cancel on an already-cancelled handle is safe: the callback fires immediately with success. The handle remains valid as a Perl reference until you drop it.
$watch->cancel(sub {
my ($resp, $err) = @_;
if ($err) {
warn "Cancel failed: $err->{message}";
} else {
print "Watch cancelled\n";
}
});
LEASE SERVICE
lease_grant
$client->lease_grant($ttl, $callback);
Grant a lease with the specified TTL (time-to-live) in seconds.
The callback receives ($response, $error). Response keys:
- id
-
The lease ID.
- ttl
-
The actual TTL granted by the server.
- header
-
Standard response header.
lease_revoke
$client->lease_revoke($lease_id, $callback);
Revoke a lease. All keys attached to the lease will be deleted. The response contains header only.
lease_keepalive
my $keepalive = $client->lease_keepalive($lease_id, $callback);
my $keepalive = $client->lease_keepalive($lease_id, \%opts, $callback);
Keep a lease alive. Creates a bidirectional streaming connection that keeps the lease refreshed. Returns an EV::Etcd::Keepalive object that can be used to cancel the keepalive stream:
$keepalive->cancel(sub { my ($resp, $err) = @_; });
Options:
- auto_reconnect
-
If true, the keepalive stream will automatically reconnect after a connection failure, with exponential backoff up to
max_retries(set on the client). Default is 1 (enabled). Pass0to disable.
The keepalive callback receives ($response, $error) for each tick. On success the response includes id, ttl, and header. When the lease has expired the server sends ttl=0; the client maps that to an error callback with source => "keepalive" and status NOT_FOUND.
EV::Etcd::Keepalive Methods
cancel
$keepalive->cancel($callback);
Cancel the keepalive stream. The callback receives ($response, $error) when cancellation is complete. The response is an empty hash reference on success.
Calling cancel on an already-cancelled handle is safe: the callback fires immediately with success. The handle remains valid as a Perl reference until you drop it.
lease_time_to_live
$client->lease_time_to_live($lease_id, $callback);
$client->lease_time_to_live($lease_id, \%opts, $callback);
Get the remaining TTL of a lease.
Options:
- keys
-
If true, also return the list of keys attached to this lease.
The callback receives ($response, $error). Response keys:
- id
-
The lease ID.
- ttl
-
Remaining TTL in seconds. Returns -1 if the lease has expired.
- granted_ttl
-
The original TTL granted when the lease was created.
- keys
-
Array of keys attached to this lease (only when the
keysoption is true). - header
-
Standard response header.
lease_leases
$client->lease_leases($callback);
List all active leases. The callback receives ($response, $error). Response keys: header, and leases — an array of hashrefs each with an id key.
LOCK SERVICE
EV::Etcd provides distributed locking through the etcd Lock service. Locks are tied to leases - when the lease expires or is revoked, the lock is automatically released.
lock
$client->lock($name, $lease_id, $callback);
Acquire a distributed lock.
Arguments:
- name
-
The name (identifier) of the lock to acquire. This is a byte string that identifies the resource being locked. Multiple clients attempting to lock the same name will block until the lock is available.
- lease_id
-
The ID of a lease to attach to the lock. The lock will be held for the duration of the lease. If the lease expires or is revoked, the lock is automatically released. You must first create a lease with
lease_grantand optionally keep it alive withlease_keepalive. - callback
-
Called with
($response, $error)when the lock is acquired (or fails).
The response contains:
- key
-
The key that holds the lock. This key is used to unlock the lock and should be stored by the caller. The key is unique to this lock holder and contains the lock name as a prefix.
- header
-
Standard response header with cluster_id, member_id, revision, and raft_term.
Example:
# First, create a lease for the lock
$client->lease_grant(30, sub {
my ($lease_resp, $err) = @_;
die $err->{message} if $err;
my $lease_id = $lease_resp->{id};
# Now acquire the lock
$client->lock("my-resource", $lease_id, sub {
my ($lock_resp, $err) = @_;
die $err->{message} if $err;
my $lock_key = $lock_resp->{key};
print "Lock acquired with key: $lock_key\n";
# ... do protected work ...
# Release the lock when done
$client->unlock($lock_key, sub {
my ($unlock_resp, $err) = @_;
warn "Unlock failed: $err->{message}" if $err;
});
});
});
unlock
$client->unlock($key, $callback);
Release a distributed lock.
Arguments:
- key
-
The lock key returned from a successful
lockcall. This is the unique key that was created to hold the lock ownership. - callback
-
Called with
($response, $error)when the unlock completes.
The response contains:
- header
-
Standard response header with cluster_id, member_id, revision, and raft_term.
Note: You can also release a lock by revoking its associated lease with lease_revoke. This is useful if you want to release all resources associated with a lease at once.
AUTHENTICATION SERVICE
EV::Etcd provides full support for etcd's authentication and authorization system. Authentication uses username/password credentials, and authorization is based on roles with key-range permissions.
authenticate
$client->authenticate($username, $password, $callback);
Authenticate with etcd using username and password. On success, the client automatically stores the auth token and uses it for subsequent requests.
Arguments:
- username
-
The username to authenticate as.
- password
-
The password for the user.
- callback
-
Called with
($response, $error)when authentication completes.
The response contains:
- token
-
The authentication token (also automatically stored in the client).
- header
-
Standard response header.
Example:
$client->authenticate('admin', 'secret', sub {
my ($resp, $err) = @_;
if ($err) {
die "Authentication failed: $err->{message}";
}
say "Authenticated successfully";
# Client now automatically uses the token for all requests
});
auth_enable
$client->auth_enable($callback);
Enable authentication on the etcd cluster.
Warning: Before enabling auth, you must create at least one user with the root role, otherwise you will be locked out of the cluster.
Example:
# First create root user
$client->user_add('root', 'rootpassword', sub {
my ($resp, $err) = @_;
die $err->{message} if $err;
$client->user_grant_role('root', 'root', sub {
my ($resp, $err) = @_;
die $err->{message} if $err;
# Now safe to enable auth
$client->auth_enable(sub {
my ($resp, $err) = @_;
say "Authentication enabled" unless $err;
});
});
});
auth_disable
$client->auth_disable($callback);
Disable authentication on the etcd cluster. Requires root privileges.
auth_status
$client->auth_status($callback);
Check whether authentication is enabled on the etcd cluster. Response keys:
- enabled
-
Boolean indicating whether authentication is enabled.
- auth_revision
-
The current revision of the auth store.
- header
-
Standard response header.
Example:
$client->auth_status(sub {
my ($resp, $err) = @_;
if ($resp->{enabled}) {
say "Authentication is enabled (revision: $resp->{auth_revision})";
} else {
say "Authentication is disabled";
}
});
User Management
user_add
$client->user_add($username, $password, $callback);
Create a new user.
Arguments:
- username
-
The username for the new user.
- password
-
The password for the new user.
- callback
-
Called with
($response, $error)when complete.
user_delete
$client->user_delete($username, $callback);
Delete a user.
user_change_password
$client->user_change_password($username, $password, $callback);
Change a user's password.
user_get
$client->user_get($username, $callback);
Get information about a user.
The response contains:
- roles
-
Array of role names assigned to the user.
Example:
$client->user_get('myuser', sub {
my ($resp, $err) = @_;
say "User has roles: @{$resp->{roles}}";
});
user_list
$client->user_list($callback);
List all users.
The response contains:
- users
-
Array of usernames.
user_grant_role
$client->user_grant_role($username, $role_name, $callback);
Grant a role to a user.
Example:
$client->user_grant_role('myuser', 'readwrite', sub {
my ($resp, $err) = @_;
say "Role granted" unless $err;
});
user_revoke_role
$client->user_revoke_role($username, $role_name, $callback);
Revoke a role from a user.
Role Management
role_add
$client->role_add($role_name, $callback);
Create a new role.
Example:
$client->role_add('readonly', sub {
my ($resp, $err) = @_;
say "Role created" unless $err;
});
role_delete
$client->role_delete($role_name, $callback);
Delete a role.
role_get
$client->role_get($role_name, $callback);
Get information about a role, including its permissions.
The response contains:
- perm
-
Array of permission objects, each containing:
- perm_type
-
Permission type: "READ", "WRITE", or "READWRITE".
- key
-
The key or key prefix this permission applies to.
- range_end
-
The end of the key range (if applicable).
Example:
$client->role_get('myrole', sub {
my ($resp, $err) = @_;
for my $perm (@{$resp->{perm}}) {
say "Permission: $perm->{perm_type} on $perm->{key}";
}
});
role_list
$client->role_list($callback);
List all roles.
The response contains:
- roles
-
Array of role names.
role_grant_permission
$client->role_grant_permission($role_name, $perm_type, $key, $range_end, $callback);
Grant a permission to a role.
Arguments:
- role_name
-
The role to grant the permission to.
- perm_type
-
The permission type: "READ", "WRITE", or "READWRITE".
- key
-
The key or key prefix to grant access to.
- range_end
-
The end of the key range. Use
undeffor a single key, or use the special value"\x00"after the last byte of the prefix to match all keys with that prefix. - callback
-
Called with
($response, $error)when complete.
Example:
# Grant read access to a single key
$client->role_grant_permission('readonly', 'READ', '/config/setting', undef, sub {
my ($resp, $err) = @_;
say "Permission granted" unless $err;
});
# Grant read/write access to all keys under /app/
# Range end is /app0 (the byte after / is 0)
$client->role_grant_permission('readwrite', 'READWRITE', '/app/', '/app0', sub {
my ($resp, $err) = @_;
say "Permission granted" unless $err;
});
role_revoke_permission
$client->role_revoke_permission($role_name, $key, $range_end, $callback);
Revoke a permission from a role.
Arguments:
- role_name
-
The role to revoke the permission from.
- key
-
The key or key prefix of the permission to revoke.
- range_end
-
The end of the key range of the permission to revoke.
- callback
-
Called with
($response, $error)when complete.
Complete Authentication Example
use EV;
use EV::Etcd;
my $client = EV::Etcd->new(endpoints => ['127.0.0.1:2379']);
# Setup authentication (run once, as root)
sub setup_auth {
# Create a role with permissions
$client->role_add('app-role', sub {
my ($resp, $err) = @_;
# Grant read/write on /app/ prefix
$client->role_grant_permission('app-role', 'READWRITE', '/app/', '/app0', sub {
my ($resp, $err) = @_;
# Create user
$client->user_add('appuser', 'apppassword', sub {
my ($resp, $err) = @_;
# Assign role to user
$client->user_grant_role('appuser', 'app-role', sub {
my ($resp, $err) = @_;
say "Auth setup complete";
EV::break;
});
});
});
});
}
# Normal usage with authentication
sub use_with_auth {
$client->authenticate('appuser', 'apppassword', sub {
my ($resp, $err) = @_;
die "Auth failed: $err->{message}" if $err;
# Now all operations use the auth token
$client->put('/app/key', 'value', sub {
my ($resp, $err) = @_;
say "Put succeeded" unless $err;
EV::break;
});
});
}
use_with_auth();
EV::run;
MAINTENANCE SERVICE
EV::Etcd provides access to etcd's maintenance operations for cluster administration and monitoring.
status
$client->status($callback);
Get the status of the etcd member this client is connected to. Useful for health checks and cluster monitoring. Response keys:
- version
-
The etcd server version.
- db_size
-
Backend database size in bytes.
- db_size_in_use
-
Database size in use after compaction.
- leader
-
Member ID of the cluster leader.
- raft_index
-
Current raft index of this member.
- raft_term
-
Current raft term of the cluster.
- raft_applied_index
-
Raft applied index of this member.
- is_learner
-
True if this member is a learner (non-voting).
- errors
-
Array of error strings if this member has any. Absent when no errors.
- header
-
Standard response header.
compact
$client->compact($revision, $callback);
$client->compact($revision, \%opts, $callback);
Compact the key-value store up to the given revision. All revisions older than $revision are discarded.
Warning: compaction is irreversible — historical reads of older revisions fail after the compact completes.
Options:
- physical
-
If true, the RPC waits until the compaction is physically applied to the local backend (entries fully removed). Default is false (the call returns once the compaction is logically committed).
The response contains header only.
alarm
$client->alarm($action, $callback);
$client->alarm($action, \%opts, $callback);
Get, activate, or deactivate alarms on etcd cluster members.
Arguments:
- action
-
The alarm action to perform. Must be one of:
- GET
-
List all active alarms.
- ACTIVATE
-
Activate an alarm on a member.
- DEACTIVATE
-
Deactivate an alarm on a member.
- callback
-
Called with
($response, $error)when the operation completes.
Options:
- member_id
-
The member ID to operate on. Required for ACTIVATE/DEACTIVATE on a specific member. Use 0 for all members.
- alarm
-
The alarm type. Can be "NOSPACE" (storage quota exceeded) or "CORRUPT" (data corruption detected). Default is "NONE" which means all alarms.
The response contains:
- alarms
-
Array of alarm objects, each containing:
- member_id
-
The member ID where the alarm is active.
- alarm
-
The alarm type as an integer.
- alarm_type
-
The alarm type as a string ("NONE", "NOSPACE", or "CORRUPT").
- header
-
Standard response header.
Example:
# List all alarms
$client->alarm('GET', sub {
my ($resp, $err) = @_;
for my $alarm (@{$resp->{alarms}}) {
warn "Alarm on member $alarm->{member_id}: $alarm->{alarm_type}";
}
});
# Deactivate NOSPACE alarm on all members
$client->alarm('DEACTIVATE', { alarm => 'NOSPACE' }, sub {
my ($resp, $err) = @_;
warn "Alarm deactivated" unless $err;
});
defragment
$client->defragment($callback);
Defragment the storage backend on the etcd member this client is connected to. This reclaims storage space by removing deleted keys and compacted revisions.
Warning: Defragmentation is a blocking operation and may cause latency spikes. Run it during maintenance windows.
The response contains:
- header
-
Standard response header.
Example:
$client->defragment(sub {
my ($resp, $err) = @_;
if ($err) {
warn "Defragment failed: $err->{message}";
} else {
say "Defragmentation complete";
}
});
hash_kv
$client->hash_kv($callback);
$client->hash_kv($revision, $callback);
Compute the hash of the KV store up to the given revision. Useful for verifying data consistency across cluster members.
Arguments:
- revision (optional)
-
The revision to hash up to. If not specified, uses the current revision.
- callback
-
Called with
($response, $error)when the operation completes.
The response contains:
- hash
-
The hash value of the KV store.
- compact_revision
-
The compaction revision of the KV store.
- header
-
Standard response header.
Example:
$client->hash_kv(sub {
my ($resp, $err) = @_;
say "KV hash: $resp->{hash}";
say "Compact revision: $resp->{compact_revision}";
});
move_leader
$client->move_leader($target_id, $callback);
Transfer leadership to another member. Only the current leader can transfer leadership.
Arguments:
- target_id
-
The member ID of the new leader. Must be a voting member (not a learner).
- callback
-
Called with
($response, $error)when the operation completes.
The response contains:
- header
-
Standard response header.
Example:
# Get current cluster status to find member IDs
$client->member_list(sub {
my ($resp, $err) = @_;
my $target = $resp->{members}[1]{id}; # Pick a different member
$client->move_leader($target, sub {
my ($resp, $err) = @_;
if ($err) {
warn "Failed to move leader: $err->{message}";
} else {
say "Leadership transferred";
}
});
});
ELECTION SERVICE
EV::Etcd provides leader election support through the etcd Election service. Elections use leases to ensure that leadership is automatically released when a leader fails.
election_campaign
$client->election_campaign($name, $lease_id, $value, $callback);
Campaign for leadership of an election.
This call blocks until the caller is elected as leader. Once elected, the caller should periodically keep the lease alive to maintain leadership.
Arguments:
- name
-
The name of the election to campaign in.
- lease_id
-
The lease ID to use for the campaign. The leadership is held for the duration of this lease.
- value
-
The value to set when elected. Other clients can read this value to identify the current leader.
- callback
-
Called with
($response, $error)when elected (or on failure).
The response contains:
- leader
-
A hash containing the leader key information:
- name
-
The election name.
- key
-
The key in etcd that holds the leadership (use for proclaim/resign).
- rev
-
The creation revision of the leader key.
- lease
-
The lease ID attached to the leadership.
- header
-
Standard response header.
Example:
$client->lease_grant(30, sub {
my ($resp, $err) = @_;
my $lease_id = $resp->{id};
$client->election_campaign("my-election", $lease_id, "leader-1", sub {
my ($resp, $err) = @_;
if ($err) {
warn "Failed to become leader: $err->{message}";
return;
}
say "Elected as leader!";
my $leader = $resp->{leader}; # hashref — pass to proclaim/resign
});
});
election_leader
$client->election_leader($name, $callback);
Get the current leader of an election.
Arguments:
- name
-
The name of the election.
- callback
-
Called with
($response, $error)when complete.
The response contains:
- kv
-
The key-value pair of the current leader, containing the leader's value.
- header
-
Standard response header.
Returns an error if there is no current leader.
Example:
$client->election_leader("my-election", sub {
my ($resp, $err) = @_;
if ($err) {
warn "No leader: $err->{message}";
} else {
say "Current leader value: $resp->{kv}{value}";
}
});
election_proclaim
$client->election_proclaim($leader, $value, $callback);
Update the leader's value. Only the current leader can proclaim. The response contains header only.
Arguments:
- leader
-
The leader hashref returned in
$resp->{leader}fromelection_campaign. - value
-
The new value to announce.
- callback
-
Called with
($response, $error)when complete.
Example:
$client->election_proclaim($leader, "new-value", sub {
my ($resp, $err) = @_;
warn "Proclaim failed: $err->{message}" if $err;
});
election_resign
$client->election_resign($leader, $callback);
Voluntarily give up leadership. The response contains header only.
Arguments:
- leader
-
The leader hashref returned in
$resp->{leader}fromelection_campaign. - callback
-
Called with
($response, $error)when complete.
Example:
$client->election_resign($leader, sub {
my ($resp, $err) = @_;
say "Resigned from leadership" unless $err;
});
election_observe
my $observe = $client->election_observe($name, $callback);
my $observe = $client->election_observe($name, \%opts, $callback);
Observe leader changes for an election. This creates a streaming connection that receives notifications whenever the leader changes. Returns an EV::Etcd::Observe object that can be used to cancel the observe stream:
$observe->cancel(sub { my ($resp, $err) = @_; });
Arguments:
- name
-
The name of the election to observe.
- callback
-
Called with
($response, $error)for each leader change.
Options:
- auto_reconnect
-
If true, automatically reconnect after connection failures. Default is true.
The response contains:
- kv
-
The key-value pair of the current leader.
- header
-
Standard response header.
Example:
my $observe = $client->election_observe("my-election", sub {
my ($resp, $err) = @_;
if ($err) {
warn "Observe error: $err->{message}";
return;
}
say "Leader changed: $resp->{kv}{value}";
});
EV::Etcd::Observe Methods
cancel
$observe->cancel($callback);
Cancel the observe stream. The callback receives ($response, $error) when cancellation is complete. The response is an empty hash reference on success.
Calling cancel on an already-cancelled handle is safe: the callback fires immediately with success. The handle remains valid as a Perl reference until you drop it.
CLUSTER SERVICE
EV::Etcd provides cluster membership management through the Cluster service.
member_list
$client->member_list($callback);
$client->member_list(\%opts, $callback);
List all members in the etcd cluster.
Options:
- linearizable
-
If true, perform a linearizable (strongly consistent) read. Default is 0 (serializable read — faster but may be slightly stale).
The response contains:
- members
-
Array of member objects, each containing:
- id
-
The member ID.
- name
-
The member name.
- peer_urls
-
Array of peer URLs for cluster communication.
- client_urls
-
Array of client URLs for client requests.
- is_learner
-
Boolean indicating if the member is a learner.
- header
-
Standard response header.
Example:
$client->member_list(sub {
my ($resp, $err) = @_;
for my $member (@{$resp->{members}}) {
say "Member $member->{name} (ID: $member->{id})";
say " Client URLs: @{$member->{client_urls}}";
}
});
member_add
$client->member_add(\@peer_urls, $callback);
$client->member_add(\@peer_urls, \%opts, $callback);
Add a new member to the cluster.
Arguments:
- peer_urls
-
Array of peer URLs for the new member.
- callback
-
Called with
($response, $error)when complete.
Options:
- is_learner
-
If true, add the member as a non-voting learner.
member_remove
$client->member_remove($member_id, $callback);
Remove a member from the cluster. The response contains header and members (the cluster's remaining members after removal).
member_update
$client->member_update($member_id, \@peer_urls, $callback);
Update a member's peer URLs. The response contains header and members.
member_promote
$client->member_promote($member_id, $callback);
Promote a learner member to a voting member. The response contains header and members.
TRANSACTIONS
EV::Etcd supports atomic transactions with compare-and-swap semantics.
txn
$client->txn(
compare => \@compare_ops,
success => \@success_ops,
failure => \@failure_ops,
callback => $callback,
);
# Or positional form:
$client->txn(\@compare, \@success, \@failure, $callback);
Execute an atomic transaction. If all compare operations succeed, the success operations are executed; otherwise, the failure operations are executed.
Compare operations:
{ key => $key, target => 'value', value => $expected }
{ key => $key, target => 'version', version => $expected }
{ key => $key, target => 'create', create_revision => $expected }
{ key => $key, target => 'mod', mod_revision => $expected }
{ key => $key, target => 'lease', lease => $expected }
The optional result field controls the comparison operator (default: =):
result => '=' # EQUAL (default)
result => '!=' # NOT_EQUAL
result => '<' # LESS
result => '>' # GREATER
Note: Specify exactly one target field (value/version/create_revision/mod_revision/lease) per compare operation. If multiple are provided, the last one processed takes precedence.
Request operations (for success/failure):
{ request_put => { key => $key, value => $value } }
{ request_delete_range => { key => $key } }
{ request_range => { key => $key } }
Short aliases are also accepted:
{ put => { key => $key, value => $value } }
{ delete => { key => $key } }
{ range => { key => $key } }
Example:
$client->txn(
compare => [
{ key => '/counter', target => 'value', value => '0' }
],
success => [
{ request_put => { key => '/counter', value => '1' } }
],
failure => [],
callback => sub {
my ($resp, $err) = @_;
say $resp->{succeeded} ? "Incremented" : "Already changed";
},
);
CAVEATS
Fork safety: EV::Etcd clients must not be used after fork(). The background gRPC thread does not survive into the child process. Inherited client objects will be safely cleaned up on destruction (with a warning), but cannot perform any operations. Create a new client in the child process if needed.
AUTHOR
Yegor Korablev (egor@cpan.org)
LICENSE
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.