File Coverage

blib/lib/Unicorn/Manager/CLI.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1             package Unicorn::Manager::CLI;
2              
3 1     1   25892 use 5.010;
  1         5  
  1         85  
4 1     1   7 use strict;
  1         3  
  1         43  
5 1     1   5 use warnings;
  1         8  
  1         35  
6 1     1   1104 use autodie;
  1         28406  
  1         6  
7 1     1   8762 use Moo;
  1         17268  
  1         7  
8 1     1   1782 use Carp; # for sane error reporting
  1         2  
  1         93  
9 1     1   5 use File::Basename; # to strip the config file from the path
  1         2  
  1         93  
10 1     1   5 use File::Find;
  1         2  
  1         46  
11 1     1   4 use Cwd 'abs_path';
  1         2  
  1         34  
12              
13 1     1   549 use Unicorn::Manager::CLI::Proc;
  0            
  0            
14             use Unicorn::Manager::Version;
15              
16             has username => ( is => 'rw', required => 1 );
17             has group => ( is => 'rw' );
18             has config => ( is => 'rw' );
19             has DEBUG => ( is => 'rw' );
20             has proc => ( is => 'rw' );
21             has uid => ( is => 'rw' );
22             has rails => ( is => 'rw' );
23             has version => (
24             is => 'ro',
25             default => sub {
26             Unicorn::Manager::Version->new;
27             },
28             );
29              
30             sub start {
31             my ( $self, $opts ) = @_;
32             my $config_file = $opts->{config};
33             my $args = $opts->{args};
34             my $timeout = 20;
35             if ( -f $config_file ) {
36             if ( my $pid = fork() ) {
37             my $spawned = 0;
38             while ( $spawned == 0 && $timeout > 0 ) {
39             sleep 2;
40             $self->proc->refresh;
41             $spawned = 1 if $self->proc->process_table->ptable->{ $self->uid };
42             $timeout--;
43             }
44             croak "Failed to start unicorn. Timed out.\n" if $timeout <= 0;
45              
46             }
47             else {
48              
49             # 0 => name
50             # 2 => uid
51             # 3 => gid
52             # 7 => home dir
53             my @passwd = getpwnam( $self->username );
54              
55             # drop rights:
56             # group rights first because we can not drop group rights
57             # after user rights
58             # set $HOME to our users home directory
59             $ENV{'HOME'} = $passwd[7];
60             $( = $) = $passwd[3];
61             $< = $> = $passwd[2];
62              
63             my $appdir = '';
64             my $conf_file;
65             my $conf_dir;
66              
67             if ( defined $config_file && $config_file ne '' ) {
68             $conf_dir = dirname($config_file);
69             $conf_file = basename($config_file);
70              
71             if ( $self->_is_abspath($conf_dir) ) {
72             $appdir = $conf_dir;
73             }
74             else {
75             $appdir = abs_path($conf_dir);
76             }
77             }
78              
79             $self->_change_dir($appdir);
80              
81             my $argstring;
82              
83             $argstring .= $_ . ' ' for @{$args};
84              
85             $ENV{'RAILS_ENV'} = 'development' unless $ENV{'RAILS_ENV'};
86              
87             # spawn the unicorn
88             if ( $self->rails ) {
89              
90             # start unicorn_rails
91             exec "/bin/bash --login -c \"unicorn_rails -c $conf_file $argstring\"";
92             }
93             else {
94              
95             # start unicorn
96             exec "/bin/bash --login -c \"unicorn -c $conf_file $argstring\"";
97             }
98             }
99             }
100             else {
101             return 0;
102             }
103             return 1;
104             }
105              
106             sub query {
107              
108             # TODO
109             # Put all of this into Unicorn::Manager::CLI::Query or similar
110             my ( $self, $query, @params ) = @_;
111             my $render = sub {
112             my $status = shift;
113             my $message = shift;
114             my $data = shift;
115              
116             my $json = JSON->new->utf8(1);
117              
118             return $json->encode(
119             {
120             status => $status,
121             message => $message || undef,
122             data => $data || undef,
123             }
124             );
125             };
126              
127             my $dispatch_table = {
128             has_unicorn => sub {
129             my $user = shift @params;
130             return $render->( 0, 'no user defined' ) unless $user;
131             return $render->( 1, 'user has unicorn' );
132             },
133             running => sub {
134              
135             # refresh before querying
136             $self->proc->refresh;
137              
138             # TODO
139             # fix the encode->decode->encode
140             return $render->( 1, 'running unicorns', JSON::decode_json( $self->proc->as_json ) );
141             },
142             help => sub {
143             my $help = {
144             has_unicorn => {
145             description => 'return true or false',
146             params => ['username'],
147             },
148             running => {
149             description => 'return unicorn masters and children running for all users',
150             params => [],
151             },
152             };
153             return $render->( 1, 'uc.pl query options', $help );
154             },
155             };
156              
157             if ( exists $dispatch_table->{$query} ) {
158             $dispatch_table->{$query}->(@params);
159             }
160             else {
161             $dispatch_table->{help}->();
162             }
163              
164             }
165              
166             sub stop {
167             my $self = shift;
168             my $master = ( keys %{ $self->proc->process_table->ptable->{ $self->uid } } )[0];
169              
170             $self->_send_signal( 'QUIT', $master ) if $master;
171              
172             return 1;
173             }
174              
175             sub restart {
176             my ( $self, $opts ) = @_;
177             my $mode = $opts->{mode} || 'graceful';
178              
179             my @signals = ( 'USR2', 'WINCH', 'QUIT' );
180             my $master = ( keys %{ $self->proc->process_table->ptable->{ $self->uid } } )[0];
181              
182             my $err = 0;
183              
184             for (@signals) {
185             $err += $self->_send_signal( $_, $master );
186             sleep 5;
187             }
188              
189             if ( ( defined $mode && $mode eq 'hard' ) || $err ) {
190             $err = 0;
191             $err += $self->stop;
192             sleep 3;
193             $err += $self->start;
194             }
195              
196             if ($err) {
197             carp "error restarting unicorn! error code: $err\n";
198             return 0;
199             }
200             else {
201             return 1;
202             }
203             }
204              
205             sub reload {
206             my $self = shift;
207             my $err;
208              
209             for my $pid ( keys %{ $self->proc->process_table->ptable->{ $self->uid } } ) {
210             $err = $self->_send_signal( 'HUP', $pid );
211             }
212              
213             $err > 0 ? return 0 : return 1;
214             }
215              
216             sub read_config {
217             my $self = shift;
218             my $filename = shift;
219              
220             # TODO
221             # should return a config object
222             #
223             # all config related stuff should go into a seperate class anyway: Unicorn::Manager::CLI::Config
224             return 0;
225             }
226              
227             sub write_config {
228             my $self = shift;
229             my $filename = shift;
230              
231             # TODO
232             # this one wont be fun ..
233             # create a unicorn.conf from config hash
234             # this is basically ruby code, so an idea could be to build it from
235             # heredoc snippets
236             #
237             # should return a string. could be written to file or screen.
238             #
239             # all config related stuff should go into a seperate class anyway: Unicorn::Manager::CLI::Config
240             return 0;
241             }
242              
243             sub add_worker {
244             my ( $self, $opts ) = @_;
245             my $num = $opts->{num} || 1;
246              
247             # return error on non positive number
248             return 0 unless $num > 0;
249              
250             my $err = 0;
251              
252             for ( 1 .. $num ) {
253             my $master = ( keys %{ $self->proc->process_table->ptable->{ $self->uid } } )[0];
254              
255             $err += $self->_send_signal( 'TTIN', $master );
256             }
257              
258             $err > 0 ? return 0 : return 1;
259             }
260              
261             sub remove_worker {
262             my ( $self, $opts ) = @_;
263             my $num = $opts->{num} || 1;
264              
265             # return error on non positive number
266             return 0 unless $num > 0;
267              
268             my $err = 0;
269             my $master = ( keys %{ $self->proc->process_table->ptable->{ $self->uid } } )[0];
270             my $count = @{ $self->proc->process_table->ptable->{ $self->uid }->{$master} };
271              
272             # save at least one worker
273             $num = $count - 1 if $num >= $count;
274              
275             if ( $self->DEBUG ) {
276             print "\$count => $count\n";
277             print "\$num => $num\n";
278             }
279              
280             for ( 1 .. $num ) {
281             $err += $self->_send_signal( 'TTOU', $master );
282             }
283              
284             $err > 0 ? return 0 : return 1;
285             }
286              
287             #
288             # send a signal to a pid
289             #
290             sub _send_signal {
291             my ( $self, $signal, $pid ) = @_;
292             ( kill $signal => $pid ) ? return 0 : return 1;
293             }
294              
295             #
296             # small piece to check if a path is starting at root
297             #
298             sub _is_abspath {
299             my ( $self, $path ) = @_;
300             return 0 unless $path =~ /^\//;
301             return 1;
302             }
303              
304             #
305             # cd into the given dir
306             # requires an absolute path
307             #
308             sub _change_dir {
309             my ( $self, $dir ) = @_;
310              
311             # requires abs path
312             return 0 unless $self->_is_abspath($dir);
313              
314             my $dh;
315              
316             opendir $dh, $dir;
317             chdir $dh;
318             closedir $dh;
319              
320             use Cwd;
321              
322             cwd() eq $dir ? return 1 : return 0;
323             }
324              
325             sub BUILD {
326             my $self = shift;
327              
328             # does username exist?
329             if ( $self->DEBUG ) {
330             print "Initializing object with username: " . $self->username . "\n";
331             }
332             croak "no such username\n" unless getpwnam( $self->username );
333              
334             $self->uid( ( getpwnam( $self->username ) )[2] );
335             $self->proc( Unicorn::Manager::CLI::Proc->new ) unless $self->proc;
336              
337             }
338              
339             1;
340              
341             __END__