Group
Extension

Mojolicious/t/mojo/hypnotoad.t

use Mojo::Base -strict;

BEGIN { $ENV{MOJO_REACTOR} = 'Mojo::Reactor::Poll' }

use Test::More;

plan skip_all => 'set TEST_HYPNOTOAD to enable this test (developer only!)'
  unless $ENV{TEST_HYPNOTOAD} || $ENV{TEST_ALL};

use IO::Socket::INET;
use Mojo::File qw(curfile tempdir);
use Mojo::IOLoop::Server;
use Mojo::Server::Hypnotoad;
use Mojo::UserAgent;

subtest 'Configure' => sub {
  my $hypnotoad = Mojo::Server::Hypnotoad->new;
  $hypnotoad->prefork->app->config->{myserver} = {
    accepts            => 13,
    backlog            => 43,
    clients            => 1,
    graceful_timeout   => 23,
    heartbeat_interval => 7,
    heartbeat_timeout  => 9,
    inactivity_timeout => 5,
    keep_alive_timeout => 3,
    listen             => ['http://*:8081'],
    pid_file           => '/foo/bar.pid',
    proxy              => 1,
    requests           => 3,
    spare              => 4,
    trusted_proxies    => ['127.0.0.0/8'],
    upgrade_timeout    => 45,
    workers            => 7
  };
  is $hypnotoad->upgrade_timeout, 180, 'right default';
  $hypnotoad->configure('test');
  is_deeply $hypnotoad->prefork->listen, ['http://*:8080'], 'right value';
  $hypnotoad->configure('myserver');
  is $hypnotoad->prefork->accepts,            13, 'right value';
  is $hypnotoad->prefork->backlog,            43, 'right value';
  is $hypnotoad->prefork->graceful_timeout,   23, 'right value';
  is $hypnotoad->prefork->heartbeat_interval, 7,  'right value';
  is $hypnotoad->prefork->heartbeat_timeout,  9,  'right value';
  is $hypnotoad->prefork->inactivity_timeout, 5,  'right value';
  is $hypnotoad->prefork->keep_alive_timeout, 3,  'right value';
  is_deeply $hypnotoad->prefork->listen, ['http://*:8081'], 'right value';
  is $hypnotoad->prefork->max_clients,  1,              'right value';
  is $hypnotoad->prefork->max_requests, 3,              'right value';
  is $hypnotoad->prefork->pid_file,     '/foo/bar.pid', 'right value';
  ok $hypnotoad->prefork->reverse_proxy, 'reverse proxy enabled';
  is $hypnotoad->prefork->spare, 4, 'right value';
  is_deeply $hypnotoad->prefork->trusted_proxies, ['127.0.0.0/8'], 'right value';
  is $hypnotoad->prefork->workers, 7,  'right value';
  is $hypnotoad->upgrade_timeout,  45, 'right value';
};

