diff --git a/Netdisco/Makefile.PL b/Netdisco/Makefile.PL index 414f22d0..6a28378a 100644 --- a/Netdisco/Makefile.PL +++ b/Netdisco/Makefile.PL @@ -4,6 +4,7 @@ name 'App-Netdisco'; license 'bsd'; all_from 'lib/App/Netdisco.pm'; +requires 'Algorithm::Cron' => 0; requires 'App::cpanminus' => 0; requires 'App::local::lib::helper' => 0; requires 'DBD::Pg' => 0; diff --git a/Netdisco/bin/netdisco-daemon-fg b/Netdisco/bin/netdisco-daemon-fg index 718b6e88..1bc62e3e 100755 --- a/Netdisco/bin/netdisco-daemon-fg +++ b/Netdisco/bin/netdisco-daemon-fg @@ -46,6 +46,8 @@ sub build_tasks_list { user_begin => worker_factory('Manager'), }]; + set(daemon_schedulers => 1) + if !defined setting('daemon_schedulers'); set(daemon_pollers => 2) if !defined setting('daemon_pollers'); set(daemon_interactives => 2) @@ -53,6 +55,11 @@ sub build_tasks_list { # XXX MCE does not like max_workers => 0 + push @$tasks, { + max_workers => setting('daemon_schedulers'), + user_begin => worker_factory('Scheduler'), + } if setting('daemon_schedulers'); + push @$tasks, { max_workers => setting('daemon_pollers'), user_begin => worker_factory('Poller'), @@ -63,8 +70,11 @@ sub build_tasks_list { user_begin => worker_factory('Interactive'), } if setting('daemon_interactives'); - info sprintf "MCE will load %s tasks: 1 Manager, %s Poller, %s Interactive", - (1+ scalar @$tasks), (setting('daemon_pollers') || 0), (setting('daemon_interactives') || 0); + info sprintf "MCE will load %s tasks: 1 Manager, %s Scheduler, %s Poller, %s Interactive", + (1+ scalar @$tasks), + (setting('daemon_schedulers') || 0), + (setting('daemon_pollers') || 0), + (setting('daemon_interactives') || 0); return $tasks; } diff --git a/Netdisco/lib/App/Netdisco/Daemon/Worker/Scheduler.pm b/Netdisco/lib/App/Netdisco/Daemon/Worker/Scheduler.pm new file mode 100644 index 00000000..ce8fc506 --- /dev/null +++ b/Netdisco/lib/App/Netdisco/Daemon/Worker/Scheduler.pm @@ -0,0 +1,79 @@ +package App::Netdisco::Daemon::Worker::Scheduler; + +use Dancer qw/:moose :syntax :script/; +use Dancer::Plugin::DBIC 'schema'; + +use Algorithm::Cron; +use Try::Tiny; + +use Role::Tiny; +use namespace::clean; + +my $jobactions = { + map {$_ => undef} qw/ + discoverall + refresh + macwalk + arpwalk + nbtwalk + backup + / +}; + +sub worker_begin { + my $self = shift; + my $wid = $self->wid; + debug "entering Scheduler ($wid) worker_begin()"; + + foreach my $a (keys %$jobactions) { + next unless setting('job_schedule') + and exists setting('job_schedule')->{$a}; + my $config = setting('job_schedule')->{$a}; + + # accept either single crontab format, or individual time fields + my $cron = Algorithm::Cron->new(@{ + ref [] eq ref $config->{when} + ? $config->{when} + : [crontab => $config->{when}]; + }); + + $jobactions->{$a} = $config; + $jobactions->{$a}->{when} = $cron; + } +} + +sub worker_body { + my $self = shift; + my $wid = $self->wid; + + while (1) { + # sleep until some point in the next minute + my $naptime = 60 - (time % 60) + int(rand(45)); + debug "scheduler ($wid): sleeping for $naptime seconds"; + sleep $naptime; + + my $win_start = time - (time % 60); + my $win_end = $win_start + 60; + + # if any job is due, add it to the queue + foreach my $a (keys %$jobactions) { + next unless defined $jobactions->{$a}; + my $sched = $jobactions->{$a}; + + if ($sched->{when}->next_time($win_start) < $win_end) { + # queue it! + try { + debug "scheduler ($wid): queueing $a job"; + schema('netdisco')->resultset('Admin')->create({ + action => $a, + device => ($sched->{device} || undef), + subaction => ($sched->{extra} || undef), + status => 'queued', + }); + }; + } + } + } +} + +1; diff --git a/Netdisco/share/config.yml b/Netdisco/share/config.yml index aa797a8a..f750e1b0 100644 --- a/Netdisco/share/config.yml +++ b/Netdisco/share/config.yml @@ -72,6 +72,7 @@ daemon_sleep_time: 5 # how many daemon processes # NB one worker will always be a Queue Manager -daemon_pollers: 0 +daemon_schedulers: 1 daemon_interactives: 2 +daemon_pollers: 0