AnyEvent-MP
view release on metacpan or search on metacpan
Nodes is represented by (printable) strings called "node IDs".
=item node ID - C<[A-Za-z0-9_\-.:]*>
A node ID is a string that uniquely identifies the node within a
network. Depending on the configuration used, node IDs can look like a
hostname, a hostname and a port, or a random string. AnyEvent::MP itself
doesn't interpret node IDs in any way except to uniquely identify a node.
=item binds - C<ip:port>
Nodes can only talk to each other by creating some kind of connection to
each other. To do this, nodes should listen on one or more local transport
endpoints - binds.
Currently, only standard C<ip:port> specifications can be used, which
specify TCP ports to listen on. So a bind is basically just a tcp socket
in listening mode that accepts connections from other nodes.
=item seed nodes
When a node starts, it knows nothing about the network it is in - it
needs to connect to at least one other node that is already in the
network. These other nodes are called "seed nodes".
Seed nodes themselves are not special - they are seed nodes only because
some other node I<uses> them as such, but any node can be used as seed
If the profile specifies a node ID, then this will become the node ID of
this process. If not, then the profile name will be used as node ID, with
a unique randoms tring (C</%u>) appended.
The node ID can contain some C<%> sequences that are expanded: C<%n>
is expanded to the local nodename, C<%u> is replaced by a random
strign to make the node unique. For example, the F<aemp> commandline
utility uses C<aemp/%n/%u> as nodename, which might expand to
C<aemp/cerebro/ZQDGSIkRhEZQDGSIkRhE>.
=item step 2, bind listener sockets
The next step is to look up the binds in the profile, followed by binding
aemp protocol listeners on all binds specified (it is possible and valid
to have no binds, meaning that the node cannot be contacted from the
outside. This means the node cannot talk to other nodes that also have no
binds, but it can still talk to all "normal" nodes).
If the profile does not specify a binds list, then a default of C<*> is
used, meaning the node will bind on a dynamically-assigned port on every
local IP address it finds.
=item step 3, connect to seed nodes
As the last step, the seed ID list from the profile is passed to the
L<AnyEvent::MP::Global> module, which will then use it to keep
connectivity with at least one node at any point in time.
=back
This should be the most common form of invocation for "daemon"-type nodes.
configure
Example: become a semi-anonymous node. This form is often used for
commandline clients.
configure nodeid => "myscript/%n/%u";
Example: configure a node using a profile called seed, which is suitable
for a seed node as it binds on all local addresses on a fixed port (4040,
customary for aemp).
# use the aemp commandline utility
# aemp profile seed binds '*:4040'
# then use it
configure profile => "seed";
# or simply use aemp from the shell again:
# aemp run profile seed
# or provide a nicer-to-remember nodeid
# aemp run profile seed nodeid "$(hostname)"
Register (or replace) callbacks to be called on messages starting with the
given tag on the given port (and return the port), or unregister it (when
C<$callback> is C<$undef> or missing). There can only be one callback
registered for each tag.
The original message will be passed to the callback, after the first
element (the tag) has been removed. The callback will use the same
environment as the default callback (see above).
Example: create a port and bind receivers on it in one go.
my $port = rcv port,
msg1 => sub { ... },
msg2 => sub { ... },
;
Example: create a port, bind receivers and send it in a message elsewhere
in one go:
snd $otherport, reply =>
rcv port,
msg1 => sub { ... },
...
;
Example: temporarily register a rcv callback for a tag matching some port
(e.g. for an rpc reply) and unregister it after a message was received.
&snd;
$port
}
=back
=head1 DISTRIBUTED DATABASE
AnyEvent::MP comes with a simple distributed database. The database will
be mirrored asynchronously on all global nodes. Other nodes bind to one
of the global nodes for their needs. Every node has a "local database"
which contains all the values that are set locally. All local databases
are merged together to form the global database, which can be queried.
The database structure is that of a two-level hash - the database hash
contains hashes which contain values, similarly to a perl hash of hashes,
i.e.:
$DATABASE{$family}{$subkey} = $value
AEMP 1.x), and other nodes specify them as seed nodes. This is most easily
achieved by specifying the same set of seed nodes for all nodes in the
network.
Not opening a connection to every other node is usually an advantage,
except when you need the lower latency of an already established
connection. To ensure a node establishes a connection to another node,
you can monitor the node port (C<mon $node, ...>), which will attempt to
create the connection (and notify you when the connection fails).
=item Listener-less nodes (nodes without binds) are gone.
And are not coming back, at least not in their old form. If no C<binds>
are specified for a node, AnyEvent::MP assumes a default of C<*:*>.
There are vague plans to implement some form of routing domains, which
might or might not bring back listener-less nodes, but don't count on it.
The fact that most connections are now optional somewhat mitigates this,
as a node can be effectively unreachable from the outside without any
problems, as long as it isn't a global node and only reaches out to other
nodes (as opposed to being contacted from other nodes).
MP/DataConn.pm view on Meta::CPAN
local_greeting => { dataconn_id => $id },
sub { $transport->destroy }, #TODO: destroys handshaked connections too early
;
};
}
=item AnyEvent::MP::DataConn::connect_to $node, $timeout, $initfunc, @initdata, $cb->($handle)
Creates a socket connection between the local node and the node C<$node>
(which can also be specified as a port). One of the nodes must have
listeners ("binds").
When the connection could be successfully created, the C<$initfunc>
will be called with the given C<@initdata> on the remote node (similar
to C<snd_to_func> or C<spawn>), and the C<AnyEvent::Handle> object
representing the remote connection end as additional argument.
Also, the callback given as last argument will be called with the
AnyEvent::Handle object for the local side.
The AnyEvent::Handle objects will be in a "quiescent" state - you could rip
MP/Global.pm view on Meta::CPAN
my $node = $SRCNODE;
undef $GLOBAL_SLAVE{$node};
g_set $node, $db;
};
# other global node sends us their database
$NODE_REQ{g_set} = sub {
my ($db) = @_;
# need to get it here, because g_set destroys it
my $binds = $db->{"'l"}{$SRCNODE};
g_set $SRCNODE, $db;
# a remote node always has to provide their listeners. for global
# nodes, we mirror their 'l locally, just as we also set 'g.
# that's not very efficient, but ensures that global nodes
# find each other.
db_set "'l" => $SRCNODE => $binds;
};
# other node (global and slave) sends us a family update
$NODE_REQ{g_upd} = sub {
&g_upd ($SRCNODE, @_);
};
# slave node wants to know the listeners of a node
$NODE_REQ{g_find} = sub {
my ($node) = @_;
MP/Intro.pod view on Meta::CPAN
=head2 The Receiver
Lets split the previous example up into two programs: one that contains
the sender and one for the receiver. First the receiver application, in
full:
use AnyEvent;
use AnyEvent::MP;
configure nodeid => "eg_receiver/%u", binds => ["*:4040"];
my $port = port;
db_set eg_receivers => $port;
rcv $port, test => sub {
my ($data, $reply_port) = @_;
print "Received data: " . $data . "\n";
};
AnyEvent->condvar->recv;
Now, that wasn't too bad, was it? OK, let's go through the new functions
that have been used.
=head3 C<configure> and Joining and Maintaining the Network
First let's have a look at C<configure>:
configure nodeid => "eg_receiver/%u", binds => ["*:4040"];
Before we are able to send messages to other nodes we have to configure
the node to become a "networked node". Configuring a node means naming
the node and binding some TCP listeners so that other nodes can contact
it. The choice on whether a process becomes a networked node or not must
be done before doing anything else with AnyEvent::MP.
Additionally, to actually link all nodes in a network together, you should
specify a number of seed addresses, which will be used by the node to
connect itself into an existing network, as we will see shortly.
All of this info (and more) can be passed to the C<configure> function -
later we will see how we can do all this without even passing anything to
C<configure>!
MP/Intro.pod view on Meta::CPAN
Back to the function call in the program: the first parameter, C<nodeid>,
specified the node ID (in this case C<eg_receiver/%u> - the default is to
use the node name of the current host plus C</%u>, which gives the node a
name with a random suffix to make it unique, but for this example we want
the node to have a bit more personality, and name it C<eg_receiver> with a
random suffix.
Why the random suffix? Node IDs need to be unique within the network and
appending a random suffix is the easiest way to do that.
The second parameter, C<binds>, specifies a list of C<address:port> pairs
to bind TCP listeners on. The special "address" of C<*> means to bind on
every local IP address (this might not work on every OS, so explicit IP
addresses are best).
The reason to bind on a TCP port is not just that other nodes can connect
to us: if no binds are specified, the node will still bind on a dynamic
port on all local addresses - but in this case we won't know the port, and
cannot tell other nodes to connect to it as seed node.
Now, a I<seed> is simply the TCP address of some other node in the
network, often the same string as used for the C<binds> parameter of the
other node. The need for seeds is easy to explain: I<somehow> the nodes
of an aemp network have to find each other, and often this means over the
internet. So broadcasts are out.
Instead, a node usually specifies the addresses of one or few (for
redundancy) other nodes, some of which should be up. Two nodes can set
each other as seeds without any issues. You could even specify all nodes
as seeds for all nodes, for total redundancy. But the common case is to
have some more or less central, stable servers running seed services for
other nodes.
MP/Intro.pod view on Meta::CPAN
return unless %$family;
# now there are some receivers, send them a message
snd $_ => test => time
for keys %$family;
};
AnyEvent->condvar->recv;
It's even less code. The C<configure> serves the same purpose as in the
receiver, but instead of specifying binds we specify a list of seeds - the
only seed happens to be the same as the bind used by the receiver, which
therefore becomes our seed node.
Remember the part about having to wait till things become available? Well,
after configure returns, nothing has been done yet - the node is not
connected to the network, knows nothing about the database contents, and
it can take ages (for a computer :) for this situation to change.
Therefore, the sender waits, in this case by using the C<db_mon>
function. This function registers an interest in a specific database
family (in this case C<eg_receivers>). Each time something inside the
MP/Intro.pod view on Meta::CPAN
second argument.
The callback only checks whether the C<%$family> hash is empty - if it is,
then it doesn't do anything. But eventually the family will contain the
port subkey we set in the sender. Then it will send a message to it (and
any other receiver in the same family). Likewise, should the receiver go
away and come back, or should another receiver come up, it will again send
a message to all of them.
You can experiment by having multiple receivers - you have to change the
"binds" parameter in the receiver to the seeds used in the sender to start
up additional receivers, but then you can start as many as you like. If
you specify proper IP addresses for the seeds, you can even run them on
different computers.
Each time you start the sender, it will send a message to all receivers it
finds (you have to interrupt it manually afterwards).
Additional experiments you could try include using C<AE_MP_TRACE=1> to see
which messages are exchanged, or starting the sender before the receiver
and see how long it then takes to find the receiver.
MP/Intro.pod view on Meta::CPAN
configure;
then AnyEvent::MP tries to look up a profile using the current node name
in its configuration database, falling back to some global default.
You can run "generic" nodes using the F<aemp> utility as well, and we will
exploit this in the following way: we configure a profile "seed" and run
a node using it, whose sole purpose is to be a seed node for our example
programs.
We bind the seed node to port 4040 on all interfaces:
aemp profile seed binds "*:4040"
And we configure all nodes to use this as seed node (this only works when
running on the same host, for multiple machines you would replace the C<*>
by the IP address or hostname of the node running the seed), by changing
the global settings shared between all profiles:
aemp seeds "*:4040"
Then we run the seed node:
MP/Intro.pod view on Meta::CPAN
created - the clients only want to know which node the server should
be running on, and there can only be one such server (or service) per
node. In fact, the clients could also use some kind of election mechanism,
to find the node with lowest node ID, or lowest load, or something like
that.
The much more interesting difference to the previous server is that
indeed no server port is created - the server consists only of code,
and "does" nothing by itself. All it "does" is to define a function
named C<client_connect>, which expects a client port and a nick name as
arguments. It then monitors the client port and binds a receive callback
on C<$SELF>, which expects messages that in turn are broadcast to all
clients.
The two C<mon> calls are a bit tricky - the first C<mon> is a shorthand
for C<mon $client, $SELF>. The second does the normal "client has gone
away" clean-up action.
The last line, the C<rcv $SELF>, is a good hint that something interesting
is going on. And indeed, when looking at the client code, you can see a
new function, C<spawn>:
MP/Kernel.pm view on Meta::CPAN
# sent by global nodes
# g_global - global nodes send this to all others
#
# database protocol
# g_slave database - make other global node master of the sender
# g_set database - global node's database to other global nodes
# g_upd family set del - update single family (any to global)
#
# slave <-> global protocol
# g_find node - query addresses for node (slave to global)
# g_found node binds - node addresses (global to slave)
# g_db_family family id - send g_reply with data (global to slave)
# g_db_keys family id - send g_reply with data (global to slave)
# g_db_values family id - send g_reply with data (global to slave)
# g_reply id result - result of any query (global to slave)
# g_mon1 family - start to monitor family, replies with g_chg1
# g_mon0 family - stop monitoring family
# g_chg1 family hash - initial value of family when starting to monitor
# g_chg2 family set del - like g_upd, but for monitoring only
#
# internal database families:
# "'l" -> node -> listeners
# "'g" -> node -> undef
# ...
#
# used on all nodes:
our $MASTER; # the global node we bind ourselves to
our $MASTER_MON;
our %LOCAL_DB; # this node database
our $GPROTO = 1;
# tell everybody who connects our gproto
push @AnyEvent::MP::Transport::HOOK_GREET, sub {
$_[0]{local_greeting}{gproto} = $GPROTO;
};
MP/Kernel.pm view on Meta::CPAN
if ($NODE =~ s!(?:(?<=/)$|%u)!$RUNIQ!g) {
# nodes with randomised node names do not need randomised port names
$UNIQ = "";
}
$node_obj->{id} = $NODE;
$NODE{$NODE} = $node_obj;
my $seeds = $CONFIG->{seeds};
my $binds = $CONFIG->{binds};
$binds ||= ["*"];
AE::log 8 => "node $NODE starting up.";
$BINDS = [];
%BINDS = ();
for (map _resolve $_, @$binds) {
for my $bind ($_->recv) {
my ($host, $port) = AnyEvent::Socket::parse_hostport $bind
or Carp::croak "$bind: unparsable local bind address";
my $listener = AnyEvent::MP::Transport::mp_server
$host,
$port,
prepare => sub {
my (undef, $host, $port) = @_;
$bind = AnyEvent::Socket::format_hostport $host, $port;
0
},
;
$BINDS{$bind} = $listener;
push @$BINDS, $bind;
}
}
AE::log 9 => "running post config hooks and init.";
# might initialise Global, so need to do it before db_set
post_configure { };
db_set "'l" => $NODE => $BINDS;
Nodes is represented by (printable) strings called "node IDs".
node ID - "[A-Za-z0-9_\-.:]*"
A node ID is a string that uniquely identifies the node within a
network. Depending on the configuration used, node IDs can look like
a hostname, a hostname and a port, or a random string. AnyEvent::MP
itself doesn't interpret node IDs in any way except to uniquely
identify a node.
binds - "ip:port"
Nodes can only talk to each other by creating some kind of
connection to each other. To do this, nodes should listen on one or
more local transport endpoints - binds.
Currently, only standard "ip:port" specifications can be used, which
specify TCP ports to listen on. So a bind is basically just a tcp
socket in listening mode that accepts connections from other nodes.
seed nodes
When a node starts, it knows nothing about the network it is in - it
needs to connect to at least one other node that is already in the
network. These other nodes are called "seed nodes".
Seed nodes themselves are not special - they are seed nodes only
because some other node *uses* them as such, but any node can be
used as seed node for other nodes, and eahc node can use a different
If the profile specifies a node ID, then this will become the
node ID of this process. If not, then the profile name will be
used as node ID, with a unique randoms tring ("/%u") appended.
The node ID can contain some "%" sequences that are expanded: %n
is expanded to the local nodename, %u is replaced by a random
strign to make the node unique. For example, the aemp
commandline utility uses "aemp/%n/%u" as nodename, which might
expand to "aemp/cerebro/ZQDGSIkRhEZQDGSIkRhE".
step 2, bind listener sockets
The next step is to look up the binds in the profile, followed
by binding aemp protocol listeners on all binds specified (it is
possible and valid to have no binds, meaning that the node
cannot be contacted from the outside. This means the node cannot
talk to other nodes that also have no binds, but it can still
talk to all "normal" nodes).
If the profile does not specify a binds list, then a default of
"*" is used, meaning the node will bind on a
dynamically-assigned port on every local IP address it finds.
step 3, connect to seed nodes
As the last step, the seed ID list from the profile is passed to
the AnyEvent::MP::Global module, which will then use it to keep
connectivity with at least one node at any point in time.
Example: become a distributed node using the local node name as
profile. This should be the most common form of invocation for
"daemon"-type nodes.
configure
Example: become a semi-anonymous node. This form is often used for
commandline clients.
configure nodeid => "myscript/%n/%u";
Example: configure a node using a profile called seed, which is
suitable for a seed node as it binds on all local addresses on a
fixed port (4040, customary for aemp).
# use the aemp commandline utility
# aemp profile seed binds '*:4040'
# then use it
configure profile => "seed";
# or simply use aemp from the shell again:
# aemp run profile seed
# or provide a nicer-to-remember nodeid
# aemp run profile seed nodeid "$(hostname)"
rcv $local_port, tag => $callback->(@msg_without_tag), ...
Register (or replace) callbacks to be called on messages starting
with the given tag on the given port (and return the port), or
unregister it (when $callback is $undef or missing). There can only
be one callback registered for each tag.
The original message will be passed to the callback, after the first
element (the tag) has been removed. The callback will use the same
environment as the default callback (see above).
Example: create a port and bind receivers on it in one go.
my $port = rcv port,
msg1 => sub { ... },
msg2 => sub { ... },
;
Example: create a port, bind receivers and send it in a message
elsewhere in one go:
snd $otherport, reply =>
rcv port,
msg1 => sub { ... },
...
;
Example: temporarily register a rcv callback for a tag matching some
port (e.g. for an rpc reply) and unregister it after a message was
If no time-out is given (or it is "undef"), then the local port will
monitor the remote port instead, so it eventually gets cleaned-up.
Currently this function returns the temporary port, but this
"feature" might go in future versions unless you can make a
convincing case that this is indeed useful for something.
DISTRIBUTED DATABASE
AnyEvent::MP comes with a simple distributed database. The database will
be mirrored asynchronously on all global nodes. Other nodes bind to one
of the global nodes for their needs. Every node has a "local database"
which contains all the values that are set locally. All local databases
are merged together to form the global database, which can be queried.
The database structure is that of a two-level hash - the database hash
contains hashes which contain values, similarly to a perl hash of
hashes, i.e.:
$DATABASE{$family}{$subkey} = $value
nodes. This is most easily achieved by specifying the same set of
seed nodes for all nodes in the network.
Not opening a connection to every other node is usually an
advantage, except when you need the lower latency of an already
established connection. To ensure a node establishes a connection to
another node, you can monitor the node port ("mon $node, ..."),
which will attempt to create the connection (and notify you when the
connection fails).
Listener-less nodes (nodes without binds) are gone.
And are not coming back, at least not in their old form. If no
"binds" are specified for a node, AnyEvent::MP assumes a default of
"*:*".
There are vague plans to implement some form of routing domains,
which might or might not bring back listener-less nodes, but don't
count on it.
The fact that most connections are now optional somewhat mitigates
this, as a node can be effectively unreachable from the outside
without any problems, as long as it isn't a global node and only
reaches out to other nodes (as opposed to being contacted from other
aemp gencert # generate a random certificate
aemp setcert <file> # set a certificate (key.pem + certificate.pem)
aemp delcert # remove certificate (= inherit)
# node configuration: seed addresses for bootstrapping
aemp setseeds <host:port>,... # set seeds
aemp delseeds # clear all seeds (= inherit)
aemp addseed <host:port> # add a seed
aemp delseed <host:port> # remove seed
# node configuration: bind addresses
aemp setbinds <host:port>,... # set binds
aemp delbinds # clear all binds (= inherit)
aemp addbind <host:port> # add a bind address
aemp delbind <host:port> # remove a bind address
# node configuration: services
aemp setservices initfunc,... # set service functions
aemp delservices # clear all services (= inherit)
aemp addservice <initfunc> # add an instance of a service
aemp delservice <initfunc> # delete one instance of a service
# profile management
aemp profile <name> <command>... # apply command to profile only
aemp setparent <name> # specify a parent profile
The F<aemp> utility works like F<cvs>, F<svn> or other commands: the first
argument defines which operation (subcommand) is requested, after which
arguments for this operation are expected. When a subcommand does not eat
all remaining arguments, the remaining arguments will again be interpreted
as subcommand and so on.
This means you can chain multiple commands, which is handy for profile
configuration, e.g.:
aemp gensecret profile xyzzy binds 4040,4041 nodeid anon/
Please note that all C<setxxx> subcommands have an alias without the
C<set> prefix.
All configuration data is stored in a human-readable (JSON) config file
stored in F<~/.perl-anyevent-mp> (or F<%appdata%/perl-anyevent-mp> on
loser systems, or wherever C<$ENV{PERL_ANYEVENT_MP_RC}> points to). Feel
free to look at it or edit it, the format is relatively simple.
=head2 SPECIFYING ARGUMENTS
few special cases:
If the I<first> argument starts with a literal C<[>-character, then it is
interpreted as a UTF-8 encoded JSON text. The resulting array replaces all
arguments.
Otherwise, if I<any> argument starts with one of C<[>, C<{> or C<">, then
it is interpreted as UTF-8 encoded JSON text (or a single value in case of
C<">), and the resulting reference or scalar replaces the argument.
This allows you, for example, to specify binds in F<aemp run> (using POSIX
shell syntax):
aemp run binds '["*:4040"]'
=head2 RUNNING A NODE
This can be used to run a node - together with some services, this makes
it unnecessary to write any wrapper programs.
=over 4
=item run <configure_args>...
Restarts the node using C<AnyEvent::Watchdog::Util::restart>. This works
for nodes started by C<aemp run>, but also for any other node that uses
L<AnyEvent::Watchdog>.
=back
=head2 PROTOCOL COMMANDS
These commands actually communicate with other nodes. They all use a node
profile name of C<aemp> (specifying a default node ID of C<anon/> and a
binds list containing C<*:*> only).
They all use a timeout of five seconds, after which they give up.
=over 4
=item snd <port> <arguments...>
Simply send a message to the given port - where you get the port ID from
is your problem.
ignored).
=over 4
=item setseeds <host:port>,...
Sets or replaces the list of seeds, which must be specified as a
comma-separated list of C<host:port> pairs. The C<host> can be a hostname,
an IP address, or C<*> to signify all local host addresses (which makes
little sense for seeds, outside some examples, but a lot of sense for
binds).
An empty list is allowed.
Example: use C<doomed> with default port as only seednode.
aemp setseeds doomed
=item delseeds
Removes the seed list again, which means it is inherited again from it's
Adds a single seed address.
=item delseed <host:port>
Deletes the given seed address, if it exists.
=back
=head2 CONFIGURATION/BINDS
To be able to be reached from other nodes, a node must I<bind> itself
to some listening socket(s). The list of these can either bs specified
manually, or AnyEvent::MP can guess them. Nodes without any binds are
possible to some extent.
=over 4
=item setbinds <host:port>,...
Sets the list of bind addresses explicitly - see the F<aemp setseeds>
command for the exact syntax. In addition, a value of C<*> for the port,
or not specifying a port, means to use a dynamically-assigned port.
Note that the C<*>, C<*:*> or C<*:port> patterns are very useful here.
Example: bind on a ephemeral port on all local interfaces.
aemp setbinds "*"
Example: bind on a random port on all local interfaces.
aemp setbinds "*:*"
Example: resolve "doomed.mydomain" and try to bind on port C<4040> of all
IP addressess returned.
aep setbinds doomed.mydomain:4040
=item delbinds
Removes the bind list again, which means it is inherited again from it's
parent profile, or stays unset.
=item addbind <host:port>
Adds a single bind address.
=item delbind <host:port>
Deletes the given bind address, if it exists.
=back
=head2 CONFIGURATION/SERVICES
Services are modules (or functions) that are automatically loaded (or
executed) when a node starts. They are especially useful when used in
conjunction with F<aemp run>, to configure which services a node should
run.
=over 4
=item profile <name> ...
This subcommand makes the following subcommands act only on a specific
named profile, instead of on the global default. The profile is created if
necessary.
Example: create a C<server> profile, give it a random node name, some seed
nodes and bind it on an unspecified port on all local interfaces. You
should add some services then and run the node...
aemp profile server nodeid anon/ seeds doomed,10.0.0.2:5000 binds "*:*"
=item delprofile <name>
Deletes the profile of the given name.
=item setparent <name>
Sets the parent profile to use - values not specified in a profile will be
taken from the parent profile (even recursively, with the global default
config being the default parent). This is useful to configure profile
},
gencert => sub {
$profile->{cert} = gen_cert;
++$cfg->{dirty};
},
delcert => sub {
delete $profile->{cert};
++$cfg->{dirty};
},
setbinds => sub {
@ARGV >= 1
or die "bind addresses missing\n";
my $list = shift @ARGV;
$profile->{binds} = ref $list ? $list : [split /,/, $list];
++$cfg->{dirty};
},
delbinds => sub {
delete $profile->{binds};
++$cfg->{dirty};
},
addbind => sub {
@ARGV >= 1
or die "bind address missing\n";
my $bind = shift @ARGV;
@{ $profile->{binds} } = grep $_ ne $bind, @{ $profile->{binds} };
push @{ $profile->{binds} }, $bind;
++$cfg->{dirty};
},
delbind => sub {
@ARGV >= 1
or die "bind address missing\n";
my $bind = shift @ARGV;
@{ $profile->{binds} } = grep $_ ne $bind, @{ $profile->{binds} };
++$cfg->{dirty};
},
setseeds => sub {
@ARGV >= 1
or die "seed addresses missing\n";
my $list = shift @ARGV;
$profile->{seeds} = ref $list ? $list : [split /,/, $list];
++$cfg->{dirty};
},
( run in 1.066 second using v1.01-cache-2.11-cpan-2398b32b56e )