subtest 'Hot deployment' => sub {
  my $dir    = tempdir;
  my $script = $dir->child('myapp.pl');
  my $log    = $dir->child('mojo.log');
  my $port1  = Mojo::IOLoop::Server->generate_port;
  my $port2  = Mojo::IOLoop::Server->generate_port;
  $script->spew(<<EOF);
use Mojolicious::Lite;
use Mojo::IOLoop;

app->log->path('$log');

plugin Config => {
  default => {
    hypnotoad => {
      listen => ['http://127.0.0.1:$port1', 'http://127.0.0.1:$port2'],
      workers => 1
    }
  }
};

app->log->level('trace');

get '/hello' => {text => 'Hello Hypnotoad!'};

my \$graceful;
Mojo::IOLoop->singleton->on(finish => sub { \$graceful++ });

get '/graceful' => sub {
  my \$c = shift;
  my \$id;
  \$id = Mojo::IOLoop->recurring(0 => sub {
    return unless \$graceful;
    \$c->render(text => 'Graceful shutdown!');
    Mojo::IOLoop->remove(\$id);
  });
};

app->start;
EOF

  my $prefix = curfile->dirname->dirname->sibling('script');
  open my $start, '-|', $^X, "$prefix/hypnotoad", $script;
  sleep 3;
  sleep 1 while !_port($port2);
  my $old = _pid($dir->child('hypnotoad.pid'));
  my $ua  = Mojo::UserAgent->new;

  subtest 'Application is alive' => sub {
    my $tx = $ua->get("http://127.0.0.1:$port1/hello");
    ok $tx->is_finished, 'transaction is finished';
    ok $tx->keep_alive,  'connection will be kept alive';
    ok !$tx->kept_alive, 'connection was not kept alive';
    is $tx->res->code, 200,                'right status';
    is $tx->res->body, 'Hello Hypnotoad!', 'right content';

    $tx = $ua->get("http://127.0.0.1:$port2/hello");
    ok $tx->is_finished, 'transaction is finished';
    ok $tx->keep_alive,  'connection will be kept alive';
    ok !$tx->kept_alive, 'connection was not kept alive';
    is $tx->res->code, 200,                'right status';
    is $tx->res->body, 'Hello Hypnotoad!', 'right content';

    $tx = $ua->get("http://127.0.0.1:$port1/hello");
    ok $tx->is_finished, 'transaction is finished';
    ok $tx->keep_alive,  'connection will be kept alive';
    ok $tx->kept_alive,  'connection was kept alive';
    is $tx->res->code, 200,                'right status';
    is $tx->res->body, 'Hello Hypnotoad!', 'right content';

    $tx = $ua->get("http://127.0.0.1:$port2/hello");
    ok $tx->is_finished, 'transaction is finished';
    ok $tx->keep_alive,  'connection will be kept alive';
    ok $tx->kept_alive,  'connection was kept alive';
    is $tx->res->code, 200,                'right status';
    is $tx->res->body, 'Hello Hypnotoad!', 'right content';
  };

  $script->spew(<<'EOF');
use Mojolicious::Lite;

die if $ENV{HYPNOTOAD_PID};

app->start;
EOF
  open my $hot_deploy, '-|', $^X, "$prefix/hypnotoad", $script;

  while (1) {
    last if $log->slurp =~ qr/Zero downtime software upgrade failed/;
    sleep 1;
  }

  subtest 'Connection did not get lost' => sub {
    my $tx = $ua->get("http://127.0.0.1:$port1/hello");
    ok $tx->is_finished, 'transaction is finished';
    ok $tx->keep_alive,  'connection will be kept alive';
    ok $tx->kept_alive,  'connection was kept alive';
    is $tx->res->code, 200,                'right status';
    is $tx->res->body, 'Hello Hypnotoad!', 'right content';

    $tx = $ua->get("http://127.0.0.1:$port2/hello");
    ok $tx->is_finished, 'transaction is finished';
    ok $tx->keep_alive,  'connection will be kept alive';
    ok $tx->kept_alive,  'connection was kept alive';
    is $tx->res->code, 200,                'right status';
    is $tx->res->body, 'Hello Hypnotoad!', 'right content';
  };

  subtest 'Request that will be served after graceful shutdown has been initiated' => sub {
    my $tx = $ua->build_tx(GET => "http://127.0.0.1:$port1/graceful");
    $ua->start($tx => sub { });
    Mojo::IOLoop->one_tick until $tx->req->is_finished;

    $script->spew(<<EOF);
use Mojolicious::Lite;

app->log->path('$log');

plugin Config => {
  default => {
    hypnotoad => {
      accepts => 2,
      inactivity_timeout => 3,
      listen => ['http://127.0.0.1:$port1', 'http://127.0.0.1:$port2'],
      requests => 1,
      workers => 1
    }
  }
};

app->log->level('trace');

get '/hello' => sub { shift->render(text => "Hello World \$\$!") };

app->start;
EOF
    open $hot_deploy, '-|', $^X, "$prefix/hypnotoad", $script;

    while (1) {
      sleep 1;
      next unless my $new = _pid($dir->child('hypnotoad.pid'));
      last if $new ne $old;
    }

    Mojo::IOLoop->one_tick until $tx->is_finished;
    ok !$tx->keep_alive, 'connection will not be kept alive';
    ok !$tx->kept_alive, 'connection was not kept alive';
    is $tx->res->code, 200,                  'right status';
    is $tx->res->body, 'Graceful shutdown!', 'right content';
  };

  subtest 'One uncertain request that may or may not be served by the old worker' => sub {
    my $tx = $ua->get("http://127.0.0.1:$port1/hello");
    is $tx->res->code, 200, 'right status';
    $tx = $ua->get("http://127.0.0.1:$port2/hello");
    is $tx->res->code, 200, 'right status';
  };

  subtest 'Application has been reloaded' => sub {
    my $tx = $ua->get("http://127.0.0.1:$port1/hello");
    ok $tx->is_finished, 'transaction is finished';
    ok !$tx->keep_alive, 'connection will not be kept alive';
    ok !$tx->kept_alive, 'connection was not kept alive';
    is $tx->res->code, 200, 'right status';
    my $first = $tx->res->body;
    like $first, qr/Hello World \d+!/, 'right content';

    $tx = $ua->get("http://127.0.0.1:$port2/hello");
    ok $tx->is_finished, 'transaction is finished';
    ok !$tx->keep_alive, 'connection will not be kept alive';
    ok !$tx->kept_alive, 'connection was not kept alive';
    is $tx->res->code, 200,    'right status';
    is $tx->res->body, $first, 'same content';

    $tx = $ua->get("http://127.0.0.1:$port1/hello");
    ok $tx->is_finished, 'transaction is finished';
    ok !$tx->keep_alive, 'connection will not be kept alive';
    ok !$tx->kept_alive, 'connection was not kept alive';
    is $tx->res->code, 200, 'right status';
    my $second = $tx->res->body;
    isnt $first, $second, 'different content';
    like $second, qr/Hello World \d+!/, 'right content';

    $tx = $ua->get("http://127.0.0.1:$port2/hello");
    ok $tx->is_finished, 'transaction is finished';
    ok !$tx->keep_alive, 'connection will not be kept alive';
    ok !$tx->kept_alive, 'connection was not kept alive';
    is $tx->res->code, 200,     'right status';
    is $tx->res->body, $second, 'same content';
  };

  open my $stop, '-|', $^X, "$prefix/hypnotoad", $script, '-s';
  sleep 1 while _port($port2);

  subtest 'Check log' => sub {
    my $log = $log->slurp;
    like $log, qr/Worker \d+ started/,                                      'right message';
    like $log, qr/Starting zero downtime software upgrade \(180 seconds\)/, 'right message';
    like $log, qr/Upgrade successful, stopping $old/,                       'right message';
  };
};

sub _pid {
  my $path = shift;
  return undef unless open my $file, '<', $path;
  my $pid = <$file>;
  chomp $pid;
  return $pid;
}

sub _port { IO::Socket::INET->new(PeerAddr => '127.0.0.1', PeerPort => shift) }

done_testing();


Powered by Groonga
Maintained by Kenichi Ishigaki <ishigaki@cpan.org>. If you find anything, submit it on GitHub.