| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Couchbase::MockServer; | 
| 2 | 2 |  |  | 2 |  | 8 | use strict; | 
|  | 2 |  |  |  |  | 2 |  | 
|  | 2 |  |  |  |  | 62 |  | 
| 3 | 2 |  |  | 2 |  | 8 | use warnings; | 
|  | 2 |  |  |  |  | 2 |  | 
|  | 2 |  |  |  |  | 38 |  | 
| 4 | 2 |  |  | 2 |  | 1054 | use IO::Socket::INET; | 
|  | 2 |  |  |  |  | 37150 |  | 
|  | 2 |  |  |  |  | 12 |  | 
| 5 | 2 |  |  | 2 |  | 830 | use Socket; | 
|  | 2 |  |  |  |  | 2 |  | 
|  | 2 |  |  |  |  | 1338 |  | 
| 6 | 2 |  |  | 2 |  | 984 | use POSIX qw(:errno_h :signal_h :sys_wait_h); | 
|  | 2 |  |  |  |  | 9464 |  | 
|  | 2 |  |  |  |  | 30 |  | 
| 7 | 2 |  |  | 2 |  | 3584 | use Log::Fu { level => "warn" }; | 
|  | 2 |  |  |  |  | 48622 |  | 
|  | 2 |  |  |  |  | 14 |  | 
| 8 | 2 |  |  | 2 |  | 478 | use Data::Dumper; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 104 |  | 
| 9 | 2 |  |  | 2 |  | 1034 | use Time::HiRes qw(sleep); | 
|  | 2 |  |  |  |  | 2396 |  | 
|  | 2 |  |  |  |  | 6 |  | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | my $SYMLINK = "CouchbaseMock_PLTEST.jar"; | 
| 12 |  |  |  |  |  |  | our $INSTANCE; | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | use Class::XSAccessor { | 
| 15 | 2 |  |  |  |  | 12 | constructor => '_real_new', | 
| 16 |  |  |  |  |  |  | accessors => [qw( | 
| 17 |  |  |  |  |  |  | pid | 
| 18 |  |  |  |  |  |  | jarfile | 
| 19 |  |  |  |  |  |  | nodes | 
| 20 |  |  |  |  |  |  | buckets | 
| 21 |  |  |  |  |  |  | vbuckets | 
| 22 |  |  |  |  |  |  | harakiri_socket | 
| 23 |  |  |  |  |  |  | port | 
| 24 |  |  |  |  |  |  | )] | 
| 25 | 2 |  |  | 2 |  | 1200 | }; | 
|  | 2 |  |  |  |  | 3744 |  | 
| 26 |  |  |  |  |  |  | # This is the couchbase mock server, it will attempt to download, spawn, and | 
| 27 |  |  |  |  |  |  | # otherwise control the java-based CouchbaseMock server. | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | sub _accept_harakiri { | 
| 31 | 1 |  |  | 1 |  | 6 | my $self = shift; | 
| 32 | 1 |  |  |  |  | 62 | $self->harakiri_socket->blocking(0); | 
| 33 | 1 |  |  |  |  | 49 | my $begin_time = time(); | 
| 34 | 1 |  |  |  |  | 5 | my $max_wait = 5; | 
| 35 | 1 |  |  |  |  | 3 | my $got_accept = 0; | 
| 36 |  |  |  |  |  |  |  | 
| 37 | 1 |  |  |  |  | 9 | while(time - $begin_time < $max_wait) { | 
| 38 | 42 |  |  |  |  | 973 | my $sock = $self->harakiri_socket->accept(); | 
| 39 | 42 | 50 |  |  |  | 11819 | if($sock) { | 
| 40 | 0 |  |  |  |  | 0 | $sock->blocking(1); | 
| 41 | 0 |  |  |  |  | 0 | $self->harakiri_socket($sock); | 
| 42 | 0 |  |  |  |  | 0 | $got_accept = 1; | 
| 43 | 0 |  |  |  |  | 0 | log_info("Got harakiri connection"); | 
| 44 | 0 |  |  |  |  | 0 | my $buf = ""; | 
| 45 | 0 |  |  |  |  | 0 | $self->harakiri_socket->recv($buf, 100, 0); | 
| 46 | 0 | 0 |  |  |  | 0 | if(defined $buf) { | 
| 47 | 0 |  |  |  |  | 0 | my ($port) = ($buf =~ /(\d+)/); | 
| 48 | 0 |  |  |  |  | 0 | $self->port($port); | 
| 49 |  |  |  |  |  |  | } else { | 
| 50 | 0 |  |  |  |  | 0 | die("Couldn't get port"); | 
| 51 |  |  |  |  |  |  | } | 
| 52 | 0 |  |  |  |  | 0 | last; | 
| 53 |  |  |  |  |  |  | } else { | 
| 54 | 42 |  |  |  |  | 4207261 | sleep(0.1); | 
| 55 |  |  |  |  |  |  | } | 
| 56 |  |  |  |  |  |  | } | 
| 57 | 1 | 50 |  |  |  | 15 | if(!$got_accept) { | 
| 58 | 1 |  |  |  |  | 61 | die("Could not establish harakiri control connection"); | 
| 59 |  |  |  |  |  |  | } | 
| 60 |  |  |  |  |  |  | } | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | sub _do_run { | 
| 63 | 2 |  |  | 2 |  | 4 | my $self = shift; | 
| 64 | 2 |  |  |  |  | 2 | my @command; | 
| 65 | 2 |  |  |  |  | 8 | push @command, "java", "-jar", $self->jarfile; | 
| 66 |  |  |  |  |  |  |  | 
| 67 | 2 |  |  |  |  | 2 | my $buckets_arg = "--buckets="; | 
| 68 |  |  |  |  |  |  |  | 
| 69 | 2 |  |  |  |  | 4 | foreach my $bucket (@{$self->buckets}) { | 
|  | 2 |  |  |  |  | 8 |  | 
| 70 | 2 |  |  |  |  | 2 | my ($name,$password,$type) = @{$bucket}{qw(name password type)}; | 
|  | 2 |  |  |  |  | 6 |  | 
| 71 | 2 |  | 50 |  |  | 6 | $name ||= ""; | 
| 72 | 2 |  | 50 |  |  | 12 | $password ||= ""; | 
| 73 | 2 |  | 50 |  |  | 6 | $type ||= ""; | 
| 74 | 2 | 50 | 33 |  |  | 24 | if($type && $type ne "couchbase" && $type ne "memcache") { | 
|  |  |  | 33 |  |  |  |  | 
| 75 | 0 |  |  |  |  | 0 | die("type for bucket must be either 'couchbase' or 'memcache'"); | 
| 76 |  |  |  |  |  |  | } | 
| 77 | 2 |  |  |  |  | 6 | my $spec = join(":", $name, $password, $type); | 
| 78 | 2 |  |  |  |  | 8 | $buckets_arg .= $spec . ","; | 
| 79 |  |  |  |  |  |  | } | 
| 80 |  |  |  |  |  |  |  | 
| 81 | 2 |  |  |  |  | 10 | $buckets_arg =~ s/,$//g; | 
| 82 |  |  |  |  |  |  |  | 
| 83 | 2 |  |  |  |  | 4 | push @command, $buckets_arg; | 
| 84 |  |  |  |  |  |  |  | 
| 85 | 2 |  |  |  |  | 4 | push @command, "--port=0"; | 
| 86 |  |  |  |  |  |  |  | 
| 87 | 2 | 50 |  |  |  | 8 | if($self->nodes) { | 
| 88 | 2 |  |  |  |  | 10 | push @command, "--nodes=" . $self->nodes; | 
| 89 |  |  |  |  |  |  | } | 
| 90 |  |  |  |  |  |  |  | 
| 91 | 2 |  |  |  |  | 20 | my $sock = IO::Socket::INET->new(Listen => 5); | 
| 92 | 2 |  |  |  |  | 556 | $self->harakiri_socket($sock); | 
| 93 | 2 |  |  |  |  | 12 | my $port = $self->harakiri_socket->sockport; | 
| 94 | 2 |  |  |  |  | 74 | log_infof("Listening on %d for harakiri", $port); | 
| 95 | 2 |  |  |  |  | 76 | push @command, "--harakiri-monitor=localhost:$port"; | 
| 96 |  |  |  |  |  |  |  | 
| 97 | 2 |  |  |  |  | 2053 | my $pid = fork(); | 
| 98 |  |  |  |  |  |  |  | 
| 99 | 2 | 100 |  |  |  | 99 | if($pid) { | 
| 100 |  |  |  |  |  |  | #Parent: setup harakiri monitoring socket | 
| 101 | 1 |  |  |  |  | 50311 | sleep(0.05); | 
| 102 | 1 | 50 |  |  |  | 88 | if(waitpid($pid, WNOHANG) > 0) { | 
| 103 | 0 |  |  |  |  | 0 | die("Child process died prematurely"); | 
| 104 |  |  |  |  |  |  | } | 
| 105 | 1 |  |  |  |  | 38 | log_info("Launched CouchbaseMock PID=$pid"); | 
| 106 |  |  |  |  |  |  | #$self->pid(getpgrp($pid)); | 
| 107 | 1 |  |  |  |  | 96 | $self->pid($pid); | 
| 108 | 1 |  |  |  |  | 23 | $self->_accept_harakiri(); | 
| 109 |  |  |  |  |  |  | } else { | 
| 110 |  |  |  |  |  |  |  | 
| 111 | 1 |  |  |  |  | 55 | setpgrp(0, 0); | 
| 112 | 1 |  |  |  |  | 87 | log_warnf("Executing %s", join(" ", @command)); | 
| 113 | 1 |  |  |  |  | 0 | exec(@command); | 
| 114 | 0 |  |  |  |  | 0 | warn"exec @command failed: $!"; | 
| 115 | 0 |  |  |  |  | 0 | exit(1); | 
| 116 |  |  |  |  |  |  | } | 
| 117 |  |  |  |  |  |  | } | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | sub new { | 
| 120 | 2 |  |  | 2 | 0 | 6 | my ($cls,%opts) = @_; | 
| 121 | 2 | 50 |  |  |  | 8 | if($INSTANCE) { | 
| 122 | 0 |  |  |  |  | 0 | log_warn("Returning cached instance"); | 
| 123 | 0 |  |  |  |  | 0 | return $INSTANCE; | 
| 124 |  |  |  |  |  |  | } | 
| 125 |  |  |  |  |  |  |  | 
| 126 | 2 | 50 |  |  |  | 8 | unless(exists $opts{jarfile}) { | 
| 127 | 0 |  |  |  |  | 0 | die("Must have path to JAR"); | 
| 128 |  |  |  |  |  |  | } | 
| 129 | 2 |  |  |  |  | 18 | my $o = $cls->_real_new(%opts); | 
| 130 | 2 |  |  |  |  | 10 | my $file = $o->jarfile; | 
| 131 | 2 | 50 |  |  |  | 26 | if(!-e $file) { | 
| 132 | 0 |  |  |  |  | 0 | die("Cannot find $file"); | 
| 133 |  |  |  |  |  |  | } | 
| 134 |  |  |  |  |  |  |  | 
| 135 | 2 |  |  |  |  | 10 | $o->_do_run(); | 
| 136 | 0 |  |  |  |  | 0 | $INSTANCE = $o; | 
| 137 | 0 |  |  |  |  | 0 | return $o; | 
| 138 |  |  |  |  |  |  | } | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  | sub GetInstance { | 
| 141 | 0 |  |  | 0 | 0 | 0 | my $cls = shift; | 
| 142 | 0 |  |  |  |  | 0 | return $INSTANCE; | 
| 143 |  |  |  |  |  |  | } | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | sub suspend_process { | 
| 146 | 0 |  |  | 0 | 0 | 0 | my $self = shift; | 
| 147 | 0 |  |  |  |  | 0 | my $pid = $self->pid; | 
| 148 | 0 | 0 |  |  |  | 0 | return unless defined $pid; | 
| 149 | 0 |  |  |  |  | 0 | kill SIGSTOP, -(getpgrp($pid)); | 
| 150 |  |  |  |  |  |  | } | 
| 151 |  |  |  |  |  |  | sub resume_process { | 
| 152 | 0 |  |  | 0 | 0 | 0 | my $self = shift; | 
| 153 | 0 |  |  |  |  | 0 | my $pid = $self->pid; | 
| 154 | 0 | 0 |  |  |  | 0 | return unless defined $pid; | 
| 155 | 0 |  |  |  |  | 0 | kill SIGCONT, -(getpgrp($pid)); | 
| 156 |  |  |  |  |  |  | } | 
| 157 |  |  |  |  |  |  |  | 
| 158 |  |  |  |  |  |  | sub failover_node { | 
| 159 | 0 |  |  | 0 | 0 | 0 | my ($self,$nodeidx,$bucket_name) = @_; | 
| 160 | 0 |  | 0 |  |  | 0 | $bucket_name ||= "default"; | 
| 161 | 0 |  |  |  |  | 0 | my $cmd = "failover,$nodeidx,$bucket_name\n"; | 
| 162 | 0 |  |  |  |  | 0 | log_warn($cmd); | 
| 163 | 0 | 0 |  |  |  | 0 | $self->harakiri_socket->send($cmd, 0) or die "Couldn't send"; | 
| 164 |  |  |  |  |  |  | } | 
| 165 |  |  |  |  |  |  |  | 
| 166 |  |  |  |  |  |  | sub respawn_node { | 
| 167 | 0 |  |  | 0 | 0 | 0 | my ($self,$nodeidx,$bucket_name) = @_; | 
| 168 | 0 |  | 0 |  |  | 0 | $bucket_name ||= "default"; | 
| 169 | 0 |  |  |  |  | 0 | my $cmd = "respawn,$nodeidx,$bucket_name\n"; | 
| 170 | 0 |  |  |  |  | 0 | log_warn($cmd); | 
| 171 | 0 | 0 |  |  |  | 0 | $self->harakiri_socket->send($cmd, 0) or die "Couldn't send"; | 
| 172 |  |  |  |  |  |  | } | 
| 173 |  |  |  |  |  |  |  | 
| 174 |  |  |  |  |  |  | sub DESTROY { | 
| 175 | 1 |  |  | 1 |  | 51 | my $self = shift; | 
| 176 | 1 | 50 |  |  |  | 12 | return unless $self->pid; | 
| 177 | 1 |  |  |  |  | 27 | kill SIGTERM, $self->pid; | 
| 178 | 1 |  |  |  |  | 8 | log_debugf("Waiting for process to terminate"); | 
| 179 | 1 |  |  |  |  | 139 | waitpid($self->pid, 0); | 
| 180 | 1 |  |  |  |  | 13 | log_infof("Reaped PID %d, status %d", $self->pid, $? >> 8); | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | } | 
| 183 |  |  |  |  |  |  |  | 
| 184 |  |  |  |  |  |  | 1; |