Files
netdisco/lib/App/Netdisco/Backend/Job.pm
Oliver Gorwits 826e1db39d API Endpoints to submit arpnip and macsuck results (#942)
* Add macsuck worker to collect various PortAccessEntity (NAC) attributes

* Incorporate PAE feedback on #937

 * missing Result/Device.pm column added
 * pae_is... columns instead of pae_capabilities
 * moved most code to Util/PortAccessEntity.pm so the update can
   be done in discover and macsuck

* Refactor PAE attributes during discover as separate Plugin

* PortAccessEntity: don't use device->dns in log string

* Fix "Experimental keys on scalar is now forbidden" test failure

* Revamp pae_control and add missing attribute

 - device.pae_control (text) is now device.pae_is_enabled (bool)
 - also store pae_authconfig_port_control (port mode auto/force(un)Auth)

* Fix "Experimental keys on scalar is now forbidden" test failure

 - ... again because of botched merge
 - at least perlgolfed away a set of curly braces

* Update PortAccessEntity.pm

* Incorporate @ollyg PR feedback

* allow actions without transport to run when there are also no creds

* initial refactor for separate gather, process, store phases for macsuck

* factor out the vlan sanity check

* additional help with log of action workers

* cleanup logic in check macsuck

* refactor to make main phases only

* some fixes

* implement file slurp. amazingly the whole thing works

* remove outdated noop from test

* treat error as critical, use cancel to suppress further drivers

* big refactor to share mac sanity code to both paths

* fix inverted logic on vlan sanity filter

* some code tidy

* fix error in default value

* fix for vlan 0 nodes input from cli

* ensure imported MACs are IEEE format

* add api endpoint, no useful return status yet

* exit status if error from nodes PUT

* suppress other networked workers when direct workers are active

* better log showing worker

* fix status recording to get first error or last done message

* implement arpnip API PUT

* avoid package redeclaration error

* make sure write API methods require admin status

* add doc for passing JSON data to arpnip and macsuck

* update manifest

* remove option to do jobs in web handler; all by queue now

* use job entry timestamp for offline queued jobs

* fix store username and IP on api PUT

* never de-duplicate user-submitted jobs; never reset DeviceSkip for offline jobs

* myworker no longer needed

* make logic cleaner

Co-authored-by: Christian Ramseyer <ramseyer@netnea.com>
2022-11-25 15:24:23 +00:00

257 lines
5.3 KiB
Perl
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package App::Netdisco::Backend::Job;
use Dancer qw/:moose :syntax !error/;
use aliased 'App::Netdisco::Worker::Status';
use Moo;
use namespace::clean;
foreach my $slot (qw/
job
entered
started
finished
device
port
action
only_namespace
subaction
status
username
userip
log
device_key
job_priority
is_cancelled
is_offline
_current_phase
_last_namespace
_last_priority
/) {
has $slot => (
is => 'rw',
);
}
has '_statuslist' => (
is => 'rw',
default => sub { [] },
);
sub BUILD {
my ($job, $args) = @_;
if ($job->action =~ m/^(\w+)::(\w+)$/i) {
$job->action($1);
$job->only_namespace($2);
}
if (!defined $job->subaction) {
$job->subaction('');
}
}
=head1 METHODS
=head2 display_name
An attempt to make a meaningful written statement about the job.
=cut
sub display_name {
my $job = shift;
return join ' ',
$job->action,
($job->device || ''),
($job->port || '');
}
=head2 cancel
Log a status and prevent other stages from running.
=cut
sub cancel {
my ($job, $msg) = @_;
$msg ||= 'unknown reason for cancelled job';
$job->is_cancelled(true);
return Status->error($msg);
}
=head2 best_status
Find the best status so far. The process is to track back from the last worker
and find the highest scoring status, skipping the check phase.
=cut
sub best_status {
my $job = shift;
my $cur_level = 0;
my $cur_status = '';
foreach my $status (reverse @{ $job->_statuslist }) {
next if $status->phase
and $status->phase !~ m/^(?:early|main|store|late)$/;
if ($status->level >= $cur_level) {
$cur_level = $status->level;
$cur_status = $status->status;
}
}
return $cur_status;
}
=head2 finalise_status
Find the best status and log it into the job's C<status> and C<log> slots.
=cut
sub finalise_status {
my $job = shift;
# use DDP; p $job->_statuslist;
# fallback
$job->status('error');
$job->log('failed to report from any worker!');
my $max_level = 0;
foreach my $status (reverse @{ $job->_statuslist }) {
next if $status->phase
and $status->phase !~ m/^(?:check|early|main|store|late)$/;
# done() from check phase should not be the action's done()
next if $status->phase eq 'check' and $status->is_ok;
# for done() we want the latest log message
# for error() (and others) we want the earliest log message
if (($max_level != Status->done()->level and $status->level >= $max_level)
or ($status->level > $max_level)) {
$job->status( $status->status );
$job->log( $status->log );
$max_level = $status->level;
}
}
}
=head2 check_passed
Returns true if at least one worker during the C<check> phase flagged status
C<done>.
=cut
sub check_passed {
my $job = shift;
return true if 0 == scalar @{ $job->_statuslist };
foreach my $status (@{ $job->_statuslist }) {
return true if
(($status->phase eq 'check') and $status->is_ok);
}
return false;
}
=head2 namespace_passed( \%workerconf )
Returns true when, for the namespace specified in the given configuration, a
worker of a higher priority level has already succeeded.
=cut
sub namespace_passed {
my ($job, $workerconf) = @_;
if ($job->_last_namespace) {
foreach my $status (@{ $job->_statuslist }) {
next unless ($status->phase eq $workerconf->{phase})
and ($workerconf->{namespace} eq $job->_last_namespace)
and ($workerconf->{priority} < $job->_last_priority);
return true if $status->is_ok;
}
}
$job->_last_namespace( $workerconf->{namespace} );
$job->_last_priority( $workerconf->{priority} );
return false;
}
=head2 enter_phase( $phase )
Pass the name of the phase being entered.
=cut
sub enter_phase {
my ($job, $phase) = @_;
$job->_current_phase( $phase );
debug "=> running workers for phase: $phase";
$job->_last_namespace( undef );
$job->_last_priority( undef );
}
=head2 add_status
Passed an L<App::Netdisco::Worker::Status> will add it to this job's internal
status cache. Phase slot of the Status will be set to the current phase.
=cut
sub add_status {
my ($job, $status) = @_;
return unless ref $status eq 'App::Netdisco::Worker::Status';
$status->phase( $job->_current_phase || '' );
push @{ $job->_statuslist }, $status;
debug $status->log if $status->log
and (($status->phase eq 'check') or $status->not_ok);
}
=head1 ADDITIONAL COLUMNS
Columns which exist in this class but are not in
L<App::Netdisco::DB::Result::Admin> class.
=head2 id
Alias for the C<job> column.
=cut
sub id { (shift)->job }
=head2 extra
Alias for the C<subaction> column.
=head2 only_namespace
Action command from the user can be an action name or the action name plus one
child namespace in the form: "C<action::child>". This slot stores the C<child>
component of the command so that C<action> is backwards compatible with
Netdisco.
=head2 job_priority
When selecting jobs from the database, some types of job are higher priority -
usually those submitted in the web interface by a user, and those making
changes (writing to) the device. This slot stores a number which is the
priority of the job and is used by L<MCE> when managing its job queue.
=cut
sub extra { (shift)->subaction }
true;