EV-Etcd
view release on metacpan or search on metacpan
t/auth_enable_disable.t view on Meta::CPAN
#!/usr/bin/env perl
use strict;
use warnings;
BEGIN { delete @ENV{qw(http_proxy https_proxy grpc_proxy)} }
use lib 'blib/lib', 'blib/arch';
use Test::More;
# Toggles auth cluster-wide; a failure mid-run can leave the cluster locked, so
# run it only against a throwaway etcd.
BEGIN {
unless ($ENV{ETCD_TEST_AUTH_ENABLE_DISABLE}) {
plan skip_all => 'Set ETCD_TEST_AUTH_ENABLE_DISABLE=1 to run auth enable/disable tests (destructive)';
}
eval { require EV };
plan skip_all => 'EV required' if $@;
}
use EV;
use EV::Etcd;
my $etcd_available = 0;
eval {
my $client = EV::Etcd->new(
endpoints => ['127.0.0.1:2379'],
timeout => 2,
);
$client->status(sub {
my ($resp, $err) = @_;
$etcd_available = 1 if !$err;
EV::break;
});
my $t = EV::timer(3, 0, sub { EV::break });
EV::run;
};
plan skip_all => 'etcd not available on 127.0.0.1:2379' unless $etcd_available;
# Each step waits for its own reply; none at all counts as an error, and a
# late one is ignored rather than ending a later step's wait
sub call {
my ($client, $method, @args) = @_;
my ($resp, $err, $done);
my $live = 1;
$client->$method(@args, sub {
return unless $live;
($resp, $err) = @_;
$done = 1;
EV::break;
});
my $t = EV::timer(10, 0, sub { EV::break });
EV::run;
$live = 0;
return $done ? ($resp, $err) : (undef, { status => 'TIMEOUT', message => "$method: no response" });
}
my ($resp, $err) = call(EV::Etcd->new(endpoints => ['127.0.0.1:2379']), 'auth_status');
plan skip_all => 'Auth already enabled - cannot run enable/disable tests'
if $resp && $resp->{enabled};
plan tests => 18;
my $client = EV::Etcd->new(endpoints => ['127.0.0.1:2379']);
my $root_password = "root-test-pwd-$$-" . time();
my $auth_on;
# Disable auth again if the run dies while it may be on
END {
if ($auth_on) {
my $cleanup_client = EV::Etcd->new(endpoints => ['127.0.0.1:2379']);
my (undef, $err) = call($cleanup_client, 'authenticate', 'root', $root_password);
(undef, $err) = call($cleanup_client, 'auth_disable') unless $err;
diag("auth may still be enabled: $err->{message}")
if $err && $err->{status} ne 'FAILED_PRECONDITION';
}
}
($resp, $err) = call($client, 'user_add', 'root', $root_password);
ok(!$err, 'root user created') or diag explain $err;
($resp, $err) = call($client, 'user_grant_role', 'root', 'root');
ok(!$err, 'root role granted') or diag explain $err;
$auth_on = 1;
($resp, $err) = call($client, 'auth_enable');
ok(!$err, 'auth_enable succeeded') or diag explain $err;
($resp, $err) = call($client, 'authenticate', 'root', $root_password);
my $auth_token = $resp && $resp->{token};
ok($auth_token, 'authenticated and got token') or diag explain $err;
my $auth_client = EV::Etcd->new(
endpoints => ['127.0.0.1:2379'],
auth_token => $auth_token,
);
($resp, $err) = call($auth_client, 'auth_status');
ok($resp && $resp->{enabled}, 'auth_status confirms enabled') or diag explain $err;
# Stream leader metadata must coexist with the saved authentication token.
($resp) = call($auth_client, 'lease_grant', 30);
my $lease = $resp && $resp->{id};
my (%ready, %errors, @handles);
if ($lease) {
my $election = "/test-auth-streams-$$";
call($auth_client, 'election_campaign', $election, $lease, 'leader');
my $stream_callback = sub {
my $name = shift;
return sub {
if ($_[1]) { $errors{$name} = $_[1] }
else { $ready{$name} = 1 }
EV::break if keys(%ready) + keys(%errors) == 3;
};
};
push @handles, $auth_client->watch($election, $stream_callback->('watch'));
push @handles, $auth_client->lease_keepalive($lease, $stream_callback->('keepalive'));
push @handles, $auth_client->election_observe($election, $stream_callback->('observe'));
my $t = EV::timer(10, 0, sub { EV::break });
EV::run;
}
for my $name (qw(watch keepalive observe)) {
ok($ready{$name} && !$errors{$name}, "authenticated $name receives responses");
diag explain $errors{$name} if $errors{$name};
}
$_->cancel(sub {}) for @handles;
call($auth_client, 'lease_revoke', $lease) if $lease;
($resp, $err) = call($auth_client, 'auth_disable');
ok(!$err, 'auth_disable succeeded') or diag explain $err;
ok($resp && $resp->{header}, 'auth_disable has header');
$auth_on = 0 unless $err;
# etcd before 3.4.28/3.5.10 keeps rejecting $client's token: the failure must drop it
($resp, $err) = call($client, 'authenticate', 'root', $root_password);
is($err && $err->{status}, 'FAILED_PRECONDITION', 'authenticate fails once auth is disabled');
# Disabling auth wiped the old simple token on every version: a client still
# sending it is refused as "invalid auth token" once auth is back on
$auth_on = 1;
($resp, $err) = call($client, 'auth_enable');
ok(!$err, 'auth re-enabled') or diag explain $err;
($resp, $err) = call($client, 'get', '/test-auth-dropped-token');
is($err && $err->{message}, 'etcdserver: user name is empty', 'the dropped token is not sent');
($resp, $err) = call($client, 'authenticate', 'root', $root_password);
ok($resp && $resp->{token}, 're-authenticated') or diag explain $err;
($resp, $err) = call($client, 'auth_disable');
ok(!$err, 'auth disabled again') or diag explain $err;
$auth_on = 0 unless $err;
($resp, $err) = call($client, 'auth_status');
ok($resp && !$resp->{enabled}, 'auth_status confirms disabled') or diag explain $err;
($resp, $err) = call($client, 'user_delete', 'root');
ok(!$err, 'root user deleted') or diag explain $err;
ok($resp && $resp->{header}, 'user_delete has header');
( run in 0.502 second using v1.01-cache-2.11-cpan-16aabcbfbcb )