ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/AnyEvent-MP/bin/aemp
Revision: 1.23
Committed: Sun Aug 30 18:15:49 2009 UTC (14 years, 9 months ago) by root
Branch: MAIN
Changes since 1.22: +295 -4 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.1 #!/opt/bin/perl
2    
3     =head1 NAME
4    
5     aemp - AnyEvent:MP utility
6    
7     =head1 SYNOPSIS
8    
9     aemp command args...
10    
11     # protocol commands
12 root 1.19 aemp snd <port> <arg...> # send a message
13     aemp mon <port> # wait till port is killed
14     aemp rpc <port> <arg...> # send message, append reply
15     aemp eval <node> <expr...> # evaluate expression
16 root 1.22 aemp trace <nodeid> # trace the network topology
17 root 1.8
18     # run a node
19 root 1.19 aemp run initialise_args... # run a node
20 root 1.8
21 root 1.23 # node configuration: node ID
22 root 1.19 aemp setnodeid <nodeid> # configure the real node id
23     aemp delnodeid # reset node id to default (= inherit)
24 root 1.1
25     # node configuration: secret
26 root 1.19 aemp gensecret # generate a random shared secret
27     aemp setsecret <secret> # set the shared secret
28     aemp delsecret # remove the secret (= inherit)
29 root 1.1
30     # node configuration: TLS
31 root 1.19 aemp gencert # generate a random certificate
32     aemp setcert <file> # set a certificate (key.pem + certificate.pem)
33     aemp delcert # remove certificate (= inherit)
34 root 1.8
35 root 1.17 # node configuration: seed addresses for bootstrapping
36 root 1.19 aemp setseeds <host:port>,... # set seeds
37     aemp delseeds # clear all seeds (= inherit)
38     aemp addseed <host:port> # add a seed
39     aemp delseed <host:port> # remove seed
40 root 1.17
41     # node configuration: bind addresses
42 root 1.19 aemp setbinds <host:port>,... # set binds
43     aemp delbinds # clear all binds (= inherit)
44     aemp addbind <host:port> # add a bind address
45     aemp delbind <host:port> # remove a bind address
46 root 1.8
47     # node configuration: services
48 root 1.19 aemp setservices initfunc,... # set service functions
49     aemp delservices # clear all services (= inherit)
50     aemp addservice <initfunc> # add an instance of a service
51     aemp delservice <initfunc> # delete one instance of a service
52 root 1.6
53 root 1.10 # profile-specific configuration
54     aemp profile <name> <command>... # apply command to profile only
55 root 1.19 aemp delprofile <name> # eradicate the named profile
56     aemp showprofile <name> # display given profile
57     aemp showconfig <name> # display effective config
58 root 1.10
59 root 1.1 =head1 DESCRIPTION
60    
61 root 1.8 With aemp you can configure various aspects of AnyEvent::MP and its
62 root 1.22 protocol, send various messages and even run a node.
63 root 1.1
64 root 1.23 The F<aemp> utility works like F<cvs>, F<svn> or other commands: the first
65     argument defines which operation (subcommand) is requested, after which
66     arguments for this operation are expected. When a subcommand does not eat
67     all remaining arguments, the remaining arguments will again be interpreted
68     as subcommand and so on.
69    
70     This means you can chain multiple commands, which is handy for profile
71     configuration, e.g.:
72    
73     aemp gensecret profile xyzzy setbinds 4040,4041 setnodeid anon/
74    
75     =head2 RUNNING A NODE
76    
77     This can be used to run a node - together with some services, this makes
78     it unnecesary to write any wrapper programs.
79    
80     =over 4
81    
82     =item run <profile> <...>
83    
84     Runs a node by calling C<AnyEvent::MP::initialise_node> with the given
85     arguments. The node runs under L<AnyEvent::Watchdog>, can be restarted
86     (and autorestarted, see the L<AnyEvent::Watchdog> manual).
87    
88     Care has been taken to load (almost) no modules other than
89     L<AnyEvent::Watchdog> and the modules it loads, so everything (including
90     the L<AnyEvent::MP> modules themselves) will be freshly loaded on restart,
91     which makes upgrading everything except the perl binary easy.
92    
93     =back
94    
95     =head2 PROTOCOL COMMANDS
96    
97     These commands actually communicate with other nodes. They all use a node
98     profile name of L<anon/> currently.
99    
100     They all use a timeout of five seconds, after which they give up.
101    
102     =over 4
103    
104     =item snd <port> <arguments...>
105    
106     Simply send a message to the given port - where you get the port ID from
107     is your problem.
108    
109     Exits after ensuring that the message has been delivered to its node.
110    
111     Most useful to take avdantage of some undocumented functionality inside
112     nodes, such as node ports being able to call any method:
113    
114     aemp snd doomed AnyEvent::Watchdog::restart 1
115    
116     =item rpc <port> <arg...>
117    
118     Like F<aemp snd>, but appends a local reply port to the message and waits
119     for a message to it.
120    
121     Any return values will be JSON-encoded and printed separated by commas
122     (kind of like a JSON array without []-brackets).
123    
124     Example: ask the (undocumented) time service of a node for it'S current
125     time.
126    
127     aemp rpc mynode time
128    
129     =item mon <port>
130    
131     Monitors the port and exits when it's monitorign callback is called. Most
132     useful to monitor node ports.
133    
134     Example: monitor some node.
135    
136     aemp mon doomed
137    
138     =item eval <node> <expr...>
139    
140     Joins all remaining arguments into a string and evaluates it on the given
141     node. Return values are handled as with F<aemp rpc>.
142    
143     Example: find the unix process ID of the node called posicks.
144    
145     aemp eval posicks '$$'
146    
147     =item trace <node>
148    
149     Asks the given node for all currently connected nodes, then asks those
150     nodes for the same, thus tracing all node connections.
151    
152     =cut
153    
154     =head2 CONFIGURATION/NODE ID/SECRET/CERTIFICATE
155    
156     These commands deal with rather basic settings, the node ID, the shared
157     secret and the TLS certificate.
158    
159     =over 4
160    
161     =item setnodeid <nodeid>
162    
163     Set the node ID to the given string.
164    
165     =item delnodeid
166    
167     Removes the node ID again, which means it is inherited again from it's
168     parent profile, or stays unset.
169    
170     =item gensecret
171    
172     Generates a random shared secret and sets it. The shared secret is used to
173     authenticate nodes to each other when TLS is not required.
174    
175     =item setsecret <secret>
176    
177     Sets the shared secret tot he given string, which can be anything.
178    
179     =item delsecret
180    
181     Removes the shared secret again, which means it is inherited again from
182     it's parent profile, or stays unset.
183    
184     =item gencert
185    
186     Generates a self-signed certficate and key, and sets it. This works
187     similarly to a shared secret: when all nodes have it, TLS will be used to
188     authenticate and encrypt all traffic.
189    
190     =item setcert <file>
191    
192     Set a node certificate (and optionally any CA certificates) from the given
193     file. The file must contain the key, followed by the certificate, followed
194     by any CA certificates you want to trust, all in PEM format.
195    
196     See L<AnyEvent::TLS> for some more details - this sets the C<cert> and
197     C<ca_cert> options.
198    
199     =item delcert
200    
201     Removes the certificate(s) again, which means it is inherited again from
202     it's parent profile, or stays unset.
203    
204     =back
205    
206     =head2 CONFIGURATION/SEEDS
207    
208     To discover the network you have to specify some seed addresses, which are
209     basically C<host:port> pairs where you expect some long-running nodes. It
210     does no harm to have a node as its own seed (they will eventually be
211     ignored).
212    
213     =item setseeds <host:port>,...
214    
215     Sets or replaces the list of seeds, which must be specified as a
216     comma-separated list of C<host:port> pairs. The C<host> can be a hostname,
217     an IP address, or C<*> to signify all local host addresses. If the
218     C<:port> is omitted, then the default port of C<4040> is assumed.
219    
220     An empty list is allowed.
221    
222     Example: use C<doomed> with default port as only seednode.
223    
224     aemp setseeds doomed
225    
226     =item delseeds
227    
228     Removes the seed list again, which means it is inherited again from it's
229     parent profile, or stays unset.
230    
231     =item addseed <host:port>
232    
233     Adds a single seed address.
234    
235     =item delseed <host:port>
236    
237     Deletes the given seed address, if it exists.
238    
239     =back
240    
241     =head2 CONFIGURATION/BINDS
242    
243     To be able to be reached from other nodes, a node must I<bind> itself
244     to some listening socket(s). The list of these can either bs specified
245     manually, or AnyEvent::MP can guess them. Nodes without any binds are
246     possible to some extent.
247    
248     =over 4
249    
250     =item setbinds <host:port>,...
251    
252     Sets the list of bind addresses explicitly - see the F<aemp setseeds>
253     command for the exact syntax. In addition, a value of C<0> for the port
254     means to use a dynamically-assigned port.
255    
256     Note that the C<*>, C<*:0> or C<*:port> values are very useful here.
257    
258     Example: bind on the default port (4040) on all local interfaces.
259    
260     aemp setbinds "*"
261    
262     Example: bind on a random port on all local interfaces.
263    
264     aemp setbinds "*:0"
265    
266     Example: resolve "doomed.mydomain" and try to bind on port C<4040> of all
267     IP addressess returned.
268    
269     aep setbinds doomed.mydomain
270    
271     =item delbinds
272    
273     Removes the bind list again, which means it is inherited again from it's
274     parent profile, or stays unset.
275    
276     =item addbind <host:port>
277    
278     Adds a single bind address.
279    
280     =item delbind <host:port>
281    
282     Deletes the given bind address, if it exists.
283    
284     =back
285    
286     =head2 CONFIGURATION/SERVICES
287    
288     Services are modules (or functions) that are automatically loaded (or
289     executed) when a node starts. They are especially useful when used in
290     conjunction with F<aemp run>, to configure which services a node should
291     run.
292    
293     =over 4
294    
295     =item setservices <initfunc>...
296    
297     Sets or replaces the list of services, which must be specified as a
298     comma-separated list.
299    
300     Each entry in the list is interpreted as either a module name to
301     load (when it ends with C<::>) or a function to call (all other
302     cases). The algorithm to find the function is the same as used for C<<
303     L<AnyEvent::MP>::spawn >>.
304    
305     =item delservices
306    
307     Removes the service list again, which means it is inherited again from
308     it's parent profile, or stays unset.
309    
310     =item addservice <initfunc>
311    
312     Adds a single service.
313    
314     =item delservice <initfunc>
315    
316     Deletes the given service, if it exists.
317    
318     =back
319    
320     =head2 CONFIGURATION/PROFILE MANAGEMENT
321    
322     All the above configuration functions by default affect the I<global
323     default configuration>, which is basically used to augment every profile
324     and node configuration.
325    
326     =over 4
327    
328     =item profile <name> ...
329    
330     This subcommand makes the following subcommands act only on a specific
331     named profile, instead of on the global default. The profile is created if
332     necessary.
333    
334     Example: create a C<server> profile, give it a random node name, some seed
335     nodes and bind it on an unspecified port on all local interfaces. You
336     should add some services then and run the node...
337    
338     aemp server setnodeid anon/ setseeds doomed,10.0.0.2:5000 setbinds "*:0"
339    
340     =item delprofile <name>
341    
342     Deletes the profile of the given name.
343    
344     =item showprofile <name>
345    
346     Shows the values of the given profile, and only those, no inherited
347     values.
348    
349     =item showconfig <name>
350    
351     Shows the I<effective> config, i.e. the values as used by a node started with the given profile name.
352    
353     =back
354    
355 root 1.1 =cut
356    
357     use common::sense;
358    
359 root 1.19 # should come before anything else, so all modules
360     # will be loaded on each restart
361 root 1.22 BEGIN {
362 root 1.8 if ($ARGV[0] eq "run") {
363     shift;
364    
365 root 1.13 # d'oh
366 root 1.22 require AnyEvent::Watchdog;
367 root 1.19 # now we can load extra modules
368    
369 root 1.22 AnyEvent::Watchdog::autorestart (1);
370     AnyEvent::Watchdog::heartbeat (300);
371 root 1.13
372 root 1.8 require AnyEvent;
373     require AnyEvent::MP;
374     AnyEvent::MP::initialise_node (@ARGV);
375    
376 root 1.16 AnyEvent::detect () eq "AnyEvent::Impl::EV"
377 root 1.8 ? EV::loop ()
378     : AE::cv ()->recv;
379     }
380 root 1.22 }
381 root 1.8
382 root 1.1 use Carp ();
383    
384 root 1.17 use JSON::XS;
385    
386 root 1.1 use AnyEvent;
387 root 1.4 use AnyEvent::Util;
388    
389 root 1.8 use AnyEvent::MP;
390 root 1.1 use AnyEvent::MP::Config;
391    
392     sub my_run_cmd {
393     my ($cmd) = @_;
394    
395     my $cv = &run_cmd;
396     my $status = $cv->recv;
397    
398     $status
399     and die "@$cmd: command failed with exit status $status.";
400     }
401    
402     sub gen_cert {
403 root 1.2 my_run_cmd [qw(openssl req
404     -new -nodes -x509 -days 3650
405     -newkey rsa:2048 -keyout /dev/fd/3
406     -batch -subj /CN=AnyEvent::MP
407     )],
408 root 1.5 "<", "/dev/null",
409 root 1.1 ">" , \my $cert,
410     "3>", \my $key,
411 root 1.4 "2>", "/dev/null";
412 root 1.1
413     "$cert$key"
414     }
415    
416 root 1.10 our $cfg = AnyEvent::MP::Config::config;
417 root 1.8 our $profile = $cfg;
418 root 1.1
419 root 1.11 sub trace {
420     my ($node) = @_;
421     my $cv = AE::cv;
422     my %seen;
423    
424     my $to = AE::timer 15, 0, sub {
425     warn "timeout\n";
426     $cv->();
427     };
428    
429 root 1.19 initialise_node "anon/";
430 root 1.11
431     my $reply = port {
432 root 1.12 my ($node, @neigh) = @_;
433 root 1.11
434 root 1.12 @neigh = grep $_ ne $NODE, @neigh;
435    
436     print +(join " ", $node, @neigh), "\n";
437    
438     for (@neigh) {
439 root 1.11 unless ($seen{$_}++) {
440     $cv->begin;
441     snd $_, up_nodes => $SELF => $_;
442     }
443     }
444    
445     $cv->end;
446     };
447    
448     $cv->begin;
449     snd $reply, seed => $node;
450    
451     $cv->recv;
452     }
453    
454 root 1.10 sub docmd;
455    
456 root 1.1 our %CMD = (
457     snd => sub {
458 root 1.17 my $port = shift @ARGV;
459 root 1.19 initialise_node "anon/";
460 root 1.1
461 root 1.7 snd $port, @ARGV; @ARGV = ();
462 root 1.1
463     my $cv = AE::cv;
464 root 1.7 my $to = AE::timer 5, 0, sub { $cv->("timeout") };
465 root 1.1 mon $port, $cv;
466 root 1.19 my $reply = port sub { &$cv };
467     snd node_of $port, snd => $reply, "message sent successfully";
468 root 1.1
469     print join " ", $cv->recv, "\n";
470     },
471    
472 root 1.7 rpc => sub {
473 root 1.17 my $port = shift @ARGV;
474 root 1.19 initialise_node "anon/";
475 root 1.7
476     my $cv = AE::cv;
477     my $to = AE::timer 5, 0, sub { $cv->("timeout") };
478 root 1.19 snd $port, @ARGV, port { &$cv }; @ARGV = ();
479 root 1.7 mon $port, $cv;
480    
481 root 1.23 print +(substr JSON::XS->new->encode ([$cv->recv]), 1, -1), "\n";
482 root 1.7 },
483    
484 root 1.1 mon => sub {
485 root 1.17 my $port = shift @ARGV;
486 root 1.19 initialise_node "anon/";
487 root 1.1
488     mon $port, my $cv = AE::cv;
489     print join " ", $cv->recv, "\n";
490     },
491    
492 root 1.19 eval => sub {
493     my $node = node_of shift @ARGV;
494     my $expr = join " ", @ARGV; @ARGV = ();
495     initialise_node "anon/";
496    
497     my $cv = AE::cv;
498     my $to = AE::timer 5, 0, sub { $cv->("timeout") };
499     AnyEvent::MP::Kernel::eval_on $node, $expr, port { &$cv };
500     mon $node, $cv;
501    
502     my ($err, @res) = $cv->recv;
503    
504     die $err if length $err;
505    
506     print +(substr JSON::XS->new->encode (\@res), 1, -1), "\n";
507     },
508    
509 root 1.11 trace => sub {
510     @ARGV >= 1
511 root 1.17 or die "node id missing\n";
512 root 1.11
513 root 1.17 trace shift @ARGV;
514 root 1.11 },
515    
516 root 1.17 setnodeid => sub {
517 root 1.8 @ARGV >= 1
518     or die "shared secret missing\n";
519    
520 root 1.17 $profile->{nodeid} = shift @ARGV;
521 root 1.8 ++$cfg->{dirty};
522     },
523 root 1.17 delnodeid => sub {
524     delete $profile->{nodeid};
525 root 1.8 ++$cfg->{dirty};
526     },
527    
528 root 1.1 setsecret => sub {
529 root 1.8 @ARGV >= 1
530 root 1.1 or die "shared secret missing\n";
531    
532 root 1.8 $profile->{secret} = shift @ARGV;
533 root 1.1 ++$cfg->{dirty};
534     },
535     gensecret => sub {
536 root 1.18 $profile->{secret} = AnyEvent::MP::Kernel::alnumbits AnyEvent::MP::Kernel::nonce 64;
537 root 1.1 ++$cfg->{dirty};
538     },
539 root 1.17 delsecret => sub {
540 root 1.8 delete $profile->{secret};
541 root 1.1 ++$cfg->{dirty};
542     },
543    
544     setcert => sub {
545 root 1.8 @ARGV >= 1
546 root 1.1 or die "key+certificate pem filename missing\n";
547    
548     open my $fh, "<", $ARGV[0]
549     or die "$ARGV[0]: $!";
550    
551     local $/;
552 root 1.8 $profile->{cert} = <$fh>;
553 root 1.1 ++$cfg->{dirty};
554     },
555     gencert => sub {
556 root 1.8 $profile->{cert} = gen_cert;
557 root 1.1 ++$cfg->{dirty};
558     },
559 root 1.17 delcert => sub {
560 root 1.8 delete $profile->{cert};
561 root 1.1 ++$cfg->{dirty};
562     },
563 root 1.6
564 root 1.17 setbinds => sub {
565 root 1.19 @ARGV >= 1
566     or die "bind addresses missing\n";
567     $profile->{binds} = [split /,/, shift @ARGV];
568 root 1.17 ++$cfg->{dirty};
569     },
570     delbinds => sub {
571     delete $profile->{binds};
572     ++$cfg->{dirty};
573     },
574     addbind => sub {
575     @ARGV >= 1
576     or die "bind address missing\n";
577     my $bind = shift @ARGV;
578    
579     @{ $profile->{binds} } = grep $_ ne $bind, @{ $profile->{binds} };
580     push @{ $profile->{binds} }, $bind;
581     ++$cfg->{dirty};
582     },
583     delbind => sub {
584     @ARGV >= 1
585     or die "bind address missing\n";
586     my $bind = shift @ARGV;
587    
588     @{ $profile->{binds} } = grep $_ ne $bind, @{ $profile->{binds} };
589     ++$cfg->{dirty};
590     },
591    
592 root 1.6 setseeds => sub {
593 root 1.19 @ARGV >= 1
594     or die "seed addresses missing\n";
595     $profile->{seeds} = [split /,/, shift @ARGV];
596 root 1.6 ++$cfg->{dirty};
597     },
598 root 1.17 delseeds => sub {
599     delete $profile->{seeds};
600     ++$cfg->{dirty};
601     },
602 root 1.6 addseed => sub {
603 root 1.10 @ARGV >= 1
604 root 1.17 or die "seed address missing\n";
605 root 1.6 my $seed = shift @ARGV;
606 root 1.10
607 root 1.8 @{ $profile->{seeds} } = grep $_ ne $seed, @{ $profile->{seeds} };
608     push @{ $profile->{seeds} }, $seed;
609     ++$cfg->{dirty};
610     },
611     delseed => sub {
612 root 1.10 @ARGV >= 1
613 root 1.17 or die "seed address missing\n";
614 root 1.8 my $seed = shift @ARGV;
615 root 1.10
616 root 1.8 @{ $profile->{seeds} } = grep $_ ne $seed, @{ $profile->{seeds} };
617     ++$cfg->{dirty};
618     },
619    
620     setservices => sub {
621 root 1.19 @ARGV >= 1
622     or die "service specifications missing\n";
623     $profile->{services} = [split /,/, shift @ARGV];
624 root 1.8 @ARGV = ();
625 root 1.6 ++$cfg->{dirty};
626     },
627 root 1.17 delservices => sub {
628     delete $profile->{services};
629     ++$cfg->{dirty};
630     },
631 root 1.9 addservice => sub {
632 root 1.10 @ARGV >= 1
633     or die "service specification missing\n";
634 root 1.8 my $service = shift @ARGV;
635     push @{ $profile->{services} }, $service;
636     ++$cfg->{dirty};
637     },
638 root 1.9 delservice => sub {
639 root 1.10 @ARGV >= 1
640     or die "service specification missing\n";
641 root 1.8 my $service = shift @ARGV;
642     for (0 .. $#{ $profile->{services} }) {
643     next unless $profile->{services}[$_] eq $service;
644     splice @{ $profile->{services} }, $_, 1;
645     last;
646     }
647 root 1.6 ++$cfg->{dirty};
648     },
649 root 1.10
650     profile => sub {
651 root 1.19 @ARGV >= 1
652     or die "profile name is missing\n";
653 root 1.10 my $name = shift @ARGV;
654    
655     $profile = $cfg->{profile}{$name} ||= {};
656 root 1.14 ++$cfg->{dirty};
657 root 1.10 },
658     delprofile => sub {
659     @ARGV >= 1
660     or die "profile name is missing\n";
661     my $name = shift @ARGV;
662    
663     delete $cfg->{profile}{$name};
664 root 1.14 ++$cfg->{dirty};
665 root 1.10 },
666 root 1.17 showprofile => sub {
667     @ARGV >= 1
668     or die "profile name is missing\n";
669     my $name = shift @ARGV;
670    
671     print JSON::XS->new->pretty->encode ($cfg->{profile}{$name} || {});
672     },
673     showconfig => sub {
674     my $name = @ARGV ? shift @ARGV : AnyEvent::MP::Kernel::_nodename;
675    
676     print JSON::XS->new->pretty->encode (AnyEvent::MP::Config::find_profile $name);
677     },
678 root 1.1 );
679    
680     sub docmd {
681     my $cmd = shift @ARGV;
682    
683     $CMD{$cmd}
684 root 1.23 or die "$cmd: no such aemp command (try perldoc aemp, or man aemp)";
685 root 1.1
686     $CMD{$cmd}();
687     }
688    
689     @ARGV
690 root 1.23 or die "Usage: aemp subcommand ... (try perldoc aemp, or man aemp)\n";
691 root 1.1
692 root 1.19 docmd while @ARGV;
693 root 1.1
694