227 lines
8.2 KiB
Plaintext
227 lines
8.2 KiB
Plaintext
=head1 NAME
|
||
|
||
App::Netdisco::Manual::WritingWorkers - Developer Documentation on Worker Plugins
|
||
|
||
=head1 Introduction
|
||
|
||
L<App::Netdisco>'s plugin system allows users to write I<workers> to gather
|
||
information from network devices using different I<transports> and store
|
||
results in the database.
|
||
|
||
For example, transports might be SNMP, SSH, or HTTPS. Workers might be
|
||
combining those transports with application protocols such as SNMP, NETCONF
|
||
(OpenConfig with XML), RESTCONF (OpenConfig with JSON), eAPI, or even CLI
|
||
scraping. The combination of transport and protocol is known as a I<driver>.
|
||
|
||
Workers can be restricted to certain vendor platforms using familiar ACL
|
||
syntax. They are also attached to specific actions in Netdisco's backend
|
||
operation (discover, macsuck, etc).
|
||
|
||
See L<App::Netdisco::Worker::Plugin> for more information about worker
|
||
plugins.
|
||
|
||
=head1 Developing Workers
|
||
|
||
A worker is Perl code which is run. Therefore it can do anything you like, but
|
||
typically it will make a connection to a device, gather some data, and store
|
||
it in Netdisco's database.
|
||
|
||
App::Netdisco plugins must load the L<App::Netdisco::Worker::Plugin> module.
|
||
This exports a helper subroutine to register the worker. Here's the
|
||
boilerplate code for our example plugin module:
|
||
|
||
package App::Netdisco::Worker::Plugin::Discover::Wireless::UniFi;
|
||
|
||
use Dancer ':syntax';
|
||
use App::Netdisco::Worker::Plugin;
|
||
use aliased 'App::Netdisco::Worker::Status';
|
||
|
||
# worker registration code goes here, ** see below **
|
||
|
||
true;
|
||
|
||
=head1 Registering a Worker
|
||
|
||
Use the C<register_worker> helper from L<App::Netdisco::Worker::Plugin> to
|
||
register a worker:
|
||
|
||
register_worker( $coderef );
|
||
# or
|
||
register_worker( \%workerconf, $coderef );
|
||
|
||
For example (using the second form):
|
||
|
||
register_worker({
|
||
driver => 'unifiapi',
|
||
}, sub { "worker code here" });
|
||
|
||
The C<%workerconf> hashref is optional, and described below. The C<$coderef>
|
||
is the main body of your worker. Your worker is run in a L<Try::Tiny>
|
||
statement to catch errors, and passed the following arguments:
|
||
|
||
$coderef->($job, \%workerconf);
|
||
|
||
The C<$job> is an instance of L<App::Netdisco::Backend::Job>. Note that this
|
||
class has a C<device> slot which may be filled, depending on the action, and
|
||
if the device is not yet discovered then the row will not yet be in storage.
|
||
The C<\%workerconf> hashref is the set of configuration parameters you used
|
||
to declare the worker (documented below).
|
||
|
||
=head2 Package Naming Convention
|
||
|
||
The package name used where the worker is declared is significant. Let's look
|
||
at the boilerplate example again:
|
||
|
||
package App::Netdisco::Worker::Plugin::Discover::Wireless::UniFi;
|
||
|
||
The package name B<must> contain C<Plugin::> and the namespace component after
|
||
that becomes the action. For example workers registered in the above package
|
||
will be run during the I<discover> backend action (that is, during a
|
||
C<discover> job). You can replace C<Discover> with other actions such as
|
||
C<Macsuck>, C<Arpnip>, C<Expire>, and C<Nbtstat>, or create your own.
|
||
|
||
The component after the action is known as the I<phase> (C<Wireless> in this
|
||
example), and is the way to override a Netdisco built-in worker, by using the
|
||
same name (plus an entry in C<%workerconf>, see below). Otherwise you can use
|
||
any valid Perl bareword for the phase.
|
||
|
||
Workers may also be registered directly to the action (C<Discover>, in this
|
||
example), without any phase. This is used for very early bootstrapping code
|
||
(such as first inserting a device into the database so it can be used by
|
||
subsequent phases) or for very simple, generic actions (such as C<netdisco-do
|
||
psql>).
|
||
|
||
=head2 C<%workerconf> Options
|
||
|
||
=over 4
|
||
|
||
=item ACL Options
|
||
|
||
Workers may have C<only> and C<no> parameters configured which use the
|
||
standard ACL syntax described in L<the settings
|
||
guide|App::Netdisco::Manual::Configuration>. The C<only> directive is
|
||
especially useful as it can restrict a worker to a given device platform or
|
||
operating system (for example Cisco IOS XR for the C<restconf> driver).
|
||
|
||
=item C<driver> (string)
|
||
|
||
The driver is a label associated with a group of workers and typically refers
|
||
to the combination of transport and application protocol. Examples include
|
||
C<snmp>, C<netconf>, C<restconf>, C<eapi>, and C<cli>. The convention is for
|
||
driver names to be lowercase.
|
||
|
||
Users will bind authentication configuration settings to drivers in their
|
||
configuration. If no driver is specified when registering a worker, it will be
|
||
run for every device and phase (such as during Expire jobs).
|
||
|
||
=item C<primary> (boolean)
|
||
|
||
When multiple workers are registered for the same phase, they will all be run.
|
||
However there is a special "I<primary>" slot for each phase in which only one
|
||
worker (the first that succeeds) is used. Most of Netdisco's built-in worker
|
||
code is registered in this way, so to override it you can use the same package
|
||
namespace and set C<primary> to be C<true>.
|
||
|
||
=back
|
||
|
||
=head1 Worker Execution and Return Code
|
||
|
||
Workers are configured as an ordered list. They are grouped by C<action> and
|
||
C<phase> (as in Package Naming Convention, above).
|
||
|
||
Workers defined in C<extra_worker_plugins> are run before those in
|
||
C<worker_plugins> so you have an opportunity to override built-in workers by
|
||
adding them to C<extra_worker_plugins> and setting C<primary> to C<true> in
|
||
the worker configuration.
|
||
|
||
The return code of the worker is significant for those configured with
|
||
C<primary> as C<true>: when the worker returns true, no other C<primary> hooks
|
||
are run for that phase. You should always use the aliased
|
||
L<App::Netdisco::Worker::Status> helper (loaded as in the boilerplate code
|
||
above) when returning a value, such as:
|
||
|
||
return Status->done('everything is good');
|
||
# or
|
||
return Status->error('something went wrong');
|
||
# or
|
||
return Status->defer('this device cannot be processed right now');
|
||
|
||
Remember that a worker is only run if it matches the hardware platform of the
|
||
target device and the user's configuration, and is not also excluded by the
|
||
user's configuration. This filtering takes place before inspecting C<primary>.
|
||
|
||
=head1 Accessing Transports
|
||
|
||
From your worker you will want to connect to a device to gather data. This is
|
||
done using a transport protocol session (SNMP, SSH, etc). Transports are
|
||
singleton objects instantiated on demand, so they can be shared among a set of
|
||
workers that are accessing the same device.
|
||
|
||
See the documentation for each transport to find out how to access it:
|
||
|
||
=over 4
|
||
|
||
=item *
|
||
|
||
L<App::Netdisco::Transport::SNMP>
|
||
|
||
=back
|
||
|
||
=head1 Database Connections
|
||
|
||
The Netdisco database is available via the C<netdisco> schema key, as below.
|
||
You can also use the C<external_databases> configuration item to set up
|
||
connections to other databases.
|
||
|
||
# plugin package
|
||
use Dancer::Plugin::DBIC;
|
||
my $set =
|
||
schema('netdisco')->resultset('Devices')
|
||
->search({vendor => 'cisco'});
|
||
|
||
=head1 Review of Terminology
|
||
|
||
In summary, Worker code is defined in a package namespace specifying the
|
||
Action and Phase, and registered as a plugin with configuration which may
|
||
specify the Driver and whether it is in the Primary slot. Access Control Lists
|
||
determine which Workers are permitted to run, and when. Here are more complete
|
||
definitions:
|
||
|
||
=over 4
|
||
|
||
=item C<action>
|
||
|
||
The highest level grouping of workers, corresponding to a Netdisco command
|
||
such as C<discover> or C<macsuck>. Workers can be registered at this level to
|
||
do really early bootstrapping work.
|
||
|
||
=item C<phase>
|
||
|
||
The next level down from C<action> for grouping workers. Phases have arbitrary
|
||
names and are visited in the order defined in the C<extra_worker_plugins>
|
||
setting list, followed by the C<worker_plugins> setting list. Workers are
|
||
usually registered at this level.
|
||
|
||
=item C<worker>
|
||
|
||
A lump of code you write which does a single clearly defined task. The package
|
||
namespace of the worker identifies the action and optionally the phase.
|
||
Workers are typically registered with some configuration settings.
|
||
|
||
=item C<driver>
|
||
|
||
A label associated with a group of workers which refers to a combination of
|
||
transport and application protocol used to connect to and communicate with the
|
||
target device. Users attach authentication configuration to specific drivers.
|
||
|
||
=item C<primary> (defaults to C<false>)
|
||
|
||
Indicates that the worker will only be run if no other C<primary> worker for
|
||
this phase has already succeeded. In this way, you can override Netdisco code
|
||
by setting this option and returning true from your worker.
|
||
|
||
=back
|
||
|
||
=cut
|
||
|