File Coverage

lib/ControlFreak.pm
Criterion Covered Total %
statement 108 278 38.8
branch 19 60 31.6
condition 8 57 14.0
subroutine 24 67 35.8
pod 19 35 54.2
total 178 497 35.8


line stmt bran cond sub pod time code
1             package ControlFreak;
2              
3 8     8   220538 use strict;
  8         18  
  8         288  
4 8     8   200 use 5.008_001;
  8         24  
  8         1632  
5             our $VERSION = '1.0.0'; ## http://semver.org
6              
7 8         49 use Object::Tiny qw{
8             log
9             console
10             home
11 8     8   7351 };
  8         3034  
12              
13 8     8   1566 use Carp;
  8         18  
  8         814  
14 8     8   4170 use ControlFreak::Command;
  8         36  
  8         415  
15 8     8   5103 use ControlFreak::Logger;
  8         29  
  8         90  
16 8     8   251 use ControlFreak::Service;
  8         17  
  8         78  
17 8     8   5635 use ControlFreak::Proxy;
  8         23  
  8         103  
18 8     8   358 use File::Spec();
  8         16  
  8         152  
19 8     8   39 use Params::Util qw{ _ARRAY _CODE };
  8         17  
  8         32042  
20              
21             our $CRLF = "\015\012";
22              
23             =encoding utf8
24              
25             =head1 NAME
26              
27             ControlFreak - a process supervisor
28              
29             =head1 SYNOPSIS
30              
31             ## WARNING
32             ## see L and L manpages for how to run ControlFreak from
33             ## the shell. This is the programatic interface used by these scripts.
34              
35             $ctrl = ControlFreak->new(
36             log_config_file => $log_config_file,
37             );
38             $ctrl->run; # enter the event loop, returns only for exiting
39              
40             ## elsewhere in the eventloop
41             $ctrl->add_socket($sock);
42             $sock = $ctrl->socket($sockname);
43              
44             $svc = $ctrl->find_or_create($svcname);
45             $ctrl->add_service($svc);
46             $svc = $ctrl->service($svcname);
47              
48             @svcs = $ctrl->service_by_tag($tag);
49             @svcs = $ctrl->services;
50              
51             $ctrl->destroy($svcname);
52              
53             $ctrl->set_console($con);
54             $con = $ctrl->console;
55             $log = $ctrl->log;
56              
57             =head1 DESCRIPTION
58              
59             This is the programmer documentation. Look into L
60             for user documentation.
61              
62             =head1 METHODS
63              
64             =head2 new(%param)
65              
66             =over 4
67              
68             =item * config
69              
70             The absolute path to a initial config file.
71              
72             =back
73              
74             =cut
75              
76             sub new {
77 14     14 1 27617 my $class = shift;
78 14         62 my %param = @_;
79 14         167 my $ctrl = $class->SUPER::new(%param);
80              
81 14         198 my $base = $ctrl->{base} = $param{base};
82              
83 14         51 $ctrl->{servicemap} = {};
84 14         43 $ctrl->{socketmap} = {};
85 14         47 $ctrl->{proxymap} = {};
86              
87 14         29 my $log_config_file;
88 14         32 my $home = $param{home};
89 14 50 33     83 $log_config_file = File::Spec->rel2abs($param{log_config_file}, $home)
90             if defined $param{log_config_file} && $home;
91              
92 14         126 $ctrl->{log} = ControlFreak::Logger->new(
93             config_file => $log_config_file,
94             );
95              
96 14         65 return $ctrl;
97             }
98              
99             =head2 services
100              
101             Returns a list of L instances known to this
102             controller.
103              
104             =cut
105              
106             sub services {
107 5     5 1 1106 my $ctrl = shift;
108 5         9 return values %{ $ctrl->{servicemap} };
  5         26  
109             }
110              
111             =head2 sockets
112              
113             Returns a list of L instances known to this
114             controller.
115              
116             =cut
117              
118             sub sockets {
119 0     0 1 0 my $ctrl = shift;
120 0         0 return values %{ $ctrl->{socketmap} };
  0         0  
121             }
122              
123             =head2 service($name)
124              
125             Returns the service of name C<$name> or nothing.
126              
127             =cut
128              
129             sub service {
130 9     9 1 363 my $ctrl = shift;
131 9 50       34 my ($svcname) = shift or return;
132 9         42 return $ctrl->{servicemap}{$svcname};
133             }
134              
135             =head2 proxy($name)
136              
137             Returns the proxy of name C<$name> or nothing.
138              
139             =cut
140              
141             sub proxy {
142 1     1 1 1048 my $ctrl = shift;
143 1 50       6 my ($proxyname) = shift or return;
144 1         8 return $ctrl->{proxymap}{$proxyname};
145             }
146              
147             =head2 set_console
148              
149             Takes a L instance in parameter and sets it
150             as the console.
151              
152             =cut
153              
154             sub set_console {
155 1     1 1 3 my $ctrl = shift;
156 1         2 my $con = shift;
157              
158 1         4 $ctrl->{console} = $con;
159 1         4 return;
160             }
161              
162             =head2 socket($name)
163              
164             Returns the L object of name C<$name> or returns
165             undef.
166              
167             =cut
168              
169             sub socket {
170 3     3 1 11 my $ctrl = shift;
171 3   50     19 my $name = shift || "";
172 3         21 return $ctrl->{socketmap}->{$name};
173             }
174              
175             =head2 add_socket($socket)
176              
177             Adds the C<$socket> L object passed in parameters
178             to the list of socket this controller knows about.
179              
180             If a socket by that name already exists, it returns undef, otherwise
181             it returns a true value;
182              
183             =cut
184              
185             sub add_socket {
186 4     4 1 10 my $ctrl = shift;
187 4         6 my $socket = shift;
188              
189 4   50     123 my $name = $socket->name || "";
190 4 100       38 return if $ctrl->{socketmap}->{$name};
191 3         9 $ctrl->{socketmap}->{$name} = $socket;
192 3         13 return 1;
193             }
194              
195             =head2 remove_socket($socket_name)
196              
197             Removes the L object by the name of C<$socket_name>
198             from the list of sockets this controller knows about.
199              
200             Returns true if effectively removed.
201              
202             =cut
203              
204             sub remove_socket {
205 2     2 1 1951 my $ctrl = shift;
206 2         5 my $socket_name = shift;
207 2         13 return delete $ctrl->{socketmap}->{$socket_name};
208             }
209              
210             =head2 add_proxy($proxy)
211              
212             Adds the C<$proxy> L object passed in parameters
213             to the list of proxies this controller knows about.
214              
215             If a proxy by that name already exists, it returns undef, otherwise
216             it returns a true value;
217              
218             =cut
219              
220             sub add_proxy {
221 3     3 1 5 my $ctrl = shift;
222 3         5 my $proxy = shift;
223              
224 3   50     145 my $name = $proxy->name || "";
225 3 50       26 return if $ctrl->{proxymap}->{$name};
226 3         8 $ctrl->{proxymap}->{$name} = $proxy;
227 3         13 return 1;
228             }
229              
230             =head2 remove_proxy($proxy_name)
231              
232             Removes the L object by the name of C<$proxy_name>
233             from the list of proxies this controller knows about.
234              
235             Returns true if effectively removed.
236              
237             =cut
238              
239             sub remove_proxy {
240 0     0 1 0 my $ctrl = shift;
241 0         0 my $proxy_name = shift;
242 0         0 return delete $ctrl->{proxymap}->{$proxy_name};
243             }
244              
245             =head2 proxies
246              
247             Returns a list of proxy objects.
248              
249             =cut
250              
251             sub proxies {
252 0     0 1 0 my $ctrl = shift;
253 0         0 return values %{ $ctrl->{proxymap} };
  0         0  
254             }
255              
256             =head2 find_or_create_svc($name)
257              
258             Given a service name in parameter (a string), searches for an existing
259             defined service with that name, if not found, then a new service is
260             declared and returned.
261              
262             =cut
263              
264             sub find_or_create_svc {
265 35     35 1 6023 my $ctrl = shift;
266 35         110 my $svcname = shift;
267 35         128 my $svc = $ctrl->{servicemap}{$svcname};
268 35 100       167 return $svc if $svc;
269              
270 23         340 $svc = ControlFreak::Service->new(
271             name => $svcname,
272             state => 'stopped',
273             ctrl => $ctrl,
274             );
275 23 100       117 return unless $svc;
276              
277 22         190 return $ctrl->{servicemap}{$svcname} = $svc;
278             }
279              
280             =head2 find_or_create_sock($name)
281              
282             Given a socket name in parameter (a string), searches for an existing
283             defined socket with that name, if not found, then a new socket is
284             declared and returned.
285              
286             =cut
287              
288             sub find_or_create_sock {
289 0     0 1 0 my $ctrl = shift;
290 0         0 my $sockname = shift;
291 0         0 my $sock = $ctrl->{socketmap}{$sockname};
292 0 0       0 return $sock if $sock;
293              
294 0         0 $sock = ControlFreak::Socket->new(
295             name => $sockname,
296             ctrl => $ctrl,
297             );
298 0 0       0 return unless $sock;
299              
300 0         0 return $ctrl->{socketmap}{$sockname} = $sock;
301             }
302              
303             =head2 find_or_create_proxy($name)
304              
305             Given a proxy name in parameter (a string), searches for an existing
306             defined proxy with that name, if not found, then a new proxy is
307             declared and returned.
308              
309             =cut
310              
311             sub find_or_create_proxy {
312 2     2 1 5 my $ctrl = shift;
313 2         4 my $proxyname = shift;
314 2         7 my $proxy = $ctrl->{proxymap}{$proxyname};
315 2 100       18 return $proxy if $proxy;
316              
317 1         7 $proxy = ControlFreak::Proxy->new(
318             name => $proxyname,
319             ctrl => $ctrl,
320             );
321 1 50       4 return unless $proxy;
322              
323 1         5 return $ctrl->{proxymap}{$proxyname} = $proxy;
324             }
325              
326             =head2 logger
327              
328             Returns the logger attached to the controller.
329              
330             =cut
331              
332             =head2 services_by_tag($tag)
333              
334             Given a tag in parameter, returns a list of matching service objects.
335              
336             =cut
337              
338             sub services_by_tag {
339 3     3 1 16 my $ctrl = shift;
340 3         5 my $tag = shift;
341 3         9 return grep { $_->tags->{$tag} } $ctrl->services;
  6         151  
342             }
343              
344             =head2 services_from_args(%param)
345              
346             Given a list of arguments (typically from the console commands)
347             returns a list of L instances.
348              
349             =over 4
350              
351             =item * args
352              
353             The list of arguments to analyze.
354              
355             =item * err
356              
357             A callback called with the parsing errors of the arguments.
358              
359             =back
360              
361             =cut
362              
363             sub services_from_args {
364 2     2 1 4 my $ctrl = shift;
365 2         8 my %param = @_;
366              
367 2   100 0   20 my $err = _CODE($param{err_cb}) || sub {};
  0         0  
368 2 50       12 my $args = _ARRAY($param{args})
369             or return ();
370              
371 2         5 my $selector = shift @$args;
372 2 50       15 if ($selector eq 'service') {
    100          
    50          
373 0 0       0 unless (scalar @$args == 1) {
374 0         0 $err->('service selector takes exactly 1 argument: name');
375 0         0 return ();
376             }
377 0         0 my $name = shift @$args;
378 0         0 my $svc = $ctrl->service($name);
379 0 0       0 return $svc ? ($svc) : ();
380             }
381             elsif ($selector eq 'tag') {
382 1         5 return $ctrl->services_by_tag(shift @$args);
383             }
384             elsif ($selector eq 'all') {
385 1         10 return $ctrl->services;
386             }
387             else {
388 0         0 $err->("unknown selector '$selector'");
389             }
390 0         0 return ();
391             }
392              
393              
394             =head2 command_*
395              
396             All accessible commands to the config and the console.
397              
398             =cut
399              
400 0     0 0 0 sub command_start { _command_ctrl('start', @_ ) }
401 0     0 0 0 sub command_stop { _command_ctrl('stop', @_ ) }
402 0     0 0 0 sub command_restart { _command_ctrl('restart', @_ ) }
403 0     0 0 0 sub command_down { _command_ctrl('down', @_ ) }
404 0     0 0 0 sub command_up { _command_ctrl('up', @_ ) }
405              
406             sub _command_ctrl {
407 0     0   0 my $meth = shift;
408 0         0 my $ctrl = shift;
409 0         0 my %param = @_;
410              
411 0   0 0   0 my $err = _CODE($param{err_cb}) || sub {};
  0         0  
412 0   0 0   0 my $ok = _CODE($param{ok_cb}) || sub {};
  0         0  
413 0         0 my @svcs = $ctrl->services_from_args(
414             %param, err_cb => $err, ok_cb => $ok,
415             );
416 0 0       0 if (! @svcs) {
417 0         0 return $err->("Couldn't find a valid service. bailing.");
418             }
419 0         0 my $n = 0;
420 0         0 for (@svcs) {
421 0     0   0 $_->$meth(err_cb => $err, ok_cb => sub { $n++ });
  0         0  
422             }
423 0         0 $ok->("done $n");
424 0         0 return;
425             }
426              
427             ## for now, at least this is separated.
428             ## but could we imagine a command start all running proxies as well?
429             sub command_proxyup {
430 0     0 0 0 my $ctrl = shift;
431 0         0 my %param = @_;
432              
433 0   0 0   0 my $err = _CODE($param{err_cb}) || sub {};
  0         0  
434 0   0 0   0 my $ok = _CODE($param{ok_cb}) || sub {};
  0         0  
435              
436 0         0 my $proxyname = $param{args}[0];
437              
438 0   0     0 my $proxy = $ctrl->proxy($proxyname || "");
439 0 0       0 if (! $proxy) {
440 0         0 return $err->("Couldn't find a valid proxy. bailing.");
441             }
442 0         0 $proxy->run;
443 0         0 $ok->();
444 0         0 return;
445             }
446              
447             sub command_proxydown {
448 0     0 0 0 my $ctrl = shift;
449 0         0 my %param = @_;
450              
451 0   0 0   0 my $err = _CODE($param{err_cb}) || sub {};
  0         0  
452 0   0 0   0 my $ok = _CODE($param{ok_cb}) || sub {};
  0         0  
453              
454 0         0 my $proxyname = $param{args}[0];
455              
456 0   0     0 my $proxy = $ctrl->proxy($proxyname || "");
457 0 0       0 if (! $proxy) {
458 0         0 return $err->("Couldn't find a valid proxy. bailing.");
459             }
460 0         0 $proxy->shutdown;
461 0         0 $ok->();
462 0         0 return;
463             }
464              
465             sub command_list {
466 0     0 0 0 my $ctrl = shift;
467 0         0 my %param = @_;
468 0   0 0   0 my $ok = _CODE($param{ok_cb}) || sub {};
  0         0  
469 0         0 my @out = map { $_->name } $ctrl->services;
  0         0  
470 0         0 $ok->(join "\n", @out);
471             }
472              
473             sub command_desc {
474 0     0 0 0 my $ctrl = shift;
475 0         0 my %param = @_;
476              
477 0   0 0   0 my $ok = _CODE($param{ok_cb}) || sub {};
  0         0  
478              
479 0   0     0 my $args = $param{args} || [ 'all' ];
480 0 0       0 $args = ['all'] unless @$args;
481              
482 0         0 my @svcs = $ctrl->services_from_args(
483             %param, ok_cb => $ok,
484             );
485 0         0 my @out = map { $_->desc_as_text } @svcs;
  0         0  
486 0         0 $ok->(join "\n", @out);
487             }
488              
489             sub command_version {
490 0     0 0 0 my $ctrl = shift;
491 0         0 my %param = @_;
492 0   0 0   0 my $ok = _CODE($param{ok_cb}) || sub {};
  0         0  
493 0         0 $ok->($VERSION);
494             }
495              
496             sub command_status {
497 1     1 0 2 my $ctrl = shift;
498 1         5 my %param = @_;
499              
500 1   50 0   20 my $ok = _CODE($param{ok_cb}) || sub {};
  0            
501              
502 1   50     9 my $args = $param{args} || [ 'all' ];
503 1 50       3 $args = ['all'] unless @$args;
504 1         7 my @svcs = $ctrl->services_from_args(%param, args => $args);
505              
506 1         2 my @out;
507 1         3 for (@svcs) {
508 0         0 push @out, $_->status_as_text;
509             }
510 1         5 $ok->(join "\n", @out);
511             }
512              
513             sub command_pids {
514 0     0 0   my $ctrl = shift;
515 0           my %param = @_;
516              
517 0   0 0     my $ok = _CODE($param{ok_cb}) || sub {};
  0            
518              
519 0   0       my $args = $param{args} || [ 'all' ];
520 0 0         $args = ['all'] unless @$args;
521 0           my @svcs = $ctrl->services_from_args(%param, args => $args);
522 0           my %seen;
523             my @out;
524 0           for (@svcs) {
525 0           my $svcname = $_->name;
526 0 0         next if $seen{$svcname}++;
527 0           my @pids = ($_->pid);
528 0 0         if (my $proxy = $_->proxy) {
529 0           my $ppid = $proxy->pid;
530 0 0         unshift @pids, $ppid if $ppid;
531             }
532 0           push @out, "$svcname: " . join (", ", @pids);
533             }
534 0           $ok->(join "\n", @out);
535             }
536              
537             sub command_proxystatus {
538 0     0 0   my $ctrl = shift;
539 0           my %param = @_;
540              
541 0   0 0     my $ok = _CODE($param{ok_cb}) || sub {};
  0            
542 0           my @proxies = $ctrl->proxies;
543 0           my @out;
544 0           for my $p ($ctrl->proxies) {
545 0           push @out, $p->status_as_text;
546             }
547 0           $ok->(join "\n", @out);
548             }
549              
550             sub command_bind {
551 0     0 0   my $ctrl = shift;
552 0           my %param = @_;
553 0   0       my $args = $param{args} || [];
554 0   0 0     my $err = _CODE($param{err_cb}) || sub {};
  0            
555 0   0 0     my $ok = _CODE($param{ok_cb}) || sub {};
  0            
556 0   0       my $sockname = shift @$args || "";
557 0           my $sock = $ctrl->socket($sockname);
558 0 0         unless ($sock) {
559 0           return $err->("unknown socket '$sockname'");
560             }
561 0           $sock->bind();
562 0           $ok->();
563 0           return;
564             }
565              
566             sub command_shutdown {
567             ## I'm tired of killing my procs.
568             ## might not stay in the future
569 0     0 0   my $ctrl = shift;
570 0           $ctrl->shutdown;
571 0     0     $ctrl->{exit_cv} = AE::timer 1, 0, sub { exit };
  0            
572             }
573              
574             sub command_destroy {
575 0     0 0   my $ctrl = shift;
576 0           my %param = @_;
577              
578 0   0 0     my $err = _CODE($param{err_cb}) || sub {};
  0            
579 0   0 0     my $ok = _CODE($param{ok_cb}) || sub {};
  0            
580              
581 0           my @svcs = $ctrl->services_from_args(
582             %param, err_cb => $err, ok_cb => $ok,
583             );
584 0           my %errors;
585 0           for my $svc (@svcs) {
586 0           my $svcname = $svc->name;
587             $svc->down(
588 0     0     on_stop => sub { $ctrl->destroy($svc) },
589             err_cb => sub {
590 0     0     $errors{$svcname}++;
591             },
592 0           );
593             }
594 0 0         if (keys %errors) {
595 0           my $list = join ", ", keys %errors;
596 0           $err->("Coudn't destroy: $list");
597             }
598             else {
599 0           return $ok->()
600             }
601 0           return;
602             }
603              
604             =head2 destroy($svc)
605              
606             Removes any reference to $svc in the controller. The concerned
607             service must be down in the first place.
608              
609             =cut
610              
611             sub destroy {
612 0     0 1   my $ctrl = shift;
613 0           my $svc = shift;
614 0           my $svcname = $svc->name;
615 0 0         return unless $svc->is_down;
616 0 0         if ($svc->is_backoff) {
617 0           $svc->stop;
618             }
619 0           $ctrl->log->info("Destroying service '$svcname'");
620 0           return delete $ctrl->{servicemap}{$svcname};
621             }
622              
623             =head2 shutdown
624              
625             Cleanly exits all running commands, close all sockets etc...
626              
627             =cut
628              
629             sub shutdown {
630 0     0 1   my $ctrl = shift;
631              
632 0           $_->down for $ctrl->services;
633 0           $_->shutdown for $ctrl->proxies;
634 0           $_->unbind for $ctrl->sockets;
635             }
636              
637             =head1 AUTHOR
638              
639             Yann Kerherve Eyannk@cpan.orgE
640              
641             =head1 LICENSE
642              
643             This library is free software; you can redistribute it and/or modify
644             it under the same terms as Perl itself.
645              
646             =head1 SEE ALSO
647              
648             I think the venerable (but hatred) daemontools is the ancestor of all
649             supervisor processes. In the same class there is also runit and monit.
650              
651             More recent modules which inspired ControlFreak are God and Supervisord
652             in Python. Surprisingly I didn't find any similar program in Perl. Some
653             ideas in ControlFreak are subtely different though.
654              
655             EDIT: I've spotted Ubic recently on CPAN
656              
657             "If you have kids you probably know what I mean";