make an App::Netdisco dist using Module::Install
This commit is contained in:
23
Netdisco/lib/App/Netdisco/Daemon/DB.pm
Normal file
23
Netdisco/lib/App/Netdisco/Daemon/DB.pm
Normal file
@@ -0,0 +1,23 @@
|
||||
use utf8;
|
||||
package App::Netdisco::Daemon::DB;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Schema';
|
||||
|
||||
__PACKAGE__->load_namespaces;
|
||||
|
||||
our $VERSION = 1; # schema version used for upgrades, keep as integer
|
||||
|
||||
use Path::Class;
|
||||
use File::Basename;
|
||||
|
||||
my (undef, $libpath, undef) = fileparse( $INC{ 'App/Netdisco/Daemon/DB.pm' } );
|
||||
our $schema_versions_dir = Path::Class::Dir->new($libpath)
|
||||
->subdir("DB", "schema_versions")->stringify;
|
||||
|
||||
__PACKAGE__->load_components(qw/Schema::Versioned/);
|
||||
__PACKAGE__->upgrade_directory($schema_versions_dir);
|
||||
|
||||
1;
|
||||
37
Netdisco/lib/App/Netdisco/Daemon/DB/Result/Admin.pm
Normal file
37
Netdisco/lib/App/Netdisco/Daemon/DB/Result/Admin.pm
Normal file
@@ -0,0 +1,37 @@
|
||||
use utf8;
|
||||
package App::Netdisco::Daemon::DB::Result::Admin;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("admin");
|
||||
__PACKAGE__->add_columns(
|
||||
"job",
|
||||
{
|
||||
data_type => "integer",
|
||||
is_nullable => 0,
|
||||
},
|
||||
"started",
|
||||
{ data_type => "timestamp", is_nullable => 1 },
|
||||
"finished",
|
||||
{ data_type => "timestamp", is_nullable => 1 },
|
||||
"device",
|
||||
{ data_type => "inet", is_nullable => 1 },
|
||||
"port",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"action",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"subaction",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"status",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"log",
|
||||
{ data_type => "text", is_nullable => 1 },
|
||||
"debug",
|
||||
{ data_type => "boolean", is_nullable => 1 },
|
||||
);
|
||||
|
||||
__PACKAGE__->set_primary_key("job");
|
||||
|
||||
1;
|
||||
111
Netdisco/lib/App/Netdisco/Daemon/Worker/Interactive.pm
Normal file
111
Netdisco/lib/App/Netdisco/Daemon/Worker/Interactive.pm
Normal file
@@ -0,0 +1,111 @@
|
||||
package App::Netdisco::Daemon::Worker::Interactive;
|
||||
|
||||
use Dancer qw/:moose :syntax :script/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
use Try::Tiny;
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
# add dispatch methods for interactive actions
|
||||
with 'App::Netdisco::Daemon::Worker::Interactive::DeviceActions',
|
||||
'App::Netdisco::Daemon::Worker::Interactive::PortActions';
|
||||
|
||||
sub worker_body {
|
||||
my $self = shift;
|
||||
|
||||
# get all pending jobs
|
||||
my $rs = schema('daemon')->resultset('Admin')->search({
|
||||
action => [qw/location contact portcontrol portname vlan power/],
|
||||
status => 'queued',
|
||||
});
|
||||
|
||||
while (1) {
|
||||
while (my $job = $rs->next) {
|
||||
my $target = 'set_'. $job->action;
|
||||
next unless $self->can($target);
|
||||
|
||||
# mark job as running
|
||||
next unless $self->lock_job($job);
|
||||
|
||||
# do job
|
||||
my ($status, $log);
|
||||
try {
|
||||
($status, $log) = $self->$target($job);
|
||||
}
|
||||
catch { warn "error running job: $_\n" };
|
||||
|
||||
# revert to queued status if we failed to action the job
|
||||
if (not $status) {
|
||||
$self->revert_job($job->job);
|
||||
}
|
||||
else {
|
||||
# update job state to done/error with log
|
||||
$self->close_job($job->job, $status, $log);
|
||||
}
|
||||
}
|
||||
|
||||
# reset iterator so ->next() triggers another DB query
|
||||
$rs->reset;
|
||||
$self->gd_sleep( setting('daemon_sleep_time') || 5 );
|
||||
}
|
||||
}
|
||||
|
||||
sub lock_job {
|
||||
my ($self, $job) = @_;
|
||||
my $happy = 1;
|
||||
|
||||
# lock db table, check job state is still queued, update to running
|
||||
try {
|
||||
my $status_updated = schema('daemon')->txn_do(sub {
|
||||
my $row = schema('daemon')->resultset('Admin')->find(
|
||||
{job => $job->job},
|
||||
{for => 'update'}
|
||||
);
|
||||
|
||||
$happy = 0 if $row->status ne 'queued';
|
||||
$row->update({status => "running-$$", started => \"datetime('now')" });
|
||||
});
|
||||
|
||||
$happy = 0 if not $status_updated;
|
||||
}
|
||||
catch {
|
||||
warn "error locking job: $_\n";
|
||||
$happy = 0;
|
||||
};
|
||||
|
||||
return $happy;
|
||||
}
|
||||
|
||||
sub revert_job {
|
||||
my ($self, $id) = @_;
|
||||
|
||||
try {
|
||||
schema('daemon')->resultset('Admin')
|
||||
->find($id)
|
||||
->update({status => 'queued', started => undef});
|
||||
}
|
||||
catch { warn "error reverting job: $_\n" };
|
||||
}
|
||||
|
||||
sub close_job {
|
||||
my ($self, $id, $status, $log) = @_;
|
||||
|
||||
try {
|
||||
my $local = schema('daemon')->resultset('Admin')->find($id);
|
||||
|
||||
schema('netdisco')->resultset('Admin')
|
||||
->find($id)
|
||||
->update({
|
||||
status => $status,
|
||||
log => $log,
|
||||
started => $local->started,
|
||||
finished => \'now()',
|
||||
});
|
||||
|
||||
$local->delete;
|
||||
}
|
||||
catch { warn "error closing job: $_\n" };
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,49 @@
|
||||
package App::Netdisco::Daemon::Worker::Interactive::DeviceActions;
|
||||
|
||||
use App::Netdisco::Util::Connect qw/snmp_connect get_device/;
|
||||
use App::Netdisco::Daemon::Worker::Interactive::Util ':all';
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
sub set_location {
|
||||
my ($self, $job) = @_;
|
||||
return _set_device_generic($job->device, 'location', $job->subaction);
|
||||
}
|
||||
|
||||
sub set_contact {
|
||||
my ($self, $job) = @_;
|
||||
return _set_device_generic($job->device, 'contact', $job->subaction);
|
||||
}
|
||||
|
||||
sub _set_device_generic {
|
||||
my ($ip, $slot, $data) = @_;
|
||||
$data ||= '';
|
||||
|
||||
# snmp connect using rw community
|
||||
my $info = snmp_connect($ip)
|
||||
or return error("Failed to connect to device [$ip] to update $slot");
|
||||
|
||||
my $method = 'set_'. $slot;
|
||||
my $rv = $info->$method($data);
|
||||
|
||||
if (!defined $rv) {
|
||||
return error(sprintf 'Failed to set %s on [%s]: %s',
|
||||
$slot, $ip, ($info->error || ''));
|
||||
}
|
||||
|
||||
# confirm the set happened
|
||||
$info->clear_cache;
|
||||
my $new_data = ($info->$slot || '');
|
||||
if ($new_data ne $data) {
|
||||
return error("Verify of $slot update failed on [$ip]: $new_data");
|
||||
}
|
||||
|
||||
# update netdisco DB
|
||||
my $device = get_device($ip);
|
||||
$device->update({$slot => $data});
|
||||
|
||||
return done("Updated $slot on [$ip] to [$data]");
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -0,0 +1,136 @@
|
||||
package App::Netdisco::Daemon::Worker::Interactive::PortActions;
|
||||
|
||||
use App::Netdisco::Util::Connect ':all';
|
||||
use App::Netdisco::Util::Permissions ':all';
|
||||
use App::Netdisco::Daemon::Worker::Interactive::Util ':all';
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
sub set_portname {
|
||||
my ($self, $job) = @_;
|
||||
return _set_port_generic($job, 'alias', 'name');
|
||||
}
|
||||
|
||||
sub set_portcontrol {
|
||||
my ($self, $job) = @_;
|
||||
|
||||
my $port = get_port($job->device, $job->port)
|
||||
or return error(sprintf "Unknown port name [%s] on device [%s]",
|
||||
$job->port, $job->device);
|
||||
|
||||
my $reconfig_check = port_reconfig_check($port);
|
||||
return error("Cannot alter port: $reconfig_check")
|
||||
if length $reconfig_check;
|
||||
|
||||
return _set_port_generic($job, 'up_admin');
|
||||
}
|
||||
|
||||
sub set_vlan {
|
||||
my ($self, $job) = @_;
|
||||
|
||||
my $port = get_port($job->device, $job->port)
|
||||
or return error(sprintf "Unknown port name [%s] on device [%s]",
|
||||
$job->port, $job->device);
|
||||
|
||||
my $port_reconfig_check = port_reconfig_check($port);
|
||||
return error("Cannot alter port: $port_reconfig_check")
|
||||
if length $port_reconfig_check;
|
||||
|
||||
my $vlan_reconfig_check = vlan_reconfig_check($port);
|
||||
return error("Cannot alter vlan: $vlan_reconfig_check")
|
||||
if length $vlan_reconfig_check;
|
||||
|
||||
return _set_port_generic($job, 'vlan');
|
||||
}
|
||||
|
||||
sub _set_port_generic {
|
||||
my ($job, $slot, $column) = @_;
|
||||
$column ||= $slot;
|
||||
|
||||
my $ip = $job->device;
|
||||
my $pn = $job->port;
|
||||
(my $data = $job->subaction) =~ s/-\w+//;
|
||||
|
||||
my $port = get_port($ip, $pn)
|
||||
or return error("Unknown port name [$pn] on device [$ip]");
|
||||
|
||||
# snmp connect using rw community
|
||||
my $info = snmp_connect($ip)
|
||||
or return error("Failed to connect to device [$ip] to control port");
|
||||
|
||||
my $iid = get_iid($info, $port)
|
||||
or return error("Failed to get port ID for [$pn] from [$ip]");
|
||||
|
||||
my $method = 'set_i_'. $slot;
|
||||
my $rv = $info->$method($data, $iid);
|
||||
|
||||
if (!defined $rv) {
|
||||
return error(sprintf 'Failed to set [%s] %s to [%s] on [%s]: %s',
|
||||
$pn, $slot, $data, $ip, ($info->error || ''));
|
||||
}
|
||||
|
||||
# confirm the set happened
|
||||
$info->clear_cache;
|
||||
my $check_method = 'i_'. $slot;
|
||||
my $state = ($info->$check_method($iid) || '');
|
||||
if (ref {} ne ref $state or $state->{$iid} ne $data) {
|
||||
return error("Verify of [$pn] $slot failed on [$ip]");
|
||||
}
|
||||
|
||||
# update netdisco DB
|
||||
$port->update({$column => $data});
|
||||
|
||||
return done("Updated [$pn] $slot status on [$ip] to [$data]");
|
||||
}
|
||||
|
||||
sub set_power {
|
||||
my ($self, $job) = @_;
|
||||
|
||||
my $port = get_port($job->device, $job->port)
|
||||
or return error(sprintf "Unknown port name [%s] on device [%s]",
|
||||
$job->port, $job->device);
|
||||
|
||||
return error("No PoE service on port [%s] on device [%s]")
|
||||
unless $port->power;
|
||||
|
||||
my $reconfig_check = port_reconfig_check($port);
|
||||
return error("Cannot alter port: $reconfig_check")
|
||||
if length $reconfig_check;
|
||||
|
||||
|
||||
my $ip = $job->device;
|
||||
my $pn = $job->port;
|
||||
(my $data = $job->subaction) =~ s/-\w+//;
|
||||
|
||||
# snmp connect using rw community
|
||||
my $info = snmp_connect($ip)
|
||||
or return error("Failed to connect to device [$ip] to control port");
|
||||
|
||||
my $powerid = get_powerid($info, $port)
|
||||
or return error("Failed to get power ID for [$pn] from [$ip]");
|
||||
|
||||
my $rv = $info->set_peth_port_admin($data, $powerid);
|
||||
|
||||
if (!defined $rv) {
|
||||
return error(sprintf 'Failed to set [%s] power to [%s] on [%s]: %s',
|
||||
$pn, $data, $ip, ($info->error || ''));
|
||||
}
|
||||
|
||||
# confirm the set happened
|
||||
$info->clear_cache;
|
||||
my $state = ($info->peth_port_admin($powerid) || '');
|
||||
if (ref {} ne ref $state or $state->{$powerid} ne $data) {
|
||||
return error("Verify of [$pn] power failed on [$ip]");
|
||||
}
|
||||
|
||||
# update netdisco DB
|
||||
$port->power->update({
|
||||
admin => $data,
|
||||
status => ($data eq 'false' ? 'disabled' : 'searching'),
|
||||
});
|
||||
|
||||
return done("Updated [$pn] power status on [$ip] to [$data]");
|
||||
}
|
||||
|
||||
1;
|
||||
15
Netdisco/lib/App/Netdisco/Daemon/Worker/Interactive/Util.pm
Normal file
15
Netdisco/lib/App/Netdisco/Daemon/Worker/Interactive/Util.pm
Normal file
@@ -0,0 +1,15 @@
|
||||
package App::Netdisco::Daemon::Worker::Interactive::Util;
|
||||
|
||||
# support utilities for Daemon Actions
|
||||
|
||||
use base 'Exporter';
|
||||
our @EXPORT = ();
|
||||
our @EXPORT_OK = qw/ done error /;
|
||||
our %EXPORT_TAGS = (
|
||||
all => [qw/ done error /],
|
||||
);
|
||||
|
||||
sub done { return ('done', shift) }
|
||||
sub error { return ('error', shift) }
|
||||
|
||||
1;
|
||||
123
Netdisco/lib/App/Netdisco/Daemon/Worker/Manager.pm
Normal file
123
Netdisco/lib/App/Netdisco/Daemon/Worker/Manager.pm
Normal file
@@ -0,0 +1,123 @@
|
||||
package App::Netdisco::Daemon::Worker::Manager;
|
||||
|
||||
use Dancer qw/:moose :syntax :script/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
use App::Netdisco::Util::DeviceProperties 'is_discoverable';
|
||||
use Try::Tiny;
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
sub worker_begin {
|
||||
my $self = shift;
|
||||
my $daemon = schema('daemon');
|
||||
|
||||
# deploy local db if not already done
|
||||
try {
|
||||
$daemon->storage->dbh_do(sub {
|
||||
my ($storage, $dbh) = @_;
|
||||
$dbh->selectrow_arrayref("SELECT * FROM admin WHERE 0 = 1");
|
||||
});
|
||||
}
|
||||
catch { $daemon->txn_do( $daemon->deploy ) };
|
||||
|
||||
$daemon->storage->disconnect;
|
||||
if ($daemon->get_db_version < $daemon->schema_version) {
|
||||
$daemon->txn_do( $daemon->upgrade );
|
||||
}
|
||||
|
||||
# on start, any jobs previously grabbed by a daemon on this host
|
||||
# will be reset to "queued", which is the simplest way to restart them.
|
||||
|
||||
my $rs = schema('netdisco')->resultset('Admin')->search({
|
||||
status => "running-$self->{nd_host}"
|
||||
});
|
||||
|
||||
if ($rs->count > 0) {
|
||||
$daemon->resultset('Admin')->delete;
|
||||
$rs->update({status => 'queued', started => undef});
|
||||
}
|
||||
}
|
||||
|
||||
sub worker_body {
|
||||
my $self = shift;
|
||||
|
||||
# get all pending jobs
|
||||
my $rs = schema('netdisco')->resultset('Admin')
|
||||
->search({status => 'queued'});
|
||||
|
||||
while (1) {
|
||||
while (my $job = $rs->next) {
|
||||
# filter for discover_*
|
||||
next unless is_discoverable($job->device);
|
||||
|
||||
# mark job as running
|
||||
next unless $self->lock_job($job);
|
||||
|
||||
# copy job to local queue
|
||||
$self->copy_job($job)
|
||||
or $self->revert_job($job->job);
|
||||
}
|
||||
|
||||
# reset iterator so ->next() triggers another DB query
|
||||
$rs->reset;
|
||||
$self->gd_sleep( setting('daemon_sleep_time') || 5 );
|
||||
}
|
||||
}
|
||||
|
||||
sub lock_job {
|
||||
my ($self, $job) = @_;
|
||||
my $happy = 1;
|
||||
|
||||
# lock db table, check job state is still queued, update to running
|
||||
try {
|
||||
my $status_updated = schema('netdisco')->txn_do(sub {
|
||||
my $row = schema('netdisco')->resultset('Admin')->find(
|
||||
{job => $job->job},
|
||||
{for => 'update'}
|
||||
);
|
||||
|
||||
$happy = 0 if $row->status ne 'queued';
|
||||
$row->update({
|
||||
status => "running-$self->{nd_host}",
|
||||
started => \'now()'
|
||||
});
|
||||
});
|
||||
|
||||
$happy = 0 if not $status_updated;
|
||||
}
|
||||
catch {
|
||||
warn "error locking job: $_\n";
|
||||
$happy = 0;
|
||||
};
|
||||
|
||||
return $happy;
|
||||
}
|
||||
|
||||
sub copy_job {
|
||||
my ($self, $job) = @_;
|
||||
|
||||
try {
|
||||
my %data = $job->get_columns;
|
||||
delete $data{$_} for qw/entered username userip/;
|
||||
|
||||
schema('daemon')->resultset('Admin')->update_or_create({
|
||||
%data, status => 'queued', started => undef,
|
||||
});
|
||||
}
|
||||
catch { warn "error copying job: $_\n" };
|
||||
}
|
||||
|
||||
sub revert_job {
|
||||
my ($self, $id) = @_;
|
||||
|
||||
try {
|
||||
schema('netdisco')->resultset('Admin')
|
||||
->find($id)
|
||||
->update({status => 'queued', started => undef});
|
||||
}
|
||||
catch { warn "error reverting job: $_\n" };
|
||||
}
|
||||
|
||||
1;
|
||||
Reference in New Issue
Block a user