1package App::Netdisco::Worker::Runner; 2 3use Dancer qw/:moose :syntax/; 4use Dancer::Plugin::DBIC 'schema'; 5use App::Netdisco::Util::Device 'get_device'; 6use App::Netdisco::Util::Permission qw/check_acl_no check_acl_only/; 7use aliased 'App::Netdisco::Worker::Status'; 8 9use Try::Tiny; 10use Time::HiRes (); 11use Module::Load (); 12use Scope::Guard 'guard'; 13use Storable 'dclone'; 14use Sys::SigAction 'timeout_call'; 15 16use Moo::Role; 17use namespace::clean; 18 19with 'App::Netdisco::Worker::Loader'; 20has 'job' => ( is => 'rw' ); 21 22# mixin code to run workers loaded via plugins 23sub run { 24 my ($self, $job) = @_; 25 26 die 'cannot reuse a worker' if $self->job; 27 die 'bad job to run()' 28 unless ref $job eq 'App::Netdisco::Backend::Job'; 29 30 $self->job($job); 31 $job->device( get_device($job->device) ); 32 $self->load_workers(); 33 34 # finalise job status when we exit 35 my $statusguard = guard { $job->finalise_status }; 36 37 my @newuserconf = (); 38 my @userconf = @{ dclone (setting('device_auth') || []) }; 39 40 # reduce device_auth by only/no 41 if (ref $job->device) { 42 foreach my $stanza (@userconf) { 43 my $no = (exists $stanza->{no} ? $stanza->{no} : undef); 44 my $only = (exists $stanza->{only} ? $stanza->{only} : undef); 45 46 next if $no and check_acl_no($job->device, $no); 47 next if $only and not check_acl_only($job->device, $only); 48 49 push @newuserconf, dclone $stanza; 50 } 51 52 # per-device action but no device creds available 53 return $job->add_status( Status->defer('deferred job with no device creds') ) 54 if 0 == scalar @newuserconf && $job->action ne "delete"; 55 } 56 57 # back up and restore device_auth 58 my $configguard = guard { set(device_auth => \@userconf) }; 59 set(device_auth => \@newuserconf); 60 61 my $runner = sub { 62 my ($self, $job) = @_; 63 # roll everything back if we're testing 64 my $txn_guard = $ENV{ND2_DB_ROLLBACK} 65 ? schema('netdisco')->storage->txn_scope_guard : undef; 66 67 # run check phase and if there are workers then one MUST be successful 68 $self->run_workers('workers_check'); 69 70 # run other phases 71 if ($job->check_passed) { 72 $self->run_workers("workers_${_}") for qw/early main user store late/; 73 } 74 }; 75 76 my $maxtime = ((defined setting($job->action .'_timeout')) 77 ? setting($job->action .'_timeout') : setting('workers')->{'timeout'}); 78 if ($maxtime) { 79 debug sprintf '%s: running with timeout %ss', $job->action, $maxtime; 80 if (timeout_call($maxtime, $runner, ($self, $job))) { 81 debug sprintf '%s: timed out!', $job->action; 82 $job->add_status( Status->error("job timed out after $maxtime sec") ); 83 } 84 } 85 else { 86 $runner->($self, $job); 87 } 88} 89 90sub run_workers { 91 my $self = shift; 92 my $job = $self->job or die error 'no job in worker job slot'; 93 94 my $set = shift 95 or return $job->add_status( Status->error('missing set param') ); 96 return unless ref [] eq ref $self->$set and 0 < scalar @{ $self->$set }; 97 98 (my $phase = $set) =~ s/^workers_//; 99 $job->enter_phase($phase); 100 101 foreach my $worker (@{ $self->$set }) { 102 try { $job->add_status( $worker->($job) ) } 103 catch { 104 debug "-> $_" if $_; 105 $job->add_status( Status->error($_) ); 106 }; 107 } 108} 109 110true; 111