Merge the backend worker plugins branch og-coreplugins
Squashed commit of the following: commit86d0f61d0bAuthor: Oliver Gorwits <oliver@cpan.org> Date: Thu Nov 16 22:26:32 2017 +0000 fix typo commit5aff19621cAuthor: Oliver Gorwits <oliver@cpan.org> Date: Thu Nov 16 22:10:18 2017 +0000 fix use of snmp_connect_ip which does not work for SNMPv3 commit68a56d35bbAuthor: Oliver Gorwits <oliver@cpan.org> Date: Thu Nov 16 20:50:16 2017 +0000 no need for Array::Iterator even though it was cute commit71ee869c02Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Nov 15 22:14:47 2017 +0000 additional doc examples commit620b3fe544Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Nov 15 22:09:05 2017 +0000 stash workers within poller instance, and load plugins explicitly commit2431365583Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Nov 13 22:17:11 2017 +0000 better fix for duplicate module entity index commita400b26704Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Nov 13 22:14:42 2017 +0000 add ignore interfaces for HPE routers commit1502ec1966Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Nov 13 22:08:02 2017 +0000 bug fixes after testing on a real network commit840b6b4069Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Nov 12 20:38:35 2017 +0000 add tests commit2de36c69baAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sun Nov 12 00:14:21 2017 +0000 some reengineering to support proper testing commitc5f138fe62Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Nov 11 14:43:53 2017 +0000 correct algorithm on finalise status, correct logging commit98442a2308Author: Oliver Gorwits <oliver@cpan.org> Date: Thu Nov 9 21:49:45 2017 +0000 bug fixes commite0c6615c87Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Nov 8 20:29:33 2017 +0000 fix bugs commit1eeaba441dAuthor: Oliver Gorwits <oliver@cpan.org> Date: Tue Nov 7 22:30:55 2017 +0000 finish refactor to new desired behaviour (buggy?) commit7edfe88f25Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Nov 6 22:50:51 2017 +0000 fix to work, and correct namespace check commit25907d3544Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Nov 6 21:26:01 2017 +0000 move status tracking and checking inside job instance commit4436150bf4Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Nov 5 20:54:28 2017 +0000 remove global rubbish commit28b016e713Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Nov 4 23:31:51 2017 +0000 fix docs commit650f6c719bAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Nov 4 23:22:12 2017 +0000 tidy line commit10f78d5dbeAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Nov 4 23:06:20 2017 +0000 add priority and namespace to support fancy worker overrides commitb9f9816d09Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Oct 11 18:33:46 2017 +0100 release 2.036012_001 commitc33bf204a4Merge:5b7ce3f7d3d81eb6Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Oct 11 18:30:23 2017 +0100 Merge branch 'master' into og-coreplugins commit5b7ce3f797Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Oct 9 15:46:09 2017 +0100 cannot Sereal::Encode DBIC row commit0a575f02baAuthor: Oliver Gorwits <oliver@cpan.org> Date: Mon Oct 9 14:07:56 2017 +0100 fix bug in job->device init commit207476950dAuthor: Oliver Gorwits <oliver@cpan.org> Date: Mon Oct 9 14:03:37 2017 +0100 default causes no attr to be created?! commit912f2fa91fAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sun Oct 8 18:43:51 2017 +0100 better debug logging commitdfeb9d9ddcAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sun Oct 8 18:40:02 2017 +0100 make device_auth have driver setting for snmp entries commit460c0c0ee9Merge:3ccd107b98423445Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Oct 8 18:08:58 2017 +0100 Merge branch 'master' into og-coreplugins commit3ccd107bd4Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 7 14:13:58 2017 +0100 fix bug in device->has_layer commita4b9bf2036Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 7 13:58:52 2017 +0100 netdisco-do show takes a param for method in -p commit4389cd0459Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 7 13:36:06 2017 +0100 fix to only check last poll on devices in storage commit58d0fbdddaAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 7 13:21:13 2017 +0100 do not run discover parts if properties failed to complete commitb52aaaf1a1Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 7 13:08:46 2017 +0100 fix typo commit41be926921Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 7 13:04:45 2017 +0100 run all check workers commita41d114965Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 7 13:02:46 2017 +0100 fix driver config commitb10908a138Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 7 12:43:50 2017 +0100 use vars() cache between phases commit08b34e083dAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 7 11:39:17 2017 +0100 remove die() calls commitb8108986fbAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 7 11:31:59 2017 +0100 phase fixups commit273cbbc11bAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 7 09:42:41 2017 +0100 change stage to phase commit256c10bae5Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 7 09:35:14 2017 +0100 multi worker actions need not return done from all workers commitee38bae48aAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 7 09:05:25 2017 +0100 store result of worker if best for this phase so far commit5bddfc73baAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Oct 7 08:50:31 2017 +0100 auto debug-log worker return messages commit8b660a89c0Author: Oliver Gorwits <oliver@cpan.org> Date: Fri Oct 6 07:48:58 2017 +0100 bug fixes commitb58a5816a9Author: Oliver Gorwits <oliver@cpan.org> Date: Fri Oct 6 07:44:20 2017 +0100 remove unnecessary check phases commite44f06364aAuthor: Oliver Gorwits <oliver@cpan.org> Date: Fri Oct 6 07:18:03 2017 +0100 fix unknown command check in netdisco-do commit3af13f0dfeAuthor: Oliver Gorwits <oliver@cpan.org> Date: Fri Oct 6 07:15:59 2017 +0100 introduce noop and refactor checks in all workers commit98463c8cadAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sun Oct 1 10:49:12 2017 +0100 no need to debug log if there are no hooks in phase commit3b32e84312Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Oct 1 08:18:13 2017 +0100 fiddle about with runner logic to fix exit states commit8fdba38ee0Author: Oliver Gorwits <oliver@cpan.org> Date: Fri Sep 29 08:01:42 2017 +0100 cannot reuse a worker as the job will be already set and the wrong plugins loaded commita155d9cb77Author: Oliver Gorwits <oliver@cpan.org> Date: Fri Sep 29 08:01:06 2017 +0100 should defer when we cannot connect to device commit10b5f6cbc4Author: Oliver Gorwits <oliver@cpan.org> Date: Fri Sep 29 08:00:32 2017 +0100 fix bug in where workerconf acls are checked commit2a74e0befaAuthor: Oliver Gorwits <oliver@cpan.org> Date: Fri Sep 29 07:38:05 2017 +0100 can pass device instance to check_* commit4256b117dfAuthor: Oliver Gorwits <oliver@cpan.org> Date: Fri Sep 29 07:27:14 2017 +0100 move device_auth build to be with community defaults setting commita2de2c1616Merge:32be11c38dc4b9bcAuthor: Oliver Gorwits <oliver@cpan.org> Date: Fri Sep 29 07:21:03 2017 +0100 Merge branch 'master' into og-coreplugins commit32be11c3ffAuthor: Oliver Gorwits <oliver@cpan.org> Date: Thu Sep 21 00:09:29 2017 +0100 move remaining interactive actions to be plugins commit3e41c93f5aAuthor: Oliver Gorwits <oliver@cpan.org> Date: Wed Sep 20 21:47:50 2017 +0100 clean snmp handling commit30a2d5dd86Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Sep 20 21:00:29 2017 +0100 make sure check plugins are loaded/run before phases commit3454d95a84Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Sep 20 20:53:52 2017 +0100 capture result on main phase as well commit559fa4f93fAuthor: Oliver Gorwits <oliver@cpan.org> Date: Mon Sep 18 22:46:35 2017 +0100 build device_auth from communities commit1969291719Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Sep 18 22:04:22 2017 +0100 simplify to remove phases and fewer hooks commit6f78032e28Author: Oliver Gorwits <oliver@cpan.org> Date: Thu Sep 14 21:30:03 2017 +0100 add phase to test worker commit6edd2dc879Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Sep 13 21:51:40 2017 +0100 no need to list all plugins commitdfaeb34d8cAuthor: Oliver Gorwits <oliver@cpan.org> Date: Wed Sep 13 20:42:41 2017 +0100 add reset after messing with snmp context or community index commit09214dce92Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Sep 13 20:29:21 2017 +0100 no need to pass $snmp around commit58cd488cccAuthor: Oliver Gorwits <oliver@cpan.org> Date: Wed Sep 13 19:22:40 2017 +0100 refactor layer and pseudo checks commit753acc607fAuthor: Oliver Gorwits <oliver@cpan.org> Date: Wed Sep 13 10:53:12 2017 +0100 use overloaded $device commitd5d39289d6Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Sep 13 10:44:31 2017 +0100 rename init stage to check commit1fdb086183Author: Oliver Gorwits <oliver@cpan.org> Date: Tue Sep 12 08:12:12 2017 +0100 refactor to remove second loop commit64a9491115Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Sep 10 16:09:45 2017 +0100 change to init, first, second stages commit5f2da69697Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Sep 9 22:26:04 2017 +0100 move discover and discoverall to worker plugins commitc6ebb7cf07Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Sep 9 16:44:32 2017 +0100 move arpnip and arpwalk to worker plugins commit16a79463cbAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Sep 9 16:27:58 2017 +0100 set snmp driver on macsuck phase workers commit9167e02de5Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Sep 9 15:55:53 2017 +0100 move macsuck and macwalk to worker plugins (macsuck needs snmp scope guard) commit68ca85643bAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Sep 9 14:56:15 2017 +0100 move expire and expirenodes to worker plugins commit271ef1a25cAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Sep 9 14:46:00 2017 +0100 move nbtstat and nbtwalk to worker plugins commite7508a9ecaAuthor: Oliver Gorwits <oliver@cpan.org> Date: Wed Sep 6 21:23:54 2017 +0100 move all netdisco-do action to worker plugins commit707fc82b99Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Sep 6 21:01:37 2017 +0100 remove psql code from netdisco-do and fix detection of misspelled action commit411918e3f8Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Sep 6 20:56:26 2017 +0100 only load worker plugins for the action commit1f9740c0e2Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Sep 6 18:30:43 2017 +0100 shorten hook names commita59c23de79Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Sep 6 18:27:34 2017 +0100 make psql worker primary, add hook debug log commit36c70220a2Author: Oliver Gorwits <oliver@cpan.org> Date: Tue Sep 5 22:39:22 2017 +0100 allow two forms of worker declaration, and update docs commita79cb9a9e4Author: Oliver Gorwits <oliver@cpan.org> Date: Tue Sep 5 22:10:53 2017 +0100 all the bug fixes and a working plugin!!!!!!!!! :-D commit04896202e0Author: Oliver Gorwits <oliver@cpan.org> Date: Tue Sep 5 21:39:41 2017 +0100 refine runner commit547fce2f3cAuthor: Oliver Gorwits <oliver@cpan.org> Date: Tue Sep 5 20:56:21 2017 +0100 hack the status class to regen if needed commitcd71a0b7a8Author: Oliver Gorwits <oliver@cpan.org> Date: Tue Sep 5 20:41:05 2017 +0100 move status update to job class commitc8e5cea4edAuthor: Oliver Gorwits <oliver@cpan.org> Date: Tue Sep 5 20:37:13 2017 +0100 objectify the running commitf48004fffaAuthor: Oliver Gorwits <oliver@cpan.org> Date: Tue Sep 5 19:58:28 2017 +0100 bug squish commit46ece568f6Author: Oliver Gorwits <oliver@cpan.org> Date: Tue Sep 5 19:54:57 2017 +0100 implement runner?! commitfc9c60f707Author: Oliver Gorwits <oliver@cpan.org> Date: Tue Sep 5 19:28:38 2017 +0100 rename ok to is_ok and change slot names to avoid conflict with creators commit3ee85383abAuthor: Oliver Gorwits <oliver@cpan.org> Date: Tue Sep 5 19:25:41 2017 +0100 skip worker when action is per-device but no creds commit75abdad812Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Sep 4 21:54:37 2017 +0100 further work on retval handling from workers commit4c1fdf4f92Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Sep 4 20:37:53 2017 +0100 move worker plugin loader to Worker.pm commitbe0c5181a3Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Sep 4 20:35:42 2017 +0100 move Runner to Worker namespace commit1c2cf924bcAuthor: Oliver Gorwits <oliver@cpan.org> Date: Mon Sep 4 20:33:20 2017 +0100 worker roles in Role namespace commit3099eda393Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Sep 4 20:30:58 2017 +0100 load workers when runner role is loaded commita8c58a7b05Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Sep 3 22:30:28 2017 +0100 initial broken implementation of the runner commit49b5274c33Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Sep 3 19:04:20 2017 +0100 use run() mixin to exec action commite0a666668aAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sun Sep 3 18:54:44 2017 +0100 fix pod; set status defaults; stub runner mixin commit8eaa33770cAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sun Sep 3 18:45:00 2017 +0100 rename Core to Worker and move other packages around commit4def0af0b0Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Sep 3 17:58:03 2017 +0100 better use of new status class commit8675bf62c6Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Sep 3 17:27:38 2017 +0100 fix hook naming and implement primary workers commitef1bb81f2bAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sun Sep 3 17:26:27 2017 +0100 new backend status class commit5f50dfadf1Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Sep 3 16:51:55 2017 +0100 new Backend package to load core plugins commit3baa7a818aAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sun Sep 3 16:22:29 2017 +0100 remove unnecessary Worker::Common role commit36b4adcc06Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Sep 3 16:17:29 2017 +0100 disambiguate util/backend package and remove backend prelaod commit98bff731bdAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Sep 2 08:25:06 2017 +0100 settle on a design for hook override, I think commitfe5c16a16dAuthor: Oliver Gorwits <oliver@cpan.org> Date: Wed Aug 30 20:37:36 2017 +0100 rework docs to be more clear and reflect new operation commitb34ba1977cMerge:31d1977fc34ed61dAuthor: Oliver Gorwits <oliver@cpan.org> Date: Mon Aug 21 21:17:46 2017 +0100 Merge branch 'master' into og-coreplugins commit31d1977f1eAuthor: Oliver Gorwits <oliver@cpan.org> Date: Mon Aug 14 18:11:42 2017 +0100 Revert "move expire code to be initial plugin pilot (broken)" I think we'll only do the new backend code for jobs with a device. This reverts commit07998b72d9. commit61dc80aff8Merge:07998b72ade02db1Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Aug 14 18:10:29 2017 +0100 Merge branch 'master' into og-coreplugins commit07998b72d9Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Aug 5 22:15:00 2017 +0100 move expire code to be initial plugin pilot (broken) commit685ec02108Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Aug 5 22:10:58 2017 +0100 pass $job to the core worker commitd6523fe543Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Aug 5 22:01:49 2017 +0100 $job->device is always a DBIC row commitee6deea01bAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Aug 5 18:12:34 2017 +0100 load plugins commitfd80096ca2Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Aug 5 16:53:16 2017 +0100 rename all the things commit464c42d1f5Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Aug 2 10:19:16 2017 +0100 use Scope::Guard to reduce device_auth commitec041dafd2Author: Oliver Gorwits <oliver@cpan.org> Date: Tue Aug 1 15:34:37 2017 +0100 the other way around commit33d2fe13bdAuthor: Oliver Gorwits <oliver@cpan.org> Date: Mon Jul 31 17:57:29 2017 +0100 fix pod commit3faee1cf16Author: Oliver Gorwits <oliver@cpan.org> Date: Mon Jul 31 17:55:10 2017 +0100 remove need for instance() call commitc6d0f1c035Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Jul 26 13:51:23 2017 +0100 add doc note on accessing transports commitdca4b4fc03Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Jul 26 11:50:10 2017 +0100 add backend driver documentation commit052a2acd79Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Jul 26 10:16:58 2017 +0100 rename web plugins doc commit69c9a6393aAuthor: Oliver Gorwits <oliver@cpan.org> Date: Wed Jul 26 10:12:42 2017 +0100 rename args to driverconf commit2586a36f8cAuthor: Oliver Gorwits <oliver@cpan.org> Date: Tue Jul 25 22:41:10 2017 +0100 new version of core plugin manager with better config and filters commit4056831f99Author: Oliver Gorwits <oliver@cpan.org> Date: Tue Jul 25 20:53:56 2017 +0100 change SNMP to be a cached transport singleton commitc31030ef70Author: Oliver Gorwits <oliver@cpan.org> Date: Sun Jul 23 13:46:27 2017 +0100 fixes because Dancer docs are a mess! commitf65ef90b86Author: Oliver Gorwits <oliver@cpan.org> Date: Sat Jul 22 08:11:36 2017 +0100 rename snmp_auth to device_auth and include a little doc on transports commitd61556e1cfAuthor: Oliver Gorwits <oliver@cpan.org> Date: Sat Jul 22 07:54:26 2017 +0100 plugin config added commitde8de56308Author: Oliver Gorwits <oliver@cpan.org> Date: Wed Jul 12 21:38:31 2017 +0100 initial core plugin implementation
This commit is contained in:
7
Build.PL
7
Build.PL
@@ -13,6 +13,9 @@ Module::Build->new(
|
||||
build_requires => {
|
||||
},
|
||||
requires => {
|
||||
'aliased' => '0',
|
||||
'namespace::clean' => '0.24',
|
||||
'version' => '0.9902',
|
||||
'Algorithm::Cron' => '0.07',
|
||||
'AnyEvent' => '7.05',
|
||||
'AnyEvent::DNS::EtcHosts' => '0',
|
||||
@@ -40,6 +43,7 @@ Module::Build->new(
|
||||
'JSON::XS' => '3.01',
|
||||
'List::MoreUtils' => '0.33',
|
||||
'MIME::Base64' => '3.13',
|
||||
'Module::Find' => '0.13',
|
||||
'Module::Load' => '0.32',
|
||||
'Moo' => '1.001000',
|
||||
'MCE' => '1.703',
|
||||
@@ -57,6 +61,7 @@ Module::Build->new(
|
||||
'Plack::Middleware::ReverseProxy' => '0.15',
|
||||
'Pod::Usage' => 0,
|
||||
'Role::Tiny' => '1.002005',
|
||||
'Scope::Guard' => 0,
|
||||
'Sereal' => '0',
|
||||
'Socket6' => '0.23',
|
||||
'Starman' => '0.4008',
|
||||
@@ -72,8 +77,6 @@ Module::Build->new(
|
||||
'URL::Encode' => '0.01',
|
||||
'YAML' => '0.84',
|
||||
'YAML::XS' => '0.41',
|
||||
'namespace::clean' => '0.24',
|
||||
'version' => '0.9902',
|
||||
},
|
||||
recommends => {
|
||||
'Graph' => '0',
|
||||
|
||||
2
Changes
2
Changes
@@ -1,3 +1,5 @@
|
||||
2.036012_001 - EXPERIMENTAL RELEASE
|
||||
|
||||
2.036011 - 2017-10-09
|
||||
|
||||
[BUG FIXES]
|
||||
|
||||
70
MANIFEST
70
MANIFEST
@@ -16,24 +16,10 @@ Changes
|
||||
lib/App/Netdisco.pm
|
||||
lib/App/Netdisco/AnyEvent/Nbtstat.pm
|
||||
lib/App/Netdisco/Backend/Job.pm
|
||||
lib/App/Netdisco/Backend/Util.pm
|
||||
lib/App/Netdisco/Backend/Worker/Common.pm
|
||||
lib/App/Netdisco/Backend/Worker/Interactive/DeviceActions.pm
|
||||
lib/App/Netdisco/Backend/Worker/Interactive/PortActions.pm
|
||||
lib/App/Netdisco/Backend/Worker/Manager.pm
|
||||
lib/App/Netdisco/Backend/Worker/Poller.pm
|
||||
lib/App/Netdisco/Backend/Worker/Poller/Arpnip.pm
|
||||
lib/App/Netdisco/Backend/Worker/Poller/Common.pm
|
||||
lib/App/Netdisco/Backend/Worker/Poller/Device.pm
|
||||
lib/App/Netdisco/Backend/Worker/Poller/Expiry.pm
|
||||
lib/App/Netdisco/Backend/Worker/Poller/Macsuck.pm
|
||||
lib/App/Netdisco/Backend/Worker/Poller/Nbtstat.pm
|
||||
lib/App/Netdisco/Backend/Worker/Scheduler.pm
|
||||
lib/App/Netdisco/Backend/Role/Manager.pm
|
||||
lib/App/Netdisco/Backend/Role/Poller.pm
|
||||
lib/App/Netdisco/Backend/Role/Scheduler.pm
|
||||
lib/App/Netdisco/Configuration.pm
|
||||
lib/App/Netdisco/Core/Arpnip.pm
|
||||
lib/App/Netdisco/Core/Discover.pm
|
||||
lib/App/Netdisco/Core/Macsuck.pm
|
||||
lib/App/Netdisco/Core/Nbtstat.pm
|
||||
lib/App/Netdisco/DB.pm
|
||||
lib/App/Netdisco/DB/ExplicitLocking.pm
|
||||
lib/App/Netdisco/DB/Result/Admin.pm
|
||||
@@ -114,7 +100,8 @@ lib/App/Netdisco/Manual/ReleaseNotes.pod
|
||||
lib/App/Netdisco/Manual/Systemd.pod
|
||||
lib/App/Netdisco/Manual/Troubleshooting.pod
|
||||
lib/App/Netdisco/Manual/Vendors.pod
|
||||
lib/App/Netdisco/Manual/WritingPlugins.pod
|
||||
lib/App/Netdisco/Manual/WritingWebPlugins.pod
|
||||
lib/App/Netdisco/Manual/WritingWorkers.pod
|
||||
lib/App/Netdisco/SSHCollector/Platform/ACE.pm
|
||||
lib/App/Netdisco/SSHCollector/Platform/ASA.pm
|
||||
lib/App/Netdisco/SSHCollector/Platform/BigIP.pm
|
||||
@@ -126,12 +113,14 @@ lib/App/Netdisco/SSHCollector/Platform/IOSXR.pm
|
||||
lib/App/Netdisco/SSHCollector/Platform/Linux.pm
|
||||
lib/App/Netdisco/SSHCollector/Platform/NXOS.pm
|
||||
lib/App/Netdisco/SSHCollector/Platform/PaloAlto.pm
|
||||
lib/App/Netdisco/Util/Backend.pm
|
||||
lib/App/Netdisco/Transport/SNMP.pm
|
||||
lib/App/Netdisco/Util/Device.pm
|
||||
lib/App/Netdisco/Util/DNS.pm
|
||||
lib/App/Netdisco/Util/ExpandParams.pm
|
||||
lib/App/Netdisco/Util/FastResolver.pm
|
||||
lib/App/Netdisco/Util/Graph.pm
|
||||
lib/App/Netdisco/Util/MCE.pm
|
||||
lib/App/Netdisco/Util/Nbtstat.pm
|
||||
lib/App/Netdisco/Util/Node.pm
|
||||
lib/App/Netdisco/Util/NodeMonitor.pm
|
||||
lib/App/Netdisco/Util/Noop.pm
|
||||
@@ -203,6 +192,49 @@ lib/App/Netdisco/Web/Search.pm
|
||||
lib/App/Netdisco/Web/Static.pm
|
||||
lib/App/Netdisco/Web/Statistics.pm
|
||||
lib/App/Netdisco/Web/TypeAhead.pm
|
||||
lib/App/Netdisco/Worker.pm
|
||||
lib/App/Netdisco/Worker/Plugin.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Arpnip.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Arpnip/Nodes.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Arpnip/Subnets.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Arpwalk.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Contact.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Delete.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Discover.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Discover/CanonicalIP.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Discover/Entities.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Discover/Neighbors.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Discover/PortPower.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Discover/Properties.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Discover/VLANs.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Discover/Wireless.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Discover/WithNodes.pm
|
||||
lib/App/Netdisco/Worker/Plugin/DiscoverAll.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Expire.pm
|
||||
lib/App/Netdisco/Worker/Plugin/ExpireNodes.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Graph.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Location.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Macsuck.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Macsuck/Nodes.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Macsuck/WirelessNodes.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Macwalk.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Monitor.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Nbtstat.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Nbtstat/Core.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Nbtwalk.pm
|
||||
lib/App/Netdisco/Worker/Plugin/PortControl.pm
|
||||
lib/App/Netdisco/Worker/Plugin/PortName.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Power.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Psql.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Renumber.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Show.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Stats.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Test.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Test/Core.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Vlan.pm
|
||||
lib/App/Netdisco/Worker/Plugin/Vlan/Core.pm
|
||||
lib/App/Netdisco/Worker/Runner.pm
|
||||
lib/App/Netdisco/Worker/Status.pm
|
||||
lib/Dancer/Template/NetdiscoTemplateToolkit.pm
|
||||
LICENCE
|
||||
MANIFEST This list of files
|
||||
|
||||
204
META.json
204
META.json
@@ -56,6 +56,7 @@
|
||||
"List::MoreUtils" : "0.33",
|
||||
"MCE" : "1.703",
|
||||
"MIME::Base64" : "3.13",
|
||||
"Module::Find" : "0.13",
|
||||
"Module::Load" : "0.32",
|
||||
"Moo" : "1.001000",
|
||||
"Net::DNS" : "0.72",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Role::Tiny" : "1.002005",
|
||||
"SNMP::Info" : "3.37",
|
||||
"SQL::Translator" : "0.11018",
|
||||
"Scope::Guard" : "0",
|
||||
"Sereal" : "0",
|
||||
"Socket6" : "0.23",
|
||||
"Starman" : "0.4008",
|
||||
@@ -87,6 +89,7 @@
|
||||
"URL::Encode" : "0.01",
|
||||
"YAML" : "0.84",
|
||||
"YAML::XS" : "0.41",
|
||||
"aliased" : "0",
|
||||
"namespace::clean" : "0.24",
|
||||
"version" : "0.9902"
|
||||
}
|
||||
@@ -101,7 +104,7 @@
|
||||
"provides" : {
|
||||
"App::Netdisco" : {
|
||||
"file" : "lib/App/Netdisco.pm",
|
||||
"version" : "2.036011"
|
||||
"version" : "2.036012_001"
|
||||
},
|
||||
"App::Netdisco::AnyEvent::Nbtstat" : {
|
||||
"file" : "lib/App/Netdisco/AnyEvent/Nbtstat.pm"
|
||||
@@ -109,60 +112,18 @@
|
||||
"App::Netdisco::Backend::Job" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Job.pm"
|
||||
},
|
||||
"App::Netdisco::Backend::Util" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Util.pm"
|
||||
"App::Netdisco::Backend::Role::Manager" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Role/Manager.pm"
|
||||
},
|
||||
"App::Netdisco::Backend::Worker::Common" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Worker/Common.pm"
|
||||
"App::Netdisco::Backend::Role::Poller" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Role/Poller.pm"
|
||||
},
|
||||
"App::Netdisco::Backend::Worker::Interactive::DeviceActions" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Worker/Interactive/DeviceActions.pm"
|
||||
},
|
||||
"App::Netdisco::Backend::Worker::Interactive::PortActions" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Worker/Interactive/PortActions.pm"
|
||||
},
|
||||
"App::Netdisco::Backend::Worker::Manager" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Worker/Manager.pm"
|
||||
},
|
||||
"App::Netdisco::Backend::Worker::Poller" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Worker/Poller.pm"
|
||||
},
|
||||
"App::Netdisco::Backend::Worker::Poller::Arpnip" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Worker/Poller/Arpnip.pm"
|
||||
},
|
||||
"App::Netdisco::Backend::Worker::Poller::Common" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Worker/Poller/Common.pm"
|
||||
},
|
||||
"App::Netdisco::Backend::Worker::Poller::Device" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Worker/Poller/Device.pm"
|
||||
},
|
||||
"App::Netdisco::Backend::Worker::Poller::Expiry" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Worker/Poller/Expiry.pm"
|
||||
},
|
||||
"App::Netdisco::Backend::Worker::Poller::Macsuck" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Worker/Poller/Macsuck.pm"
|
||||
},
|
||||
"App::Netdisco::Backend::Worker::Poller::Nbtstat" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Worker/Poller/Nbtstat.pm"
|
||||
},
|
||||
"App::Netdisco::Backend::Worker::Scheduler" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Worker/Scheduler.pm"
|
||||
"App::Netdisco::Backend::Role::Scheduler" : {
|
||||
"file" : "lib/App/Netdisco/Backend/Role/Scheduler.pm"
|
||||
},
|
||||
"App::Netdisco::Configuration" : {
|
||||
"file" : "lib/App/Netdisco/Configuration.pm"
|
||||
},
|
||||
"App::Netdisco::Core::Arpnip" : {
|
||||
"file" : "lib/App/Netdisco/Core/Arpnip.pm"
|
||||
},
|
||||
"App::Netdisco::Core::Discover" : {
|
||||
"file" : "lib/App/Netdisco/Core/Discover.pm"
|
||||
},
|
||||
"App::Netdisco::Core::Macsuck" : {
|
||||
"file" : "lib/App/Netdisco/Core/Macsuck.pm"
|
||||
},
|
||||
"App::Netdisco::Core::Nbtstat" : {
|
||||
"file" : "lib/App/Netdisco/Core/Nbtstat.pm"
|
||||
},
|
||||
"App::Netdisco::DB" : {
|
||||
"file" : "lib/App/Netdisco/DB.pm",
|
||||
"version" : "44"
|
||||
@@ -413,8 +374,8 @@
|
||||
"App::Netdisco::SSHCollector::Platform::PaloAlto" : {
|
||||
"file" : "lib/App/Netdisco/SSHCollector/Platform/PaloAlto.pm"
|
||||
},
|
||||
"App::Netdisco::Util::Backend" : {
|
||||
"file" : "lib/App/Netdisco/Util/Backend.pm"
|
||||
"App::Netdisco::Transport::SNMP" : {
|
||||
"file" : "lib/App/Netdisco/Transport/SNMP.pm"
|
||||
},
|
||||
"App::Netdisco::Util::DNS" : {
|
||||
"file" : "lib/App/Netdisco/Util/DNS.pm"
|
||||
@@ -431,6 +392,12 @@
|
||||
"App::Netdisco::Util::Graph" : {
|
||||
"file" : "lib/App/Netdisco/Util/Graph.pm"
|
||||
},
|
||||
"App::Netdisco::Util::MCE" : {
|
||||
"file" : "lib/App/Netdisco/Util/MCE.pm"
|
||||
},
|
||||
"App::Netdisco::Util::Nbtstat" : {
|
||||
"file" : "lib/App/Netdisco/Util/Nbtstat.pm"
|
||||
},
|
||||
"App::Netdisco::Util::Node" : {
|
||||
"file" : "lib/App/Netdisco/Util/Node.pm"
|
||||
},
|
||||
@@ -644,18 +611,147 @@
|
||||
"App::Netdisco::Web::TypeAhead" : {
|
||||
"file" : "lib/App/Netdisco/Web/TypeAhead.pm"
|
||||
},
|
||||
"App::Netdisco::Worker" : {
|
||||
"file" : "lib/App/Netdisco/Worker.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Arpnip" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Arpnip.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Arpnip::Nodes" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Arpnip/Nodes.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Arpnip::Subnets" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Arpnip/Subnets.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Arpwalk" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Arpwalk.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Contact" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Contact.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Delete" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Delete.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Discover" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Discover.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Discover::CanonicalIP" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Discover/CanonicalIP.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Discover::Entities" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Discover/Entities.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Discover::Neighbors" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Discover/Neighbors.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Discover::PortPower" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Discover/PortPower.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Discover::Properties" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Discover/Properties.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Discover::VLANs" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Discover/VLANs.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Discover::Wireless" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Discover/Wireless.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Discover::WithNodes" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Discover/WithNodes.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::DiscoverAll" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/DiscoverAll.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Expire" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Expire.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::ExpireNodes" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/ExpireNodes.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Graph" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Graph.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Location" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Location.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Macsuck" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Macsuck.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Macsuck::Nodes" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Macsuck/Nodes.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Macsuck::WirelessNodes" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Macsuck/WirelessNodes.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Macwalk" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Macwalk.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Monitor" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Monitor.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Nbtstat" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Nbtstat.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Nbtstat::Core" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Nbtstat/Core.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Nbtwalk" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Nbtwalk.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::PortControl" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/PortControl.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::PortName" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/PortName.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Power" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Power.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Psql" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Psql.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Renumber" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Renumber.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Show" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Show.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Stats" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Stats.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Test" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Test.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Test::Core" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Test/Core.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Vlan" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Vlan.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Plugin::Vlan::Native" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Plugin/Vlan/Core.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Runner" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Runner.pm"
|
||||
},
|
||||
"App::Netdisco::Worker::Status" : {
|
||||
"file" : "lib/App/Netdisco/Worker/Status.pm"
|
||||
},
|
||||
"Dancer::Template::NetdiscoTemplateToolkit" : {
|
||||
"file" : "lib/Dancer/Template/NetdiscoTemplateToolkit.pm"
|
||||
}
|
||||
},
|
||||
"release_status" : "stable",
|
||||
"release_status" : "testing",
|
||||
"resources" : {
|
||||
"bugtracker" : {
|
||||
"web" : "https://github.com/netdisco/netdisco/issues"
|
||||
},
|
||||
"homepage" : "http://netdisco.org/",
|
||||
"license" : [
|
||||
"http://opensource.org/licenses/bsd-license.php"
|
||||
"http://opensource.org/licenses/BSD-3-Clause"
|
||||
],
|
||||
"repository" : {
|
||||
"url" : "https://github.com/netdisco/netdisco"
|
||||
@@ -663,6 +759,6 @@
|
||||
"x_IRC" : "irc://irc.freenode.org/#netdisco",
|
||||
"x_MailingList" : "https://lists.sourceforge.net/lists/listinfo/netdisco-users"
|
||||
},
|
||||
"version" : "2.036011",
|
||||
"version" : "2.036012_001",
|
||||
"x_serialization_backend" : "JSON::PP version 2.94"
|
||||
}
|
||||
|
||||
143
META.yml
143
META.yml
@@ -18,47 +18,19 @@ name: App-Netdisco
|
||||
provides:
|
||||
App::Netdisco:
|
||||
file: lib/App/Netdisco.pm
|
||||
version: '2.036011'
|
||||
version: 2.036012_001
|
||||
App::Netdisco::AnyEvent::Nbtstat:
|
||||
file: lib/App/Netdisco/AnyEvent/Nbtstat.pm
|
||||
App::Netdisco::Backend::Job:
|
||||
file: lib/App/Netdisco/Backend/Job.pm
|
||||
App::Netdisco::Backend::Util:
|
||||
file: lib/App/Netdisco/Backend/Util.pm
|
||||
App::Netdisco::Backend::Worker::Common:
|
||||
file: lib/App/Netdisco/Backend/Worker/Common.pm
|
||||
App::Netdisco::Backend::Worker::Interactive::DeviceActions:
|
||||
file: lib/App/Netdisco/Backend/Worker/Interactive/DeviceActions.pm
|
||||
App::Netdisco::Backend::Worker::Interactive::PortActions:
|
||||
file: lib/App/Netdisco/Backend/Worker/Interactive/PortActions.pm
|
||||
App::Netdisco::Backend::Worker::Manager:
|
||||
file: lib/App/Netdisco/Backend/Worker/Manager.pm
|
||||
App::Netdisco::Backend::Worker::Poller:
|
||||
file: lib/App/Netdisco/Backend/Worker/Poller.pm
|
||||
App::Netdisco::Backend::Worker::Poller::Arpnip:
|
||||
file: lib/App/Netdisco/Backend/Worker/Poller/Arpnip.pm
|
||||
App::Netdisco::Backend::Worker::Poller::Common:
|
||||
file: lib/App/Netdisco/Backend/Worker/Poller/Common.pm
|
||||
App::Netdisco::Backend::Worker::Poller::Device:
|
||||
file: lib/App/Netdisco/Backend/Worker/Poller/Device.pm
|
||||
App::Netdisco::Backend::Worker::Poller::Expiry:
|
||||
file: lib/App/Netdisco/Backend/Worker/Poller/Expiry.pm
|
||||
App::Netdisco::Backend::Worker::Poller::Macsuck:
|
||||
file: lib/App/Netdisco/Backend/Worker/Poller/Macsuck.pm
|
||||
App::Netdisco::Backend::Worker::Poller::Nbtstat:
|
||||
file: lib/App/Netdisco/Backend/Worker/Poller/Nbtstat.pm
|
||||
App::Netdisco::Backend::Worker::Scheduler:
|
||||
file: lib/App/Netdisco/Backend/Worker/Scheduler.pm
|
||||
App::Netdisco::Backend::Role::Manager:
|
||||
file: lib/App/Netdisco/Backend/Role/Manager.pm
|
||||
App::Netdisco::Backend::Role::Poller:
|
||||
file: lib/App/Netdisco/Backend/Role/Poller.pm
|
||||
App::Netdisco::Backend::Role::Scheduler:
|
||||
file: lib/App/Netdisco/Backend/Role/Scheduler.pm
|
||||
App::Netdisco::Configuration:
|
||||
file: lib/App/Netdisco/Configuration.pm
|
||||
App::Netdisco::Core::Arpnip:
|
||||
file: lib/App/Netdisco/Core/Arpnip.pm
|
||||
App::Netdisco::Core::Discover:
|
||||
file: lib/App/Netdisco/Core/Discover.pm
|
||||
App::Netdisco::Core::Macsuck:
|
||||
file: lib/App/Netdisco/Core/Macsuck.pm
|
||||
App::Netdisco::Core::Nbtstat:
|
||||
file: lib/App/Netdisco/Core/Nbtstat.pm
|
||||
App::Netdisco::DB:
|
||||
file: lib/App/Netdisco/DB.pm
|
||||
version: '44'
|
||||
@@ -226,8 +198,8 @@ provides:
|
||||
file: lib/App/Netdisco/SSHCollector/Platform/NXOS.pm
|
||||
App::Netdisco::SSHCollector::Platform::PaloAlto:
|
||||
file: lib/App/Netdisco/SSHCollector/Platform/PaloAlto.pm
|
||||
App::Netdisco::Util::Backend:
|
||||
file: lib/App/Netdisco/Util/Backend.pm
|
||||
App::Netdisco::Transport::SNMP:
|
||||
file: lib/App/Netdisco/Transport/SNMP.pm
|
||||
App::Netdisco::Util::DNS:
|
||||
file: lib/App/Netdisco/Util/DNS.pm
|
||||
App::Netdisco::Util::Device:
|
||||
@@ -238,6 +210,10 @@ provides:
|
||||
file: lib/App/Netdisco/Util/FastResolver.pm
|
||||
App::Netdisco::Util::Graph:
|
||||
file: lib/App/Netdisco/Util/Graph.pm
|
||||
App::Netdisco::Util::MCE:
|
||||
file: lib/App/Netdisco/Util/MCE.pm
|
||||
App::Netdisco::Util::Nbtstat:
|
||||
file: lib/App/Netdisco/Util/Nbtstat.pm
|
||||
App::Netdisco::Util::Node:
|
||||
file: lib/App/Netdisco/Util/Node.pm
|
||||
App::Netdisco::Util::NodeMonitor:
|
||||
@@ -380,6 +356,92 @@ provides:
|
||||
file: lib/App/Netdisco/Web/Statistics.pm
|
||||
App::Netdisco::Web::TypeAhead:
|
||||
file: lib/App/Netdisco/Web/TypeAhead.pm
|
||||
App::Netdisco::Worker:
|
||||
file: lib/App/Netdisco/Worker.pm
|
||||
App::Netdisco::Worker::Plugin:
|
||||
file: lib/App/Netdisco/Worker/Plugin.pm
|
||||
App::Netdisco::Worker::Plugin::Arpnip:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Arpnip.pm
|
||||
App::Netdisco::Worker::Plugin::Arpnip::Nodes:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Arpnip/Nodes.pm
|
||||
App::Netdisco::Worker::Plugin::Arpnip::Subnets:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Arpnip/Subnets.pm
|
||||
App::Netdisco::Worker::Plugin::Arpwalk:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Arpwalk.pm
|
||||
App::Netdisco::Worker::Plugin::Contact:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Contact.pm
|
||||
App::Netdisco::Worker::Plugin::Delete:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Delete.pm
|
||||
App::Netdisco::Worker::Plugin::Discover:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Discover.pm
|
||||
App::Netdisco::Worker::Plugin::Discover::CanonicalIP:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Discover/CanonicalIP.pm
|
||||
App::Netdisco::Worker::Plugin::Discover::Entities:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Discover/Entities.pm
|
||||
App::Netdisco::Worker::Plugin::Discover::Neighbors:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Discover/Neighbors.pm
|
||||
App::Netdisco::Worker::Plugin::Discover::PortPower:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Discover/PortPower.pm
|
||||
App::Netdisco::Worker::Plugin::Discover::Properties:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Discover/Properties.pm
|
||||
App::Netdisco::Worker::Plugin::Discover::VLANs:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Discover/VLANs.pm
|
||||
App::Netdisco::Worker::Plugin::Discover::Wireless:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Discover/Wireless.pm
|
||||
App::Netdisco::Worker::Plugin::Discover::WithNodes:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Discover/WithNodes.pm
|
||||
App::Netdisco::Worker::Plugin::DiscoverAll:
|
||||
file: lib/App/Netdisco/Worker/Plugin/DiscoverAll.pm
|
||||
App::Netdisco::Worker::Plugin::Expire:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Expire.pm
|
||||
App::Netdisco::Worker::Plugin::ExpireNodes:
|
||||
file: lib/App/Netdisco/Worker/Plugin/ExpireNodes.pm
|
||||
App::Netdisco::Worker::Plugin::Graph:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Graph.pm
|
||||
App::Netdisco::Worker::Plugin::Location:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Location.pm
|
||||
App::Netdisco::Worker::Plugin::Macsuck:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Macsuck.pm
|
||||
App::Netdisco::Worker::Plugin::Macsuck::Nodes:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Macsuck/Nodes.pm
|
||||
App::Netdisco::Worker::Plugin::Macsuck::WirelessNodes:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Macsuck/WirelessNodes.pm
|
||||
App::Netdisco::Worker::Plugin::Macwalk:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Macwalk.pm
|
||||
App::Netdisco::Worker::Plugin::Monitor:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Monitor.pm
|
||||
App::Netdisco::Worker::Plugin::Nbtstat:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Nbtstat.pm
|
||||
App::Netdisco::Worker::Plugin::Nbtstat::Core:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Nbtstat/Core.pm
|
||||
App::Netdisco::Worker::Plugin::Nbtwalk:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Nbtwalk.pm
|
||||
App::Netdisco::Worker::Plugin::PortControl:
|
||||
file: lib/App/Netdisco/Worker/Plugin/PortControl.pm
|
||||
App::Netdisco::Worker::Plugin::PortName:
|
||||
file: lib/App/Netdisco/Worker/Plugin/PortName.pm
|
||||
App::Netdisco::Worker::Plugin::Power:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Power.pm
|
||||
App::Netdisco::Worker::Plugin::Psql:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Psql.pm
|
||||
App::Netdisco::Worker::Plugin::Renumber:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Renumber.pm
|
||||
App::Netdisco::Worker::Plugin::Show:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Show.pm
|
||||
App::Netdisco::Worker::Plugin::Stats:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Stats.pm
|
||||
App::Netdisco::Worker::Plugin::Test:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Test.pm
|
||||
App::Netdisco::Worker::Plugin::Test::Core:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Test/Core.pm
|
||||
App::Netdisco::Worker::Plugin::Vlan:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Vlan.pm
|
||||
App::Netdisco::Worker::Plugin::Vlan::Native:
|
||||
file: lib/App/Netdisco/Worker/Plugin/Vlan/Core.pm
|
||||
App::Netdisco::Worker::Runner:
|
||||
file: lib/App/Netdisco/Worker/Runner.pm
|
||||
App::Netdisco::Worker::Status:
|
||||
file: lib/App/Netdisco/Worker/Status.pm
|
||||
Dancer::Template::NetdiscoTemplateToolkit:
|
||||
file: lib/Dancer/Template/NetdiscoTemplateToolkit.pm
|
||||
recommends:
|
||||
@@ -416,6 +478,7 @@ requires:
|
||||
List::MoreUtils: '0.33'
|
||||
MCE: '1.703'
|
||||
MIME::Base64: '3.13'
|
||||
Module::Find: '0.13'
|
||||
Module::Load: '0.32'
|
||||
Moo: '1.001000'
|
||||
Net::DNS: '0.72'
|
||||
@@ -434,6 +497,7 @@ requires:
|
||||
Role::Tiny: '1.002005'
|
||||
SNMP::Info: '3.37'
|
||||
SQL::Translator: '0.11018'
|
||||
Scope::Guard: '0'
|
||||
Sereal: '0'
|
||||
Socket6: '0.23'
|
||||
Starman: '0.4008'
|
||||
@@ -447,6 +511,7 @@ requires:
|
||||
URL::Encode: '0.01'
|
||||
YAML: '0.84'
|
||||
YAML::XS: '0.41'
|
||||
aliased: '0'
|
||||
namespace::clean: '0.24'
|
||||
version: '0.9902'
|
||||
resources:
|
||||
@@ -454,7 +519,7 @@ resources:
|
||||
MailingList: https://lists.sourceforge.net/lists/listinfo/netdisco-users
|
||||
bugtracker: https://github.com/netdisco/netdisco/issues
|
||||
homepage: http://netdisco.org/
|
||||
license: http://opensource.org/licenses/bsd-license.php
|
||||
license: http://opensource.org/licenses/BSD-3-Clause
|
||||
repository: https://github.com/netdisco/netdisco
|
||||
version: '2.036011'
|
||||
version: 2.036012_001
|
||||
x_serialization_backend: 'CPAN::Meta::YAML version 0.018'
|
||||
|
||||
@@ -29,26 +29,10 @@ BEGIN {
|
||||
setting('workers')->{'BACKEND'} ||= (hostfqdn || 'fqdn-undefined');
|
||||
}
|
||||
|
||||
use App::Netdisco::Util::Backend;
|
||||
use App::Netdisco::Util::MCE; # set $0 and parse maxworkers
|
||||
use NetAddr::IP::Lite ':lower'; # to quench AF_INET6 symbol errors
|
||||
use Role::Tiny::With;
|
||||
|
||||
# preload all worker modules into shared memory
|
||||
use App::Netdisco::Backend::Job ();
|
||||
use App::Netdisco::Backend::Util ();
|
||||
use App::Netdisco::Backend::Worker::Common ();
|
||||
use App::Netdisco::Backend::Worker::Interactive::DeviceActions ();
|
||||
use App::Netdisco::Backend::Worker::Interactive::PortActions ();
|
||||
use App::Netdisco::Backend::Worker::Manager ();
|
||||
use App::Netdisco::Backend::Worker::Poller::Arpnip ();
|
||||
use App::Netdisco::Backend::Worker::Poller::Common ();
|
||||
use App::Netdisco::Backend::Worker::Poller::Device ();
|
||||
use App::Netdisco::Backend::Worker::Poller::Expiry ();
|
||||
use App::Netdisco::Backend::Worker::Poller::Macsuck ();
|
||||
use App::Netdisco::Backend::Worker::Poller::Nbtstat ();
|
||||
use App::Netdisco::Backend::Worker::Poller ();
|
||||
use App::Netdisco::Backend::Worker::Scheduler ();
|
||||
|
||||
use MCE::Signal '-setpgrp';
|
||||
use MCE::Flow Sereal => 1;
|
||||
use MCE::Queue;
|
||||
@@ -90,7 +74,7 @@ sub _mk_wkr {
|
||||
|
||||
# post-fork, become manager, scheduler, poller, etc
|
||||
Role::Tiny->apply_roles_to_object(
|
||||
$self => "App::Netdisco::Backend::Worker::$role");
|
||||
$self => "App::Netdisco::Backend::Role::$role");
|
||||
|
||||
$self->worker_begin if $self->can('worker_begin');
|
||||
$self->worker_body;
|
||||
|
||||
164
bin/netdisco-do
164
bin/netdisco-do
@@ -35,17 +35,18 @@ BEGIN {
|
||||
|
||||
# for netdisco app config
|
||||
use App::Netdisco;
|
||||
use App::Netdisco::Backend::Job;
|
||||
use Dancer qw/:moose :script/;
|
||||
|
||||
info "App::Netdisco version $App::Netdisco::VERSION loaded.";
|
||||
|
||||
use NetAddr::IP qw/:rfc3021 :lower/;
|
||||
use App::Netdisco::Util::Device 'get_device';
|
||||
|
||||
use Try::Tiny;
|
||||
use Pod::Usage;
|
||||
use Scalar::Util 'blessed';
|
||||
use NetAddr::IP qw/:rfc3021 :lower/;
|
||||
|
||||
use App::Netdisco::Backend::Job;
|
||||
use App::Netdisco::Util::Device 'get_device';
|
||||
|
||||
use Getopt::Long;
|
||||
Getopt::Long::Configure ("bundling");
|
||||
|
||||
@@ -89,139 +90,14 @@ unless ($action) {
|
||||
);
|
||||
}
|
||||
|
||||
# create worker (placeholder object for the role methods)
|
||||
# create worker (placeholder object for the action runner)
|
||||
{
|
||||
package MyWorker;
|
||||
|
||||
use Moo;
|
||||
use Module::Load ();
|
||||
use Data::Printer ();
|
||||
use Scalar::Util 'blessed';
|
||||
use NetAddr::IP qw/:rfc3021 :lower/;
|
||||
use Dancer ':script';
|
||||
|
||||
use App::Netdisco::Util::SNMP ();
|
||||
use App::Netdisco::Util::Device
|
||||
qw/get_device delete_device renumber_device/;
|
||||
|
||||
with 'App::Netdisco::Backend::Worker::Poller::Device';
|
||||
with 'App::Netdisco::Backend::Worker::Poller::Arpnip';
|
||||
with 'App::Netdisco::Backend::Worker::Poller::Macsuck';
|
||||
with 'App::Netdisco::Backend::Worker::Poller::Nbtstat';
|
||||
with 'App::Netdisco::Backend::Worker::Poller::Expiry';
|
||||
with 'App::Netdisco::Backend::Worker::Interactive::DeviceActions';
|
||||
with 'App::Netdisco::Backend::Worker::Interactive::PortActions';
|
||||
|
||||
eval { Module::Load::load 'App::Netdisco::Util::Graph' };
|
||||
sub graph {
|
||||
App::Netdisco::Util::Graph::graph();
|
||||
return ('done', 'Generated graph data.');
|
||||
}
|
||||
|
||||
use App::Netdisco::Util::NodeMonitor ();
|
||||
sub monitor {
|
||||
App::Netdisco::Util::NodeMonitor::monitor();
|
||||
return ('done', 'Generated monitor data.');
|
||||
}
|
||||
|
||||
use App::Netdisco::Util::Statistics ();
|
||||
sub stats {
|
||||
App::Netdisco::Util::Statistics::update_stats();
|
||||
return ('done', 'Updated statistics.');
|
||||
}
|
||||
|
||||
sub show {
|
||||
my ($self, $job) = @_;
|
||||
my ($device, $port, $extra) = map {$job->$_} qw/device port extra/;
|
||||
return ('error', 'Missing device (-d).') if !defined $device;
|
||||
|
||||
$extra ||= 'interfaces'; my $class = undef;
|
||||
($class, $extra) = split(/::([^:]+)$/, $extra);
|
||||
if ($class and $extra) {
|
||||
$class = 'SNMP::Info::'.$class;
|
||||
}
|
||||
else {
|
||||
$extra = $class;
|
||||
undef $class;
|
||||
}
|
||||
my $i = App::Netdisco::Util::SNMP::snmp_connect($device, $class);
|
||||
Data::Printer::p($i->$extra);
|
||||
return ('done', sprintf "Showed %s response from %s.", $extra, $device->ip);
|
||||
}
|
||||
|
||||
sub delete {
|
||||
my ($self, $job) = @_;
|
||||
my ($device, $port, $extra) = map {$job->$_} qw/device port extra/;
|
||||
return ('error', 'Missing device (-d).') if !defined $device;
|
||||
|
||||
$port = ($port ? 1 : 0);
|
||||
delete_device($device, $port, $extra);
|
||||
return ('done', sprintf "Deleted device %s.", $device->ip);
|
||||
}
|
||||
|
||||
sub renumber {
|
||||
my ($self, $job) = @_;
|
||||
my ($device, $port, $extra) = map {$job->$_} qw/device port extra/;
|
||||
return ('error', 'Missing device (-d).') if !defined $device;
|
||||
my $old_ip = $device->ip;
|
||||
|
||||
my $new_ip = NetAddr::IP->new($extra);
|
||||
unless ($new_ip and $new_ip->addr ne '0.0.0.0') {
|
||||
return ('error', "Bad host or IP: ".($extra || '0.0.0.0'));
|
||||
}
|
||||
|
||||
my $new_dev = get_device($new_ip->addr);
|
||||
if ($new_dev and $new_dev->in_storage and ($new_dev->ip ne $device->ip)) {
|
||||
return ('error', sprintf "Already know new device as: %s.", $new_dev->ip);
|
||||
}
|
||||
|
||||
renumber_device($device, $new_ip);
|
||||
return ('done', sprintf 'Renumbered device %s to %s (%s).',
|
||||
$device->ip, $new_ip, ($device->dns || ''));
|
||||
}
|
||||
|
||||
sub psql {
|
||||
my ($self, $job) = @_;
|
||||
my ($device, $port, $extra) = map {$job->$_} qw/device port extra/;
|
||||
|
||||
my $name = ($ENV{NETDISCO_DBNAME} || setting('database')->{name} || 'netdisco');
|
||||
my $host = setting('database')->{host};
|
||||
my $user = setting('database')->{user};
|
||||
my $pass = setting('database')->{pass};
|
||||
|
||||
my $portnum = undef;
|
||||
if ($host and $host =~ m/([^;]+);port=(\d+)/) {
|
||||
$host = $1;
|
||||
$portnum = $2;
|
||||
}
|
||||
|
||||
$ENV{PGHOST} = $host if $host;
|
||||
$ENV{PGPORT} = $portnum if defined $portnum;
|
||||
$ENV{PGDATABASE} = $name;
|
||||
$ENV{PGUSER} = $user;
|
||||
$ENV{PGPASSWORD} = $pass;
|
||||
$ENV{PGCLIENTENCODING} = 'UTF8';
|
||||
|
||||
if ($extra) {
|
||||
system('psql', '-c', $extra);
|
||||
}
|
||||
else {
|
||||
system('psql');
|
||||
}
|
||||
return ('done', "psql session closed.");
|
||||
}
|
||||
with 'App::Netdisco::Worker::Runner';
|
||||
}
|
||||
my $worker = MyWorker->new();
|
||||
|
||||
# belt and braces check before we go ahead
|
||||
if (not $worker->can( $action )) {
|
||||
pod2usage(
|
||||
-msg => (sprintf 'error: %s is not a valid action', $action),
|
||||
-verbose => 2,
|
||||
-exitval => 3,
|
||||
);
|
||||
}
|
||||
|
||||
my $net = NetAddr::IP->new($device);
|
||||
if ($device and (!$net or $net->num == 0 or $net->addr eq '0.0.0.0')) {
|
||||
info sprintf '%s: error - Bad host, IP or prefix: %s', $action, $device;
|
||||
@@ -249,25 +125,31 @@ foreach my $host (@hostlist) {
|
||||
|
||||
my $actiontext = (
|
||||
($job->device ? ('['.$job->device->ip.']') : '') .
|
||||
($job->action eq 'show' ? ('/'.$job->subaction) : '')
|
||||
($job->action eq 'show' ? ('/'. ($job->subaction || 'interfaces')) : '')
|
||||
);
|
||||
|
||||
# do job
|
||||
my ($status, $log);
|
||||
try {
|
||||
|
||||
info sprintf '%s: %s started at %s',
|
||||
$action, $actiontext, scalar localtime;
|
||||
($status, $log) = $worker->$action($job);
|
||||
$worker->run($job);
|
||||
}
|
||||
catch {
|
||||
$status = 'error';
|
||||
$log = "error running job: $_";
|
||||
$job->status('error');
|
||||
$job->log("error running job: $_");
|
||||
};
|
||||
|
||||
if ($job->log eq 'no worker succeeded during main phase') {
|
||||
pod2usage(
|
||||
-msg => (sprintf 'error: %s is not a valid action', $action),
|
||||
-verbose => 2,
|
||||
-exitval => 3,
|
||||
);
|
||||
}
|
||||
|
||||
info sprintf '%s: finished at %s', $action, scalar localtime;
|
||||
info sprintf '%s: status %s: %s', $action, $status, $log;
|
||||
$exitstatus = 1 if !defined $status or $status eq 'error';
|
||||
info sprintf '%s: status %s: %s', $action, $job->status, $job->log;
|
||||
$exitstatus = 1 if !$exitstatus and $job->status ne 'done';
|
||||
}
|
||||
|
||||
exit $exitstatus;
|
||||
@@ -394,6 +276,10 @@ leaf with the class short name, for example "C<Layer3::C3550::interfaces>" or
|
||||
~netdisco/bin/netdisco-do show -d 192.0.2.1 -e interfaces
|
||||
~netdisco/bin/netdisco-do show -d 192.0.2.1 -e Layer2::HP::interfaces
|
||||
|
||||
A paramter may be passed to the C<SNMP::Info> method in the C<-p> parameter:
|
||||
|
||||
~netdisco/bin/netdisco-do show -d 192.0.2.1 -e has_layer -p 3
|
||||
|
||||
=head2 psql
|
||||
|
||||
Start an interactive terminal with the Netdisco PostgreSQL database. If you
|
||||
|
||||
@@ -4,7 +4,7 @@ use strict;
|
||||
use warnings;
|
||||
use 5.010_000;
|
||||
|
||||
our $VERSION = '2.036011';
|
||||
our $VERSION = '2.036012_001';
|
||||
use App::Netdisco::Configuration;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package App::Netdisco::Backend::Job;
|
||||
|
||||
use Dancer qw/:moose :syntax !error/;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use Moo;
|
||||
use namespace::clean;
|
||||
|
||||
@@ -16,7 +19,10 @@ foreach my $slot (qw/
|
||||
username
|
||||
userip
|
||||
log
|
||||
debug
|
||||
|
||||
_current_phase
|
||||
_last_namespace
|
||||
_last_priority
|
||||
/) {
|
||||
|
||||
has $slot => (
|
||||
@@ -24,25 +30,147 @@ foreach my $slot (qw/
|
||||
);
|
||||
}
|
||||
|
||||
has '_statuslist' => (
|
||||
is => 'rw',
|
||||
default => sub { [] },
|
||||
);
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 summary
|
||||
|
||||
An attempt to make a meaningful statement about the job.
|
||||
An attempt to make a meaningful written statement about the job.
|
||||
|
||||
=cut
|
||||
|
||||
sub summary {
|
||||
my $job = shift;
|
||||
return join ' ',
|
||||
$job->action,
|
||||
($job->device || ''),
|
||||
($job->port || '');
|
||||
# ($job->subaction ? (q{'}. $job->subaction .q{'}) : '');
|
||||
my $job = shift;
|
||||
return join ' ',
|
||||
$job->action,
|
||||
($job->device || ''),
|
||||
($job->port || '');
|
||||
}
|
||||
|
||||
=head2 finalise_status
|
||||
|
||||
Find the best status and log it into the job's C<status> and C<log> slots.
|
||||
|
||||
The process is to track back from the last worker and find the best status,
|
||||
which is C<done> in early or main phases, or else any status in any non-user
|
||||
phase.
|
||||
|
||||
=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 = Status->error()->level;
|
||||
|
||||
foreach my $status (reverse @{ $job->_statuslist }) {
|
||||
next if $status->phase
|
||||
and $status->phase !~ m/^(?:check|early|main)$/;
|
||||
|
||||
next if $status->phase eq 'check'
|
||||
and $status->level eq Status->done()->level;
|
||||
|
||||
if ($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 }) {
|
||||
next unless $status->phase and $status->phase eq 'check';
|
||||
return true if $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 and $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->level ne Status->done()->level));
|
||||
}
|
||||
|
||||
=head1 ADDITIONAL COLUMNS
|
||||
|
||||
=head2 id
|
||||
|
||||
Alias for the C<job> column.
|
||||
|
||||
=cut
|
||||
|
||||
sub id { (shift)->job }
|
||||
|
||||
=head2 extra
|
||||
|
||||
Alias for the C<subaction> column.
|
||||
@@ -51,4 +179,4 @@ Alias for the C<subaction> column.
|
||||
|
||||
sub extra { (shift)->subaction }
|
||||
|
||||
1;
|
||||
true;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package App::Netdisco::Backend::Worker::Manager;
|
||||
package App::Netdisco::Backend::Role::Manager;
|
||||
|
||||
use Dancer qw/:moose :syntax :script/;
|
||||
|
||||
use List::Util 'sum';
|
||||
use App::Netdisco::Util::Backend;
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
use App::Netdisco::Util::MCE;
|
||||
|
||||
use App::Netdisco::JobQueue
|
||||
qw/jq_locked jq_getsome jq_getsomep jq_lock jq_warm_thrusters/;
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
sub worker_begin {
|
||||
my $self = shift;
|
||||
my $wid = $self->wid;
|
||||
@@ -58,7 +58,7 @@ sub worker_body {
|
||||
# mark job as running
|
||||
next unless jq_lock($job);
|
||||
info sprintf "mgr (%s): job %s booked out for this processing node",
|
||||
$wid, $job->job;
|
||||
$wid, $job->id;
|
||||
|
||||
# copy job to local queue
|
||||
$self->{queue}->enqueuep(100, $job);
|
||||
@@ -75,7 +75,7 @@ sub worker_body {
|
||||
# mark job as running
|
||||
next unless jq_lock($job);
|
||||
info sprintf "mgr (%s): job %s booked out for this processing node",
|
||||
$wid, $job->job;
|
||||
$wid, $job->id;
|
||||
|
||||
# copy job to local queue
|
||||
$self->{queue}->enqueue($job);
|
||||
@@ -1,15 +1,18 @@
|
||||
package App::Netdisco::Backend::Worker::Common;
|
||||
package App::Netdisco::Backend::Role::Poller;
|
||||
|
||||
use Dancer qw/:moose :syntax :script/;
|
||||
|
||||
use Try::Tiny;
|
||||
use App::Netdisco::Util::Backend;
|
||||
use App::Netdisco::Util::MCE;
|
||||
|
||||
use Time::HiRes 'sleep';
|
||||
use App::Netdisco::JobQueue qw/jq_defer jq_complete/;
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
use Time::HiRes 'sleep';
|
||||
use App::Netdisco::JobQueue qw/jq_defer jq_complete/;
|
||||
# add dispatch methods for poller tasks
|
||||
with 'App::Netdisco::Worker::Runner';
|
||||
|
||||
sub worker_begin { (shift)->{started} = time }
|
||||
|
||||
@@ -22,17 +25,14 @@ sub worker_body {
|
||||
|
||||
my $job = $self->{queue}->dequeue(1);
|
||||
next unless defined $job;
|
||||
my $action = $job->action;
|
||||
|
||||
try {
|
||||
$job->started(scalar localtime);
|
||||
prctl sprintf 'nd2: #%s poll: #%s: %s',
|
||||
$wid, $job->job, $job->summary;
|
||||
$wid, $job->id, $job->summary;
|
||||
info sprintf "pol (%s): starting %s job(%s) at %s",
|
||||
$wid, $action, $job->job, $job->started;
|
||||
my ($status, $log) = $self->$action($job);
|
||||
$job->status($status);
|
||||
$job->log($log);
|
||||
$wid, $job->action, $job->id, $job->started;
|
||||
$self->run($job);
|
||||
}
|
||||
catch {
|
||||
$job->status('error');
|
||||
@@ -51,7 +51,7 @@ sub close_job {
|
||||
my $now = scalar localtime;
|
||||
|
||||
info sprintf "pol (%s): wrapping up %s job(%s) - status %s at %s",
|
||||
$self->wid, $job->action, $job->job, $job->status, $now;
|
||||
$self->wid, $job->action, $job->id, $job->status, $now;
|
||||
|
||||
try {
|
||||
if ($job->status eq 'defer') {
|
||||
@@ -1,15 +1,15 @@
|
||||
package App::Netdisco::Backend::Worker::Scheduler;
|
||||
package App::Netdisco::Backend::Role::Scheduler;
|
||||
|
||||
use Dancer qw/:moose :syntax :script/;
|
||||
|
||||
use Algorithm::Cron;
|
||||
use App::Netdisco::Util::Backend;
|
||||
use App::Netdisco::Util::MCE;
|
||||
|
||||
use App::Netdisco::JobQueue qw/jq_insert/;
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
use App::Netdisco::JobQueue qw/jq_insert/;
|
||||
|
||||
sub worker_begin {
|
||||
my $self = shift;
|
||||
my $wid = $self->wid;
|
||||
@@ -1,17 +0,0 @@
|
||||
package App::Netdisco::Backend::Util;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
# support utilities for Backend Actions
|
||||
|
||||
use base 'Exporter';
|
||||
our @EXPORT = ();
|
||||
our @EXPORT_OK = qw/ job_done job_error job_defer /;
|
||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||
|
||||
sub job_done { return ('done', shift) }
|
||||
sub job_error { return ('error', shift) }
|
||||
sub job_defer { return ('defer', shift) }
|
||||
|
||||
1;
|
||||
@@ -1,50 +0,0 @@
|
||||
package App::Netdisco::Backend::Worker::Interactive::DeviceActions;
|
||||
|
||||
use App::Netdisco::Util::SNMP 'snmp_connect_rw';
|
||||
use App::Netdisco::Util::Device 'get_device';
|
||||
use App::Netdisco::Backend::Util ':all';
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
sub location {
|
||||
my ($self, $job) = @_;
|
||||
return _set_device_generic($job->device, 'location', $job->subaction);
|
||||
}
|
||||
|
||||
sub 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_rw($ip)
|
||||
or return job_defer("Failed to connect to device [$ip] to update $slot");
|
||||
|
||||
my $method = 'set_'. $slot;
|
||||
my $rv = $info->$method($data);
|
||||
|
||||
if (!defined $rv) {
|
||||
return job_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 job_error("Verify of $slot update failed on [$ip]: $new_data");
|
||||
}
|
||||
|
||||
# update netdisco DB
|
||||
my $device = get_device($ip);
|
||||
$device->update({$slot => $data});
|
||||
|
||||
return job_done("Updated $slot on [$ip] to [$data]");
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,159 +0,0 @@
|
||||
package App::Netdisco::Backend::Worker::Interactive::PortActions;
|
||||
|
||||
use App::Netdisco::Util::Port ':all';
|
||||
use App::Netdisco::Util::SNMP 'snmp_connect_rw';
|
||||
use App::Netdisco::Util::Device 'get_device';
|
||||
use App::Netdisco::Backend::Util ':all';
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
sub portname {
|
||||
my ($self, $job) = @_;
|
||||
return _set_port_generic($job, 'alias', 'name');
|
||||
}
|
||||
|
||||
sub portcontrol {
|
||||
my ($self, $job) = @_;
|
||||
|
||||
my $port = get_port($job->device, $job->port)
|
||||
or return job_error(sprintf "Unknown port name [%s] on device [%s]",
|
||||
$job->port, $job->device);
|
||||
|
||||
my $reconfig_check = port_reconfig_check($port);
|
||||
return job_error("Cannot alter port: $reconfig_check")
|
||||
if $reconfig_check;
|
||||
|
||||
# need to remove "-other" which appears for power/portcontrol
|
||||
(my $sa = $job->subaction) =~ s/-\w+//;
|
||||
$job->subaction($sa);
|
||||
|
||||
if ($sa eq 'bounce') {
|
||||
$job->subaction('down');
|
||||
my @stat = _set_port_generic($job, 'up_admin');
|
||||
return @stat if $stat[0] ne 'done';
|
||||
$job->subaction('up');
|
||||
return _set_port_generic($job, 'up_admin');
|
||||
}
|
||||
else {
|
||||
return _set_port_generic($job, 'up_admin');
|
||||
}
|
||||
}
|
||||
|
||||
sub vlan {
|
||||
my ($self, $job) = @_;
|
||||
|
||||
my $port = get_port($job->device, $job->port)
|
||||
or return job_error(sprintf "Unknown port name [%s] on device [%s]",
|
||||
$job->port, $job->device);
|
||||
|
||||
my $port_reconfig_check = port_reconfig_check($port);
|
||||
return job_error("Cannot alter port: $port_reconfig_check")
|
||||
if $port_reconfig_check;
|
||||
|
||||
my $vlan_reconfig_check = vlan_reconfig_check($port);
|
||||
return job_error("Cannot alter vlan: $vlan_reconfig_check")
|
||||
if $vlan_reconfig_check;
|
||||
|
||||
my @stat = _set_port_generic($job, 'pvid'); # for Cisco trunk
|
||||
return @stat if $stat[0] eq 'done';
|
||||
return _set_port_generic($job, 'vlan');
|
||||
}
|
||||
|
||||
sub _set_port_generic {
|
||||
my ($job, $slot, $column) = @_;
|
||||
$column ||= $slot;
|
||||
|
||||
my $device = get_device($job->device);
|
||||
my $ip = $device->ip;
|
||||
my $pn = $job->port;
|
||||
my $data = $job->subaction;
|
||||
|
||||
my $port = get_port($ip, $pn)
|
||||
or return job_error("Unknown port name [$pn] on device [$ip]");
|
||||
|
||||
if ($device->vendor ne 'netdisco') {
|
||||
# snmp connect using rw community
|
||||
my $info = snmp_connect_rw($ip)
|
||||
or return job_defer("Failed to connect to device [$ip] to control port");
|
||||
|
||||
my $iid = get_iid($info, $port)
|
||||
or return job_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 job_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 job_error("Verify of [$pn] $slot failed on [$ip]");
|
||||
}
|
||||
}
|
||||
|
||||
# update netdisco DB
|
||||
$port->update({$column => $data});
|
||||
|
||||
return job_done("Updated [$pn] $slot status on [$ip] to [$data]");
|
||||
}
|
||||
|
||||
sub power {
|
||||
my ($self, $job) = @_;
|
||||
|
||||
my $port = get_port($job->device, $job->port)
|
||||
or return job_error(sprintf "Unknown port name [%s] on device [%s]",
|
||||
$job->port, $job->device);
|
||||
|
||||
return job_error("No PoE service on port [%s] on device [%s]")
|
||||
unless $port->power;
|
||||
|
||||
my $reconfig_check = port_reconfig_check($port);
|
||||
return job_error("Cannot alter port: $reconfig_check")
|
||||
if $reconfig_check;
|
||||
|
||||
my $device = get_device($job->device);
|
||||
my $ip = $device->ip;
|
||||
my $pn = $job->port;
|
||||
|
||||
# munge data
|
||||
(my $data = $job->subaction) =~ s/-\w+//; # remove -other
|
||||
$data = 'true' if $data =~ m/^(on|yes|up)$/;
|
||||
$data = 'false' if $data =~ m/^(off|no|down)$/;
|
||||
|
||||
# snmp connect using rw community
|
||||
my $info = snmp_connect_rw($ip)
|
||||
or return job_defer("Failed to connect to device [$ip] to control power");
|
||||
|
||||
my $powerid = get_powerid($info, $port)
|
||||
or return job_error("Failed to get power ID for [$pn] from [$ip]");
|
||||
|
||||
my $rv = $info->set_peth_port_admin($data, $powerid);
|
||||
|
||||
if (!defined $rv) {
|
||||
return job_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 job_error("Verify of [$pn] power failed on [$ip]");
|
||||
}
|
||||
|
||||
# update netdisco DB
|
||||
$port->power->update({
|
||||
admin => $data,
|
||||
status => ($data eq 'false' ? 'disabled' : 'searching'),
|
||||
});
|
||||
|
||||
return job_done("Updated [$pn] power status on [$ip] to [$data]");
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,18 +0,0 @@
|
||||
package App::Netdisco::Backend::Worker::Poller;
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
# main worker body
|
||||
with 'App::Netdisco::Backend::Worker::Common';
|
||||
|
||||
# add dispatch methods for poller tasks
|
||||
with 'App::Netdisco::Backend::Worker::Poller::Device',
|
||||
'App::Netdisco::Backend::Worker::Poller::Arpnip',
|
||||
'App::Netdisco::Backend::Worker::Poller::Macsuck',
|
||||
'App::Netdisco::Backend::Worker::Poller::Nbtstat',
|
||||
'App::Netdisco::Backend::Worker::Poller::Expiry',
|
||||
'App::Netdisco::Backend::Worker::Interactive::DeviceActions',
|
||||
'App::Netdisco::Backend::Worker::Interactive::PortActions';
|
||||
|
||||
1;
|
||||
@@ -1,18 +0,0 @@
|
||||
package App::Netdisco::Backend::Worker::Poller::Arpnip;
|
||||
|
||||
use App::Netdisco::Core::Arpnip 'do_arpnip';
|
||||
use App::Netdisco::Util::Device 'is_arpnipable_now';
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
with 'App::Netdisco::Backend::Worker::Poller::Common';
|
||||
|
||||
sub arpnip_action { \&do_arpnip }
|
||||
sub arpnip_filter { \&is_arpnipable_now }
|
||||
sub arpnip_layer { 3 }
|
||||
|
||||
sub arpwalk { (shift)->_walk_body('arpnip', @_) }
|
||||
sub arpnip { (shift)->_single_body('arpnip', @_) }
|
||||
|
||||
1;
|
||||
@@ -1,99 +0,0 @@
|
||||
package App::Netdisco::Backend::Worker::Poller::Common;
|
||||
|
||||
use Dancer qw/:moose :syntax :script/;
|
||||
|
||||
use App::Netdisco::Util::SNMP 'snmp_connect';
|
||||
use App::Netdisco::Util::Device 'get_device';
|
||||
use App::Netdisco::Backend::Util ':all';
|
||||
use App::Netdisco::JobQueue qw/jq_queued jq_insert/;
|
||||
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
# queue a job for all devices known to Netdisco
|
||||
sub _walk_body {
|
||||
my ($self, $job_type, $job) = @_;
|
||||
|
||||
my $layer_method = $job_type .'_layer';
|
||||
my $job_layer = $self->$layer_method;
|
||||
|
||||
my %queued = map {$_ => 1} jq_queued($job_type);
|
||||
my @devices = schema('netdisco')->resultset('Device')->search({
|
||||
-or => [ 'vendor' => undef, 'vendor' => { '!=' => 'netdisco' }],
|
||||
})->has_layer($job_layer)->get_column('ip')->all;
|
||||
my @filtered_devices = grep {!exists $queued{$_}} @devices;
|
||||
|
||||
jq_insert([
|
||||
map {{
|
||||
device => $_,
|
||||
action => $job_type,
|
||||
username => $job->username,
|
||||
userip => $job->userip,
|
||||
}} (@filtered_devices)
|
||||
]);
|
||||
|
||||
return job_done("Queued $job_type job for all devices");
|
||||
}
|
||||
|
||||
sub _single_body {
|
||||
my ($self, $job_type, $job) = @_;
|
||||
|
||||
my $action_method = $job_type .'_action';
|
||||
my $job_action = $self->$action_method;
|
||||
|
||||
my $layer_method = $job_type .'_layer';
|
||||
my $job_layer = $self->$layer_method;
|
||||
|
||||
my $device = get_device($job->device)
|
||||
or job_error("$job_type failed: unable to interpret device parameter");
|
||||
my $host = $device->ip;
|
||||
|
||||
if ($device->in_storage
|
||||
and $device->vendor and $device->vendor eq 'netdisco') {
|
||||
return job_done("$job_type skipped: $host is pseudo-device");
|
||||
}
|
||||
|
||||
my $filter_method = $job_type .'_filter';
|
||||
my $job_filter = $self->$filter_method;
|
||||
|
||||
unless ($job_filter->($device->ip)) {
|
||||
return job_defer("$job_type deferred: $host is not ${job_type}able");
|
||||
}
|
||||
|
||||
my $snmp = snmp_connect($device);
|
||||
if (!defined $snmp) {
|
||||
return job_defer("$job_type failed: could not SNMP connect to $host");
|
||||
}
|
||||
|
||||
unless ($snmp->has_layer( $job_layer )) {
|
||||
return job_done("Skipped $job_type for device $host without OSI layer $job_layer capability");
|
||||
}
|
||||
|
||||
$job_action->($device, $snmp);
|
||||
|
||||
return job_done("Ended $job_type for $host");
|
||||
}
|
||||
|
||||
sub _single_node_body {
|
||||
my ($self, $job_type, $node, $now) = @_;
|
||||
|
||||
my $action_method = $job_type .'_action';
|
||||
my $job_action = $self->$action_method;
|
||||
|
||||
my $filter_method = $job_type .'_filter';
|
||||
my $job_filter = $self->$filter_method;
|
||||
|
||||
unless ($job_filter->($node)) {
|
||||
return job_defer("$job_type deferred: $node is not ${job_type}able");
|
||||
}
|
||||
|
||||
$job_action->($node, $now);
|
||||
|
||||
# would be ignored if wrapped in a loop
|
||||
return job_done("Ended $job_type for $node");
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,100 +0,0 @@
|
||||
package App::Netdisco::Backend::Worker::Poller::Device;
|
||||
|
||||
use Dancer qw/:moose :syntax :script/;
|
||||
|
||||
use App::Netdisco::Util::SNMP 'snmp_connect';
|
||||
use App::Netdisco::Util::Device qw/get_device is_discoverable_now/;
|
||||
use App::Netdisco::Core::Discover ':all';
|
||||
use App::Netdisco::Backend::Util ':all';
|
||||
use App::Netdisco::JobQueue qw/jq_queued jq_insert/;
|
||||
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
# queue a discover job for all devices known to Netdisco
|
||||
sub discoverall {
|
||||
my ($self, $job) = @_;
|
||||
|
||||
my %queued = map {$_ => 1} jq_queued('discover');
|
||||
my @devices = schema('netdisco')->resultset('Device')->search({
|
||||
-or => [ 'vendor' => undef, 'vendor' => { '!=' => 'netdisco' }],
|
||||
})->get_column('ip')->all;
|
||||
my @filtered_devices = grep {!exists $queued{$_}} @devices;
|
||||
|
||||
jq_insert([
|
||||
map {{
|
||||
device => $_,
|
||||
action => 'discover',
|
||||
username => $job->username,
|
||||
userip => $job->userip,
|
||||
}} (@filtered_devices)
|
||||
]);
|
||||
|
||||
return job_done("Queued discover job for all devices");
|
||||
}
|
||||
|
||||
# run a discover job for one device, and its *new* neighbors
|
||||
sub discover {
|
||||
my ($self, $job) = @_;
|
||||
|
||||
my $device = get_device($job->device)
|
||||
or return job_error(
|
||||
"discover failed: unable to interpret device parameter: "
|
||||
. ($job->device || "''"));
|
||||
my $host = $device->ip;
|
||||
|
||||
if ($device->ip eq '0.0.0.0') {
|
||||
return job_error("discover failed: no device param (need -d ?)");
|
||||
}
|
||||
|
||||
if ($device->in_storage
|
||||
and $device->vendor and $device->vendor eq 'netdisco') {
|
||||
return job_done("discover skipped: $host is pseudo-device");
|
||||
}
|
||||
|
||||
unless (is_discoverable_now($device)) {
|
||||
return job_defer("discover deferred: $host is not discoverable");
|
||||
}
|
||||
|
||||
my $snmp = snmp_connect($device);
|
||||
if (!defined $snmp) {
|
||||
return job_defer("discover failed: could not SNMP connect to $host");
|
||||
}
|
||||
|
||||
store_device($device, $snmp);
|
||||
set_canonical_ip($device, $snmp); # must come after store_device
|
||||
store_interfaces($device, $snmp);
|
||||
store_wireless($device, $snmp);
|
||||
store_vlans($device, $snmp);
|
||||
store_power($device, $snmp);
|
||||
store_modules($device, $snmp) if setting('store_modules');
|
||||
discover_new_neighbors($device, $snmp);
|
||||
|
||||
# if requested, and the device has not yet been arpniped/macsucked, queue now
|
||||
if ($device->in_storage and $job->subaction and $job->subaction eq 'with-nodes') {
|
||||
if (!defined $device->last_macsuck) {
|
||||
jq_insert({
|
||||
device => $device->ip,
|
||||
action => 'macsuck',
|
||||
username => $job->username,
|
||||
userip => $job->userip,
|
||||
});
|
||||
}
|
||||
|
||||
if (!defined $device->last_arpnip) {
|
||||
jq_insert({
|
||||
device => $device->ip,
|
||||
action => 'arpnip',
|
||||
username => $job->username,
|
||||
userip => $job->userip,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return job_done("Ended discover for $host");
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,18 +0,0 @@
|
||||
package App::Netdisco::Backend::Worker::Poller::Macsuck;
|
||||
|
||||
use App::Netdisco::Core::Macsuck 'do_macsuck';
|
||||
use App::Netdisco::Util::Device 'is_macsuckable_now';
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
with 'App::Netdisco::Backend::Worker::Poller::Common';
|
||||
|
||||
sub macsuck_action { \&do_macsuck }
|
||||
sub macsuck_filter { \&is_macsuckable_now }
|
||||
sub macsuck_layer { 2 }
|
||||
|
||||
sub macwalk { (shift)->_walk_body('macsuck', @_) }
|
||||
sub macsuck { (shift)->_single_body('macsuck', @_) }
|
||||
|
||||
1;
|
||||
@@ -1,73 +0,0 @@
|
||||
package App::Netdisco::Backend::Worker::Poller::Nbtstat;
|
||||
|
||||
use Dancer qw/:moose :syntax :script/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
use App::Netdisco::Core::Nbtstat qw/nbtstat_resolve_async store_nbt/;
|
||||
use App::Netdisco::Util::Node 'is_nbtstatable';
|
||||
use App::Netdisco::Util::Device qw/get_device is_macsuckable/;
|
||||
use App::Netdisco::Backend::Util ':all';
|
||||
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
use Time::HiRes 'gettimeofday';
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
with 'App::Netdisco::Backend::Worker::Poller::Common';
|
||||
|
||||
sub nbtstat_action { \&do_nbtstat }
|
||||
sub nbtstat_filter { \&is_nbtstatable }
|
||||
sub nbtstat_layer { 2 }
|
||||
|
||||
sub nbtwalk { (shift)->_walk_body('nbtstat', @_) }
|
||||
|
||||
sub nbtstat {
|
||||
my ($self, $job) = @_;
|
||||
|
||||
my $device = get_device($job->device)
|
||||
or job_error("nbtstat failed: unable to interpret device parameter");
|
||||
my $host = $device->ip;
|
||||
|
||||
unless (is_macsuckable($device)) {
|
||||
return job_defer("nbtstat deferred: $host is not macsuckable");
|
||||
}
|
||||
|
||||
# get list of nodes on device
|
||||
my $interval = (setting('nbtstat_max_age') || 7) . ' day';
|
||||
my $rs = schema('netdisco')->resultset('NodeIp')->search({
|
||||
-bool => 'me.active',
|
||||
-bool => 'nodes.active',
|
||||
'nodes.switch' => $device->ip,
|
||||
'me.time_last' => \[ '>= now() - ?::interval', $interval ],
|
||||
},{
|
||||
join => 'nodes',
|
||||
columns => 'ip',
|
||||
distinct => 1,
|
||||
})->ip_version(4);
|
||||
|
||||
my @nodes = $rs->get_column('ip')->all;
|
||||
|
||||
# Unless we have IP's don't bother
|
||||
if (scalar @nodes) {
|
||||
# filter exclusions from config
|
||||
@nodes = grep { is_nbtstatable( $_ ) } @nodes;
|
||||
|
||||
# setup the hash nbtstat_resolve_async expects
|
||||
my @ips = map {+{'ip' => $_}} @nodes;
|
||||
my $now = 'to_timestamp('. (join '.', gettimeofday) .')';
|
||||
|
||||
my $resolved_nodes = nbtstat_resolve_async(\@ips);
|
||||
|
||||
# update node_nbt with status entries
|
||||
foreach my $result (@$resolved_nodes) {
|
||||
if (defined $result->{'nbname'}) {
|
||||
store_nbt($result, $now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return job_done("Ended nbtstat for $host");
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -1,6 +1,7 @@
|
||||
package App::Netdisco::Configuration;
|
||||
|
||||
use App::Netdisco::Environment;
|
||||
use App::Netdisco::Util::SNMP ();
|
||||
use Dancer ':script';
|
||||
|
||||
use Path::Class 'dir';
|
||||
@@ -59,6 +60,9 @@ if ((setting('snmp_auth') and 0 == scalar @{ setting('snmp_auth') })
|
||||
config->{'community_rw'} = [ @{setting('community_rw')}, 'private' ];
|
||||
}
|
||||
|
||||
# fix up device_auth (or create it from old snmp_auth and community settings)
|
||||
config->{'device_auth'} = [ App::Netdisco::Util::SNMP::fixup_device_auth() ];
|
||||
|
||||
# defaults for workers
|
||||
setting('workers')->{queue} ||= 'PostgreSQL';
|
||||
if (exists setting('workers')->{interactives}
|
||||
|
||||
@@ -1,996 +0,0 @@
|
||||
package App::Netdisco::Core::Discover;
|
||||
|
||||
use Dancer qw/:syntax :script/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
use App::Netdisco::Util::Device
|
||||
qw/get_device match_devicetype is_discoverable/;
|
||||
use App::Netdisco::Util::Permission qw/check_acl_only check_acl_no/;
|
||||
use App::Netdisco::Util::FastResolver 'hostnames_resolve_async';
|
||||
use App::Netdisco::Util::DNS ':all';
|
||||
use App::Netdisco::JobQueue qw/jq_queued jq_insert/;
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
use List::MoreUtils ();
|
||||
use Scalar::Util 'blessed';
|
||||
use Encode;
|
||||
use Try::Tiny;
|
||||
use NetAddr::MAC;
|
||||
|
||||
use base 'Exporter';
|
||||
our @EXPORT = ();
|
||||
our @EXPORT_OK = qw/
|
||||
set_canonical_ip
|
||||
store_device store_interfaces store_wireless
|
||||
store_vlans store_power store_modules
|
||||
store_neighbors discover_new_neighbors
|
||||
/;
|
||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||
|
||||
=head1 NAME
|
||||
|
||||
App::Netdisco::Core::Discover
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A set of helper subroutines to support parts of the Netdisco application.
|
||||
|
||||
There are no default exports, however the C<:all> tag will export all
|
||||
subroutines.
|
||||
|
||||
=head1 EXPORT_OK
|
||||
|
||||
=head2 set_canonical_ip( $device, $snmp )
|
||||
|
||||
Returns: C<$device>
|
||||
|
||||
Given a Device database object, and a working SNMP connection, check whether
|
||||
the database object's IP is the best choice for that device. If not, update
|
||||
the IP and hostname in the device object for the canonical IP.
|
||||
|
||||
=cut
|
||||
|
||||
sub set_canonical_ip {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
my $old_ip = $device->ip;
|
||||
my $new_ip = $old_ip;
|
||||
my $revofname = ipv4_from_hostname($snmp->name);
|
||||
|
||||
if (setting('reverse_sysname') and $revofname) {
|
||||
if ($snmp->snmp_connect_ip( $new_ip )) {
|
||||
$new_ip = $revofname;
|
||||
}
|
||||
else {
|
||||
debug sprintf ' [%s] device - cannot renumber to %s - SNMP connect failed',
|
||||
$old_ip, $revofname;
|
||||
}
|
||||
}
|
||||
|
||||
if (scalar @{ setting('device_identity') }) {
|
||||
my @idmaps = @{ setting('device_identity') };
|
||||
my $devips = $device->device_ips->order_by('alias');
|
||||
|
||||
ALIAS: while (my $alias = $devips->next) {
|
||||
next if $alias->alias eq $old_ip;
|
||||
|
||||
foreach my $map (@idmaps) {
|
||||
next unless ref {} eq ref $map;
|
||||
|
||||
foreach my $key (sort keys %$map) {
|
||||
# lhs matches device, rhs matches device_ip
|
||||
if (check_acl_only($device, $key)
|
||||
and check_acl_only($alias, $map->{$key})) {
|
||||
|
||||
if ($snmp->snmp_connect_ip( $alias->alias )) {
|
||||
$new_ip = $alias->alias;
|
||||
last ALIAS;
|
||||
}
|
||||
else {
|
||||
debug sprintf ' [%s] device - cannot renumber to %s - SNMP connect failed',
|
||||
$old_ip, $alias->alias;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} # ALIAS
|
||||
}
|
||||
|
||||
return if $new_ip eq $old_ip;
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
# delete target device with the same vendor and serial number
|
||||
schema('netdisco')->resultset('Device')->search({
|
||||
ip => $new_ip, vendor => $device->vendor, serial => $device->serial,
|
||||
})->delete;
|
||||
|
||||
# if target device exists then this will die
|
||||
$device->renumber($new_ip)
|
||||
or die "cannot renumber to: $new_ip"; # rollback
|
||||
|
||||
debug sprintf ' [%s] device - changed IP to %s (%s)',
|
||||
$old_ip, $device->ip, ($device->dns || '');
|
||||
});
|
||||
}
|
||||
|
||||
=head2 store_device( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store basic device information.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_device {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
my $ip_index = $snmp->ip_index;
|
||||
my $interfaces = $snmp->interfaces;
|
||||
my $ip_netmask = $snmp->ip_netmask;
|
||||
|
||||
# build device aliases suitable for DBIC
|
||||
my @aliases;
|
||||
foreach my $entry (keys %$ip_index) {
|
||||
my $ip = NetAddr::IP::Lite->new($entry)
|
||||
or next;
|
||||
my $addr = $ip->addr;
|
||||
|
||||
next if $addr eq '0.0.0.0';
|
||||
next if check_acl_no($ip, 'group:__LOCAL_ADDRESSES__');
|
||||
next if setting('ignore_private_nets') and $ip->is_rfc1918;
|
||||
|
||||
my $iid = $ip_index->{$addr};
|
||||
my $port = $interfaces->{$iid};
|
||||
my $subnet = $ip_netmask->{$addr}
|
||||
? NetAddr::IP::Lite->new($addr, $ip_netmask->{$addr})->network->cidr
|
||||
: undef;
|
||||
|
||||
debug sprintf ' [%s] device - aliased as %s', $device->ip, $addr;
|
||||
push @aliases, {
|
||||
alias => $addr,
|
||||
port => $port,
|
||||
subnet => $subnet,
|
||||
dns => undef,
|
||||
};
|
||||
}
|
||||
|
||||
debug sprintf ' resolving %d aliases with max %d outstanding requests',
|
||||
scalar @aliases, $ENV{'PERL_ANYEVENT_MAX_OUTSTANDING_DNS'};
|
||||
my $resolved_aliases = hostnames_resolve_async(\@aliases);
|
||||
|
||||
# fake one aliases entry for devices not providing ip_index
|
||||
push @$resolved_aliases, { alias => $device->ip, dns => $device->dns }
|
||||
if 0 == scalar @aliases;
|
||||
|
||||
# VTP Management Domain -- assume only one.
|
||||
my $vtpdomains = $snmp->vtp_d_name;
|
||||
my $vtpdomain;
|
||||
if (defined $vtpdomains and scalar values %$vtpdomains) {
|
||||
$device->set_column( vtp_domain => (values %$vtpdomains)[-1] );
|
||||
}
|
||||
|
||||
my $hostname = hostname_from_ip($device->ip);
|
||||
$device->set_column( dns => $hostname ) if $hostname;
|
||||
|
||||
my @properties = qw/
|
||||
snmp_ver
|
||||
description uptime name
|
||||
layers ports mac
|
||||
ps1_type ps2_type ps1_status ps2_status
|
||||
fan slots
|
||||
vendor os os_ver
|
||||
/;
|
||||
|
||||
foreach my $property (@properties) {
|
||||
$device->set_column( $property => $snmp->$property );
|
||||
}
|
||||
|
||||
$device->set_column( model => Encode::decode('UTF-8', $snmp->model) );
|
||||
$device->set_column( serial => Encode::decode('UTF-8', $snmp->serial) );
|
||||
$device->set_column( contact => Encode::decode('UTF-8', $snmp->contact) );
|
||||
$device->set_column( location => Encode::decode('UTF-8', $snmp->location) );
|
||||
|
||||
|
||||
$device->set_column( snmp_class => $snmp->class );
|
||||
$device->set_column( last_discover => \'now()' );
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->device_ips->delete;
|
||||
debug sprintf ' [%s] device - removed %d aliases',
|
||||
$device->ip, $gone;
|
||||
$device->update_or_insert(undef, {for => 'update'});
|
||||
$device->device_ips->populate($resolved_aliases);
|
||||
debug sprintf ' [%s] device - added %d new aliases',
|
||||
$device->ip, scalar @aliases;
|
||||
});
|
||||
}
|
||||
|
||||
=head2 store_interfaces( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store the device's interface/port information.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_interfaces {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
my $interfaces = $snmp->interfaces;
|
||||
my $i_type = $snmp->i_type;
|
||||
my $i_ignore = $snmp->i_ignore;
|
||||
my $i_descr = $snmp->i_description;
|
||||
my $i_mtu = $snmp->i_mtu;
|
||||
my $i_speed = $snmp->i_speed;
|
||||
my $i_mac = $snmp->i_mac;
|
||||
my $i_up = $snmp->i_up;
|
||||
my $i_up_admin = $snmp->i_up_admin;
|
||||
my $i_name = $snmp->i_name;
|
||||
my $i_duplex = $snmp->i_duplex;
|
||||
my $i_duplex_admin = $snmp->i_duplex_admin;
|
||||
my $i_stp_state = $snmp->i_stp_state;
|
||||
my $i_vlan = $snmp->i_vlan;
|
||||
my $i_lastchange = $snmp->i_lastchange;
|
||||
my $agg_ports = $snmp->agg_ports;
|
||||
|
||||
# clear the cached uptime and get a new one
|
||||
my $dev_uptime = $snmp->load_uptime;
|
||||
if (!defined $dev_uptime) {
|
||||
error sprintf ' [%s] interfaces - Error! Failed to get uptime from device!',
|
||||
$device->ip;
|
||||
return;
|
||||
}
|
||||
|
||||
# used to track how many times the device uptime wrapped
|
||||
my $dev_uptime_wrapped = 0;
|
||||
|
||||
# use SNMP-FRAMEWORK-MIB::snmpEngineTime if available to
|
||||
# fix device uptime if wrapped
|
||||
if (defined $snmp->snmpEngineTime) {
|
||||
$dev_uptime_wrapped = int( $snmp->snmpEngineTime * 100 / 2**32 );
|
||||
if ($dev_uptime_wrapped > 0) {
|
||||
info sprintf ' [%s] interface - device uptime wrapped %d times - correcting',
|
||||
$device->ip, $dev_uptime_wrapped;
|
||||
$device->uptime( $dev_uptime + $dev_uptime_wrapped * 2**32 );
|
||||
}
|
||||
}
|
||||
|
||||
# build device interfaces suitable for DBIC
|
||||
my %interfaces;
|
||||
foreach my $entry (keys %$interfaces) {
|
||||
my $port = $interfaces->{$entry};
|
||||
|
||||
if (not $port) {
|
||||
debug sprintf ' [%s] interfaces - ignoring %s (no port mapping)',
|
||||
$device->ip, $entry;
|
||||
next;
|
||||
}
|
||||
|
||||
if (scalar grep {$port =~ m/^$_$/} @{setting('ignore_interfaces') || []}) {
|
||||
debug sprintf
|
||||
' [%s] interfaces - ignoring %s (%s) (config:ignore_interfaces)',
|
||||
$device->ip, $entry, $port;
|
||||
next;
|
||||
}
|
||||
|
||||
if (exists $i_ignore->{$entry}) {
|
||||
debug sprintf ' [%s] interfaces - ignoring %s (%s) (%s)',
|
||||
$device->ip, $entry, $port, $i_type->{$entry};
|
||||
next;
|
||||
}
|
||||
|
||||
my $lc = $i_lastchange->{$entry} || 0;
|
||||
if (not $dev_uptime_wrapped and $lc > $dev_uptime) {
|
||||
info sprintf ' [%s] interfaces - device uptime wrapped (%s) - correcting',
|
||||
$device->ip, $port;
|
||||
$device->uptime( $dev_uptime + 2**32 );
|
||||
$dev_uptime_wrapped = 1;
|
||||
}
|
||||
|
||||
if ($device->is_column_changed('uptime') and $lc) {
|
||||
if ($lc < $dev_uptime) {
|
||||
# ambiguous: lastchange could be sysUptime before or after wrap
|
||||
if ($dev_uptime > 30000 and $lc < 30000) {
|
||||
# uptime wrap more than 5min ago but lastchange within 5min
|
||||
# assume lastchange was directly after boot -> no action
|
||||
}
|
||||
else {
|
||||
# uptime wrap less than 5min ago or lastchange > 5min ago
|
||||
# to be on safe side, assume lastchange after counter wrap
|
||||
debug sprintf
|
||||
' [%s] interfaces - correcting LastChange for %s, assuming sysUptime wrap',
|
||||
$device->ip, $port;
|
||||
$lc += $dev_uptime_wrapped * 2**32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$interfaces{$port} = {
|
||||
port => $port,
|
||||
descr => $i_descr->{$entry},
|
||||
up => $i_up->{$entry},
|
||||
up_admin => $i_up_admin->{$entry},
|
||||
mac => $i_mac->{$entry},
|
||||
speed => $i_speed->{$entry},
|
||||
mtu => $i_mtu->{$entry},
|
||||
name => Encode::decode('UTF-8', $i_name->{$entry}),
|
||||
duplex => $i_duplex->{$entry},
|
||||
duplex_admin => $i_duplex_admin->{$entry},
|
||||
stp => $i_stp_state->{$entry},
|
||||
type => $i_type->{$entry},
|
||||
vlan => $i_vlan->{$entry},
|
||||
pvid => $i_vlan->{$entry},
|
||||
is_master => 'false',
|
||||
slave_of => undef,
|
||||
lastchange => $lc,
|
||||
};
|
||||
}
|
||||
|
||||
# must do this after building %interfaces so that we can set is_master
|
||||
foreach my $sidx (keys %$agg_ports) {
|
||||
my $slave = $interfaces->{$sidx} or next;
|
||||
my $master = $interfaces->{ $agg_ports->{$sidx} } or next;
|
||||
next unless exists $interfaces{$slave} and exists $interfaces{$master};
|
||||
|
||||
$interfaces{$slave}->{slave_of} = $master;
|
||||
$interfaces{$master}->{is_master} = 'true';
|
||||
}
|
||||
|
||||
schema('netdisco')->resultset('DevicePort')->txn_do_locked(sub {
|
||||
my $gone = $device->ports->delete({keep_nodes => 1});
|
||||
debug sprintf ' [%s] interfaces - removed %d interfaces',
|
||||
$device->ip, $gone;
|
||||
$device->update_or_insert(undef, {for => 'update'});
|
||||
$device->ports->populate([values %interfaces]);
|
||||
debug sprintf ' [%s] interfaces - added %d new interfaces',
|
||||
$device->ip, scalar values %interfaces;
|
||||
});
|
||||
}
|
||||
|
||||
=head2 store_wireless( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store the device's wireless interface information.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_wireless {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
my $ssidlist = $snmp->i_ssidlist;
|
||||
return unless scalar keys %$ssidlist;
|
||||
|
||||
my $interfaces = $snmp->interfaces;
|
||||
my $ssidbcast = $snmp->i_ssidbcast;
|
||||
my $ssidmac = $snmp->i_ssidmac;
|
||||
my $channel = $snmp->i_80211channel;
|
||||
my $power = $snmp->dot11_cur_tx_pwr_mw;
|
||||
|
||||
# build device ssid list suitable for DBIC
|
||||
my @ssids;
|
||||
foreach my $entry (keys %$ssidlist) {
|
||||
(my $iid = $entry) =~ s/\.\d+$//;
|
||||
my $port = $interfaces->{$iid};
|
||||
|
||||
if (not $port) {
|
||||
debug sprintf ' [%s] wireless - ignoring %s (no port mapping)',
|
||||
$device->ip, $iid;
|
||||
next;
|
||||
}
|
||||
|
||||
push @ssids, {
|
||||
port => $port,
|
||||
ssid => $ssidlist->{$entry},
|
||||
broadcast => $ssidbcast->{$entry},
|
||||
bssid => $ssidmac->{$entry},
|
||||
};
|
||||
}
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->ssids->delete;
|
||||
debug sprintf ' [%s] wireless - removed %d SSIDs',
|
||||
$device->ip, $gone;
|
||||
$device->ssids->populate(\@ssids);
|
||||
debug sprintf ' [%s] wireless - added %d new SSIDs',
|
||||
$device->ip, scalar @ssids;
|
||||
});
|
||||
|
||||
# build device channel list suitable for DBIC
|
||||
my @channels;
|
||||
foreach my $entry (keys %$channel) {
|
||||
my $port = $interfaces->{$entry};
|
||||
|
||||
if (not $port) {
|
||||
debug sprintf ' [%s] wireless - ignoring %s (no port mapping)',
|
||||
$device->ip, $entry;
|
||||
next;
|
||||
}
|
||||
|
||||
push @channels, {
|
||||
port => $port,
|
||||
channel => $channel->{$entry},
|
||||
power => $power->{$entry},
|
||||
};
|
||||
}
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->wireless_ports->delete;
|
||||
debug sprintf ' [%s] wireless - removed %d wireless channels',
|
||||
$device->ip, $gone;
|
||||
$device->wireless_ports->populate(\@channels);
|
||||
debug sprintf ' [%s] wireless - added %d new wireless channels',
|
||||
$device->ip, scalar @channels;
|
||||
});
|
||||
}
|
||||
|
||||
=head2 store_vlans( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store the device's vlan information.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_vlans {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
my $v_name = $snmp->v_name;
|
||||
my $v_index = $snmp->v_index;
|
||||
|
||||
# build device vlans suitable for DBIC
|
||||
my %v_seen = ();
|
||||
my @devicevlans;
|
||||
foreach my $entry (keys %$v_name) {
|
||||
my $vlan = $v_index->{$entry};
|
||||
next unless defined $vlan and $vlan;
|
||||
++$v_seen{$vlan};
|
||||
|
||||
push @devicevlans, {
|
||||
vlan => $vlan,
|
||||
description => $v_name->{$entry},
|
||||
last_discover => \'now()',
|
||||
};
|
||||
}
|
||||
|
||||
my $i_vlan = $snmp->i_vlan;
|
||||
my $i_vlan_membership = $snmp->i_vlan_membership;
|
||||
my $i_vlan_type = $snmp->i_vlan_type;
|
||||
my $interfaces = $snmp->interfaces;
|
||||
|
||||
# build device port vlans suitable for DBIC
|
||||
my @portvlans = ();
|
||||
foreach my $entry (keys %$i_vlan_membership) {
|
||||
my %port_vseen = ();
|
||||
my $port = $interfaces->{$entry};
|
||||
next unless defined $port;
|
||||
|
||||
my $type = $i_vlan_type->{$entry};
|
||||
|
||||
foreach my $vlan (@{ $i_vlan_membership->{$entry} }) {
|
||||
next unless defined $vlan and $vlan;
|
||||
next if ++$port_vseen{$vlan} > 1;
|
||||
|
||||
my $native = ((defined $i_vlan->{$entry}) and ($vlan eq $i_vlan->{$entry})) ? "t" : "f";
|
||||
push @portvlans, {
|
||||
port => $port,
|
||||
vlan => $vlan,
|
||||
native => $native,
|
||||
vlantype => $type,
|
||||
last_discover => \'now()',
|
||||
};
|
||||
|
||||
next if $v_seen{$vlan};
|
||||
|
||||
# also add an unnamed vlan to the device
|
||||
push @devicevlans, {
|
||||
vlan => $vlan,
|
||||
description => (sprintf "VLAN %d", $vlan),
|
||||
last_discover => \'now()',
|
||||
};
|
||||
++$v_seen{$vlan};
|
||||
}
|
||||
}
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->vlans->delete;
|
||||
debug sprintf ' [%s] vlans - removed %d device VLANs',
|
||||
$device->ip, $gone;
|
||||
$device->vlans->populate(\@devicevlans);
|
||||
debug sprintf ' [%s] vlans - added %d new device VLANs',
|
||||
$device->ip, scalar @devicevlans;
|
||||
});
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->port_vlans->delete;
|
||||
debug sprintf ' [%s] vlans - removed %d port VLANs',
|
||||
$device->ip, $gone;
|
||||
$device->port_vlans->populate(\@portvlans);
|
||||
debug sprintf ' [%s] vlans - added %d new port VLANs',
|
||||
$device->ip, scalar @portvlans;
|
||||
});
|
||||
}
|
||||
|
||||
=head2 store_power( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store the device's PoE information.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_power {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
my $p_watts = $snmp->peth_power_watts;
|
||||
my $p_status = $snmp->peth_power_status;
|
||||
|
||||
if (!defined $p_watts) {
|
||||
debug sprintf ' [%s] power - 0 power modules', $device->ip;
|
||||
return;
|
||||
}
|
||||
|
||||
# build device module power info suitable for DBIC
|
||||
my @devicepower;
|
||||
foreach my $entry (keys %$p_watts) {
|
||||
push @devicepower, {
|
||||
module => $entry,
|
||||
power => $p_watts->{$entry},
|
||||
status => $p_status->{$entry},
|
||||
};
|
||||
}
|
||||
|
||||
my $interfaces = $snmp->interfaces;
|
||||
my $p_ifindex = $snmp->peth_port_ifindex;
|
||||
my $p_admin = $snmp->peth_port_admin;
|
||||
my $p_pstatus = $snmp->peth_port_status;
|
||||
my $p_class = $snmp->peth_port_class;
|
||||
my $p_power = $snmp->peth_port_power;
|
||||
|
||||
# build device port power info suitable for DBIC
|
||||
my @portpower;
|
||||
foreach my $entry (keys %$p_ifindex) {
|
||||
my $port = $interfaces->{ $p_ifindex->{$entry} };
|
||||
next unless $port;
|
||||
|
||||
my ($module) = split m/\./, $entry;
|
||||
|
||||
push @portpower, {
|
||||
port => $port,
|
||||
module => $module,
|
||||
admin => $p_admin->{$entry},
|
||||
status => $p_pstatus->{$entry},
|
||||
class => $p_class->{$entry},
|
||||
power => $p_power->{$entry},
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->power_modules->delete;
|
||||
debug sprintf ' [%s] power - removed %d power modules',
|
||||
$device->ip, $gone;
|
||||
$device->power_modules->populate(\@devicepower);
|
||||
debug sprintf ' [%s] power - added %d new power modules',
|
||||
$device->ip, scalar @devicepower;
|
||||
});
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->powered_ports->delete;
|
||||
debug sprintf ' [%s] power - removed %d PoE capable ports',
|
||||
$device->ip, $gone;
|
||||
$device->powered_ports->populate(\@portpower);
|
||||
debug sprintf ' [%s] power - added %d new PoE capable ports',
|
||||
$device->ip, scalar @portpower;
|
||||
});
|
||||
}
|
||||
|
||||
=head2 store_modules( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store the device's module information.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_modules {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
my $e_index = $snmp->e_index;
|
||||
|
||||
if (!defined $e_index) {
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->modules->delete;
|
||||
debug sprintf ' [%s] modules - removed %d chassis modules',
|
||||
$device->ip, $gone;
|
||||
|
||||
$device->modules->update_or_create({
|
||||
ip => $device->ip,
|
||||
index => 1,
|
||||
parent => 0,
|
||||
name => 'chassis',
|
||||
class => 'chassis',
|
||||
pos => -1,
|
||||
# too verbose and link doesn't work anyway
|
||||
# description => $device->description,
|
||||
sw_ver => $device->os_ver,
|
||||
serial => $device->serial,
|
||||
model => $device->model,
|
||||
fru => \'false',
|
||||
last_discover => \'now()',
|
||||
});
|
||||
});
|
||||
|
||||
debug
|
||||
sprintf ' [%s] modules - 0 chassis components (added one pseudo for chassis)',
|
||||
$device->ip;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
my $e_descr = $snmp->e_descr;
|
||||
my $e_type = $snmp->e_type;
|
||||
my $e_parent = $snmp->e_parent;
|
||||
my $e_name = $snmp->e_name;
|
||||
my $e_class = $snmp->e_class;
|
||||
my $e_pos = $snmp->e_pos;
|
||||
my $e_hwver = $snmp->e_hwver;
|
||||
my $e_fwver = $snmp->e_fwver;
|
||||
my $e_swver = $snmp->e_swver;
|
||||
my $e_model = $snmp->e_model;
|
||||
my $e_serial = $snmp->e_serial;
|
||||
my $e_fru = $snmp->e_fru;
|
||||
|
||||
# build device modules list for DBIC
|
||||
my @modules;
|
||||
foreach my $entry (keys %$e_index) {
|
||||
push @modules, {
|
||||
index => $e_index->{$entry},
|
||||
type => $e_type->{$entry},
|
||||
parent => $e_parent->{$entry},
|
||||
name => Encode::decode('UTF-8', $e_name->{$entry}),
|
||||
class => $e_class->{$entry},
|
||||
pos => $e_pos->{$entry},
|
||||
hw_ver => Encode::decode('UTF-8', $e_hwver->{$entry}),
|
||||
fw_ver => Encode::decode('UTF-8', $e_fwver->{$entry}),
|
||||
sw_ver => Encode::decode('UTF-8', $e_swver->{$entry}),
|
||||
model => Encode::decode('UTF-8', $e_model->{$entry}),
|
||||
serial => Encode::decode('UTF-8', $e_serial->{$entry}),
|
||||
fru => $e_fru->{$entry},
|
||||
description => Encode::decode('UTF-8', $e_descr->{$entry}),
|
||||
last_discover => \'now()',
|
||||
};
|
||||
}
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->modules->delete;
|
||||
debug sprintf ' [%s] modules - removed %d chassis modules',
|
||||
$device->ip, $gone;
|
||||
$device->modules->populate(\@modules);
|
||||
debug sprintf ' [%s] modules - added %d new chassis modules',
|
||||
$device->ip, scalar @modules;
|
||||
});
|
||||
}
|
||||
|
||||
=head2 store_neighbors( $device, $snmp )
|
||||
|
||||
returns: C<@to_discover>
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store the device's port neighbors information.
|
||||
|
||||
Entries in the Topology database table will override any discovered device
|
||||
port relationships.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
A list of discovererd neighbors will be returned as [C<$ip>, C<$type>] tuples.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_neighbors {
|
||||
my ($device, $snmp) = @_;
|
||||
my @to_discover = ();
|
||||
|
||||
# first allow any manually configured topology to be set
|
||||
_set_manual_topology($device, $snmp);
|
||||
|
||||
if (!defined $snmp->has_topo) {
|
||||
debug sprintf ' [%s] neigh - neighbor protocols are not enabled', $device->ip;
|
||||
return @to_discover;
|
||||
}
|
||||
|
||||
my $interfaces = $snmp->interfaces;
|
||||
my $c_if = $snmp->c_if;
|
||||
my $c_port = $snmp->c_port;
|
||||
my $c_id = $snmp->c_id;
|
||||
my $c_platform = $snmp->c_platform;
|
||||
my $c_cap = $snmp->c_cap;
|
||||
|
||||
# v4 and v6 neighbor tables
|
||||
my $c_ip = ($snmp->c_ip || {});
|
||||
my %c_ipv6 = %{ ($snmp->can('hasLLDP') and $snmp->hasLLDP)
|
||||
? ($snmp->lldp_ipv6 || {}) : {} };
|
||||
|
||||
# remove keys with undef values, as c_ip does
|
||||
delete @c_ipv6{ grep { not defined $c_ipv6{$_} } keys %c_ipv6 };
|
||||
# now combine them, v6 wins
|
||||
$c_ip = { %$c_ip, %c_ipv6 };
|
||||
|
||||
foreach my $entry (sort (List::MoreUtils::uniq( (keys %$c_ip), (keys %$c_cap) ))) {
|
||||
if (!defined $c_if->{$entry} or !defined $interfaces->{ $c_if->{$entry} }) {
|
||||
debug sprintf ' [%s] neigh - port for IID:%s not resolved, skipping',
|
||||
$device->ip, $entry;
|
||||
next;
|
||||
}
|
||||
|
||||
my $port = $interfaces->{ $c_if->{$entry} };
|
||||
my $portrow = schema('netdisco')->resultset('DevicePort')
|
||||
->single({ip => $device->ip, port => $port});
|
||||
|
||||
if (!defined $portrow) {
|
||||
info sprintf ' [%s] neigh - local port %s not in database!',
|
||||
$device->ip, $port;
|
||||
next;
|
||||
}
|
||||
|
||||
if (ref $c_ip->{$entry}) {
|
||||
error sprintf ' [%s] neigh - Error! port %s has multiple neighbors - skipping',
|
||||
$device->ip, $port;
|
||||
next;
|
||||
}
|
||||
|
||||
my $remote_ip = $c_ip->{$entry};
|
||||
my $remote_port = undef;
|
||||
my $remote_type = Encode::decode('UTF-8', $c_platform->{$entry} || '');
|
||||
my $remote_id = Encode::decode('UTF-8', $c_id->{$entry});
|
||||
my $remote_cap = $c_cap->{$entry} || [];
|
||||
|
||||
# IP Phone and WAP detection type fixup
|
||||
if (scalar @$remote_cap or $remote_type) {
|
||||
my $phone_flag = grep {match_devicetype($_, 'phone_capabilities')}
|
||||
@$remote_cap;
|
||||
my $ap_flag = grep {match_devicetype($_, 'wap_capabilities')}
|
||||
@$remote_cap;
|
||||
|
||||
if ($phone_flag or match_devicetype($remote_type, 'phone_platforms')) {
|
||||
$remote_type = 'IP Phone: '. $remote_type
|
||||
if $remote_type !~ /ip.phone/i;
|
||||
}
|
||||
elsif ($ap_flag or match_devicetype($remote_type, 'wap_platforms')) {
|
||||
$remote_type = 'AP: '. $remote_type;
|
||||
}
|
||||
|
||||
$portrow->update({remote_type => $remote_type});
|
||||
}
|
||||
|
||||
if ($portrow->manual_topo) {
|
||||
info sprintf ' [%s] neigh - %s has manually defined topology',
|
||||
$device->ip, $port;
|
||||
next;
|
||||
}
|
||||
|
||||
next unless $remote_ip;
|
||||
|
||||
# a bunch of heuristics to search known devices if we don't have a
|
||||
# useable remote IP...
|
||||
|
||||
if ($remote_ip eq '0.0.0.0' or
|
||||
check_acl_no($remote_ip, 'group:__LOCAL_ADDRESSES__')) {
|
||||
|
||||
if ($remote_id) {
|
||||
my $devices = schema('netdisco')->resultset('Device');
|
||||
my $neigh = $devices->single({name => $remote_id});
|
||||
info sprintf
|
||||
' [%s] neigh - bad address %s on port %s, searching for %s instead',
|
||||
$device->ip, $remote_ip, $port, $remote_id;
|
||||
|
||||
if (!defined $neigh) {
|
||||
my $mac = NetAddr::MAC->new(mac => $remote_id);
|
||||
if ($mac and not $mac->errstr) {
|
||||
$neigh = $devices->single({mac => $mac->as_ieee});
|
||||
}
|
||||
}
|
||||
|
||||
# some HP switches send 127.0.0.1 as remote_ip if no ip address
|
||||
# on default vlan for HP switches remote_ip looks like
|
||||
# "myswitchname(012345-012345)"
|
||||
if (!defined $neigh) {
|
||||
(my $tmpid = $remote_id) =~ s/.*\(([0-9a-f]{6})-([0-9a-f]{6})\).*/$1$2/;
|
||||
my $mac = NetAddr::MAC->new(mac => $tmpid);
|
||||
if ($mac and not $mac->errstr) {
|
||||
info sprintf
|
||||
'[%s] neigh - found neighbor %s by MAC %s',
|
||||
$device->ip, $remote_id, $mac->as_ieee;
|
||||
$neigh = $devices->single({mac => $mac->as_ieee});
|
||||
}
|
||||
}
|
||||
|
||||
if (!defined $neigh) {
|
||||
(my $shortid = $remote_id) =~ s/\..*//;
|
||||
$neigh = $devices->single({name => { -ilike => "${shortid}%" }});
|
||||
}
|
||||
|
||||
if ($neigh) {
|
||||
$remote_ip = $neigh->ip;
|
||||
info sprintf ' [%s] neigh - found %s with IP %s',
|
||||
$device->ip, $remote_id, $remote_ip;
|
||||
}
|
||||
else {
|
||||
info sprintf ' [%s] neigh - could not find %s, skipping',
|
||||
$device->ip, $remote_id;
|
||||
next;
|
||||
}
|
||||
}
|
||||
else {
|
||||
info sprintf ' [%s] neigh - skipping unuseable address %s on port %s',
|
||||
$device->ip, $remote_ip, $port;
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
# what we came here to do.... discover the neighbor
|
||||
debug sprintf
|
||||
' [%s] neigh - adding neighbor %s, type [%s], on %s to discovery queue',
|
||||
$device->ip, $remote_ip, ($remote_type || ''), $port;
|
||||
push @to_discover, [$remote_ip, $remote_type];
|
||||
|
||||
$remote_port = $c_port->{$entry};
|
||||
if (defined $remote_port) {
|
||||
# clean weird characters
|
||||
$remote_port =~ s/[^\d\/\.,()\w:-]+//gi;
|
||||
}
|
||||
else {
|
||||
info sprintf ' [%s] neigh - no remote port found for port %s at %s',
|
||||
$device->ip, $port, $remote_ip;
|
||||
}
|
||||
|
||||
$portrow->update({
|
||||
remote_ip => $remote_ip,
|
||||
remote_port => $remote_port,
|
||||
remote_type => $remote_type,
|
||||
remote_id => $remote_id,
|
||||
is_uplink => \"true",
|
||||
manual_topo => \"false",
|
||||
});
|
||||
|
||||
# update master of our aggregate to be a neighbor of
|
||||
# the master on our peer device (a lot of iffs to get there...).
|
||||
# & cannot use ->neighbor prefetch because this is the port insert!
|
||||
if (defined $portrow->slave_of) {
|
||||
|
||||
my $peer_device = get_device($remote_ip);
|
||||
my $master = schema('netdisco')->resultset('DevicePort')->single({
|
||||
ip => $device->ip,
|
||||
port => $portrow->slave_of
|
||||
});
|
||||
|
||||
if ($peer_device and $peer_device->in_storage and $master
|
||||
and not ($portrow->is_master or defined $master->slave_of)) {
|
||||
|
||||
my $peer_port = schema('netdisco')->resultset('DevicePort')->single({
|
||||
ip => $peer_device->ip,
|
||||
port => $portrow->remote_port,
|
||||
});
|
||||
|
||||
$master->update({
|
||||
remote_ip => ($peer_device->ip || $remote_ip),
|
||||
remote_port => ($peer_port ? $peer_port->slave_of : undef ),
|
||||
is_uplink => \"true",
|
||||
is_master => \"true",
|
||||
manual_topo => \"false",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return @to_discover;
|
||||
}
|
||||
|
||||
# take data from the topology table and update remote_ip and remote_port
|
||||
# in the devices table. only use root_ips and skip any bad topo entries.
|
||||
sub _set_manual_topology {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
# clear manual topology flags
|
||||
schema('netdisco')->resultset('DevicePort')
|
||||
->search({ip => $device->ip})->update({manual_topo => \'false'});
|
||||
|
||||
my $topo_links = schema('netdisco')->resultset('Topology')
|
||||
->search({-or => [dev1 => $device->ip, dev2 => $device->ip]});
|
||||
debug sprintf ' [%s] neigh - setting manual topology links', $device->ip;
|
||||
|
||||
while (my $link = $topo_links->next) {
|
||||
# could fail for broken topo, but we ignore to try the rest
|
||||
try {
|
||||
schema('netdisco')->txn_do(sub {
|
||||
# only work on root_ips
|
||||
my $left = get_device($link->dev1);
|
||||
my $right = get_device($link->dev2);
|
||||
|
||||
# skip bad entries
|
||||
return unless ($left->in_storage and $right->in_storage);
|
||||
|
||||
$left->ports
|
||||
->single({port => $link->port1})
|
||||
->update({
|
||||
remote_ip => $right->ip,
|
||||
remote_port => $link->port2,
|
||||
remote_type => undef,
|
||||
remote_id => undef,
|
||||
is_uplink => \"true",
|
||||
manual_topo => \"true",
|
||||
});
|
||||
|
||||
$right->ports
|
||||
->single({port => $link->port2})
|
||||
->update({
|
||||
remote_ip => $left->ip,
|
||||
remote_port => $link->port1,
|
||||
remote_type => undef,
|
||||
remote_id => undef,
|
||||
is_uplink => \"true",
|
||||
manual_topo => \"true",
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
=head2 discover_new_neighbors( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store the device's port neighbors information.
|
||||
|
||||
Entries in the Topology database table will override any discovered device
|
||||
port relationships.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
Any discovered neighbor unknown to Netdisco will have a C<discover> job
|
||||
immediately queued (subject to the filtering by the C<discover_*> settings).
|
||||
|
||||
=cut
|
||||
|
||||
sub discover_new_neighbors {
|
||||
my @to_discover = store_neighbors(@_);
|
||||
|
||||
# only enqueue if device is not already discovered,
|
||||
# discover_* config permits the discovery
|
||||
foreach my $neighbor (@to_discover) {
|
||||
my ($ip, $remote_type) = @$neighbor;
|
||||
|
||||
my $device = get_device($ip);
|
||||
next if $device->in_storage;
|
||||
|
||||
if (not is_discoverable($device, $remote_type)) {
|
||||
debug sprintf
|
||||
' queue - %s, type [%s] excluded by discover_* config',
|
||||
$ip, ($remote_type || '');
|
||||
next;
|
||||
}
|
||||
|
||||
jq_insert({
|
||||
device => $ip,
|
||||
action => 'discover',
|
||||
subaction => 'with-nodes',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -10,6 +10,8 @@ use warnings;
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
use App::Netdisco::Util::DNS 'hostname_from_ip';
|
||||
|
||||
use overload '""' => sub { shift->ip }, fallback => 1;
|
||||
|
||||
use base 'DBIx::Class::Core';
|
||||
__PACKAGE__->table("device");
|
||||
__PACKAGE__->add_columns(
|
||||
@@ -191,6 +193,29 @@ __PACKAGE__->might_have(
|
||||
|
||||
=head1 ADDITIONAL METHODS
|
||||
|
||||
=head2 is_pseudo
|
||||
|
||||
Returns true if the vendor of the device is "netdisco".
|
||||
|
||||
=cut
|
||||
|
||||
sub is_pseudo {
|
||||
my $device = shift;
|
||||
return (defined $device->vendor and $device->vendor eq 'netdisco');
|
||||
}
|
||||
|
||||
=head2 has_layer( $number )
|
||||
|
||||
Returns true if the device provided sysServices and supports the given layer.
|
||||
|
||||
=cut
|
||||
|
||||
sub has_layer {
|
||||
my ($device, $layer) = @_;
|
||||
return unless $layer and $layer =~ m/^[1-7]$/;
|
||||
return ($device->layers and (substr($device->layers, (8-$layer), 1) == 1));
|
||||
}
|
||||
|
||||
=head2 renumber( $new_ip )
|
||||
|
||||
Will update this device and all related database records to use the new IP
|
||||
|
||||
@@ -153,12 +153,12 @@ sub jq_lock {
|
||||
try {
|
||||
schema('netdisco')->txn_do(sub {
|
||||
schema('netdisco')->resultset('Admin')
|
||||
->search({ job => $job->job }, { for => 'update' })
|
||||
->search({ job => $job->id }, { for => 'update' })
|
||||
->update({ status => ('queued-'. setting('workers')->{'BACKEND'}) });
|
||||
|
||||
return unless
|
||||
schema('netdisco')->resultset('Admin')
|
||||
->count({ job => $job->job,
|
||||
->count({ job => $job->id,
|
||||
status => ('queued-'. setting('workers')->{'BACKEND'}) });
|
||||
|
||||
# remove any duplicate jobs, needed because we have race conditions
|
||||
@@ -202,7 +202,7 @@ sub jq_defer {
|
||||
|
||||
# lock db row and update to show job is available
|
||||
schema('netdisco')->resultset('Admin')
|
||||
->find($job->job, {for => 'update'})
|
||||
->find($job->id, {for => 'update'})
|
||||
->update({ status => 'queued', started => undef });
|
||||
});
|
||||
$happy = true;
|
||||
@@ -233,7 +233,7 @@ sub jq_complete {
|
||||
}
|
||||
|
||||
schema('netdisco')->resultset('Admin')
|
||||
->find($job->job, {for => 'update'})->update({
|
||||
->find($job->id, {for => 'update'})->update({
|
||||
status => $job->status,
|
||||
log => $job->log,
|
||||
started => $job->started,
|
||||
|
||||
@@ -52,8 +52,11 @@ val2} >> on one line or C<key: value> on separate lines, e.g.:
|
||||
|
||||
=item *
|
||||
|
||||
String - Quoted, just like in Perl (and essential if the item contains the
|
||||
colon character).
|
||||
String - Optionally quoted, just like in Perl (quotes required if the item
|
||||
contains a colon character). For example:
|
||||
|
||||
log: 'debug'
|
||||
expire_devices: 15
|
||||
|
||||
=back
|
||||
|
||||
@@ -424,7 +427,7 @@ indenting the query text.
|
||||
=head4 C<category> (optional)
|
||||
|
||||
Section of the Reports menu where this report will appear. See
|
||||
L<WritingPlugins|App::Netdisco::Manual::WritingPlugins> for the full list.
|
||||
L<WritingWebPlugins|App::Netdisco::Manual::WritingWebPlugins> for the full list.
|
||||
If not supplied, reports appear in a I<My Reports> category.
|
||||
|
||||
=head4 C<hidden> (optional)
|
||||
@@ -581,7 +584,7 @@ Value: Dictionary of Access Control Lists. Default: None.
|
||||
Several configuration settings in Netdisco make use of L</"ACCESS CONTROL
|
||||
LISTS"> to identify lists of devices or hosts. Examples are the C<*_no>
|
||||
settings such as C<discover_no>, the C<*_only> settings such as C<macsuck_no>,
|
||||
and some "C<only>" settings which appear in C<snmp_auth> and C<dns>
|
||||
and some "C<only>" settings which appear in C<device_auth> and C<dns>
|
||||
configuration.
|
||||
|
||||
The C<host_groups> setting allows for naming of groups which are then
|
||||
@@ -645,11 +648,11 @@ renumbered to use that interface as its new identity and the process stops.
|
||||
When using an Access Control List for the value (interface selection), as well
|
||||
as the options described in L</"ACCESS CONTROL LISTS"> you can use
|
||||
"C<port:regexp>" to match an interface's port name. For example to renumber
|
||||
all Arista devices to the IP and host name of their Mgmt1 interface (if they
|
||||
have one), you could use:
|
||||
any device to the IP and host name of its Mgmt1 interface (if it has one), you
|
||||
could use:
|
||||
|
||||
device_identity:
|
||||
'vendor:arista': 'port:(?i)mgmt1'
|
||||
'any': 'port:(?i)mgmt1'
|
||||
|
||||
Once a device is renumbered, its new identity is "sticky". That is, you could
|
||||
remove the C<device_identity> configuration and the next "discover" job will
|
||||
@@ -711,7 +714,7 @@ Each is tried in turn when polling the device, and then the working community
|
||||
string will be cached in the database.
|
||||
|
||||
For fine-grained control over which communities are tried for which devices,
|
||||
or to set SNMPv3 authentication, see C<snmp_auth>, below.
|
||||
or to set SNMPv3 authentication, see C<device_auth>, below.
|
||||
|
||||
=head3 C<community_rw>
|
||||
|
||||
@@ -725,13 +728,13 @@ is tried in turn when writing to the device, and then the working community
|
||||
string will be cached in the database.
|
||||
|
||||
For fine-grained control over which communities are tried for which devices,
|
||||
or to set SNMPv3 authentication, see C<snmp_auth>, below.
|
||||
or to set SNMPv3 authentication, see C<device_auth>, below.
|
||||
|
||||
=head3 C<snmp_auth>
|
||||
=head3 C<device_auth>
|
||||
|
||||
Value: List of Settings Trees. Default: Empty List.
|
||||
|
||||
This setting configures authenticaiton for all SNMP versions, and provides an
|
||||
This setting configures authenticaiton for all polling, and provides an
|
||||
alternative fine-grained control for SNMPv1 and SNMPv2 community strings. You
|
||||
provide a list of authentication "I<stanza>", and Netdisco will try each in
|
||||
turn, then cache the one which works for a device.
|
||||
@@ -741,7 +744,7 @@ limited to read (get) and/or write (set) operations. By default, a stanza is
|
||||
enabled for all device IPs, for read access only. The "tag" of a stanza is
|
||||
simply a friendly name used by Netdisco when referring to the configuration.
|
||||
|
||||
snmp_auth:
|
||||
device_auth:
|
||||
- community: public
|
||||
- community: publictwo
|
||||
- community: mycommunity
|
||||
@@ -757,26 +760,23 @@ simply a friendly name used by Netdisco when referring to the configuration.
|
||||
priv:
|
||||
pass: netdiscokey2
|
||||
proto: DES
|
||||
- tag: v3aclexample
|
||||
user: netdisco2
|
||||
- tag: aclexample
|
||||
community: s3kr1t
|
||||
read: false
|
||||
write: true
|
||||
only:
|
||||
- 192.0.2.0/30
|
||||
- 172.20.10.0/24
|
||||
no: '172.20.10.1'
|
||||
- tag: v2aclexample
|
||||
community: s3kr1t
|
||||
read: false
|
||||
write: true
|
||||
only: '2001:db8::/32'
|
||||
|
||||
For SNMPv1 and SNMPv2, only the C<community> key is required. Unlike the
|
||||
global C<community>/C<community_rw> setting, this is not a list but a single
|
||||
item. That is, to configure multiple community strings, have one stanza per
|
||||
item. Therefore, to configure multiple community strings, have one stanza per
|
||||
community, as in the examples above and below.
|
||||
|
||||
For any version of SNMP you can add C<read> and/or C<write> booleans to
|
||||
control operations for that stanza, and IP restrictions using C<only> and
|
||||
C<no> (see L</"ACCESS CONTROL LISTS"> for what you can use here).
|
||||
For any sanza you can add C<read> and/or C<write> booleans to control whether
|
||||
it is used for get and/or set operations, and IP restrictions using C<only>
|
||||
and C<no> (see L</"ACCESS CONTROL LISTS"> for what you can use here).
|
||||
|
||||
For SNMPv3 the C<tag> and C<user> keys are required. Providing an C<auth>
|
||||
section enables the authentication security level, providing a C<priv> section
|
||||
@@ -794,6 +794,26 @@ this you usually configure a common context "prefix", with Netdisco's default
|
||||
being "C<vlan->" (i.e. C<vlan-1>, C<vlan-2>, etc). Add the C<context_prefix>
|
||||
key to a stanza to override this default.
|
||||
|
||||
For other authentication mechanisms (HTTP, SSH, etc), C<tag> is also required.
|
||||
Each transport will have different settings, but usually a C<username> and
|
||||
C<password> are required, and optionally some other settings. See the
|
||||
transport or driver documentation pages for further details. For example:
|
||||
|
||||
device_auth:
|
||||
- tag: ye_olde_snmp
|
||||
community: public
|
||||
- tag: sshcollector
|
||||
only: 'group:sshcollectordevices'
|
||||
driver: cli
|
||||
method: arpnip_nodes
|
||||
username: foo
|
||||
password: bar
|
||||
- tag: netconf_devices
|
||||
only: 'vendor:juniper'
|
||||
driver: netconf
|
||||
username: oliver
|
||||
password: letmein
|
||||
|
||||
Netdisco caches both the successful SNMPv2 read and write community strings,
|
||||
as well as the C<tag> names if available. This allows for faster operations
|
||||
once a connection has previously been made to a device. Tags are recommended.
|
||||
@@ -806,7 +826,7 @@ Finally, a reminder that multiple SNMPv2 community strings need to be in
|
||||
separate named stanza, as below. However for simple v2 configurations you can
|
||||
revert to the "C<community>" setting, instead:
|
||||
|
||||
snmp_auth:
|
||||
device_auth:
|
||||
- tag: 'default_v2_readonly1'
|
||||
community: 'read1'
|
||||
- tag: 'default_v2_readonly2'
|
||||
@@ -822,7 +842,7 @@ Value: String. Default none.
|
||||
An external program to run to get the community string for a given device.
|
||||
This is useful if, for example, you have you devices already configured in
|
||||
another NMS and you want to use that information instead of configuring
|
||||
C<snmp_auth>.
|
||||
C<device_auth>.
|
||||
|
||||
The strings "C<%IP%>" and "C<%HOST%>" are replaced by the IP address and the
|
||||
hostname (or IP address if no hostname is known) of the system being
|
||||
@@ -836,7 +856,7 @@ The command must return output in the following form:
|
||||
setCommunity=<comma-separated list of write-communities>
|
||||
|
||||
If the community string is not known for the given system, the command should
|
||||
return no output and the community strings configured in C<snmp_auth>,
|
||||
return no output and the community strings configured in C<device_auth>,
|
||||
C<community>, and C<community_rw> will be used instead.
|
||||
|
||||
=head3 C<bulkwalk_off>
|
||||
|
||||
@@ -311,7 +311,7 @@ Every Dancer route handler must have proper role based access control enabled,
|
||||
to prevent unauthorized access to Netdisco's data, or admin features. This is
|
||||
done with the L<Dancer::Plugin::Auth::Extensible> module. It handles both the
|
||||
authentication using Netdisco's database, and then protects each route
|
||||
handler. See L<App::Netdisco::Manual::WritingPlugins> for details.
|
||||
handler. See L<App::Netdisco::Manual::WritingWebPlugins> for details.
|
||||
|
||||
=head2 Templates
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
=head1 NAME
|
||||
|
||||
App::Netdisco::Manual::WritingPlugins - Documentation on Plugins for Developers
|
||||
App::Netdisco::Manual::WritingWebPlugins - Documentation on Web Plugins for Developers
|
||||
|
||||
=head1 Introduction
|
||||
|
||||
@@ -26,7 +26,7 @@ App::Netdisco plugins should load the L<App::Netdisco::Web::Plugin> module.
|
||||
This exports a set of helper subroutines to register the new UI components.
|
||||
Here's the boilerplate code for our example plugin module:
|
||||
|
||||
package App::Netdisco::Web::Plugin::MyNewFeature
|
||||
package App::Netdisco::Web::Plugin::MyNewFeature;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin::DBIC;
|
||||
226
lib/App/Netdisco/Manual/WritingWorkers.pod
Normal file
226
lib/App/Netdisco/Manual/WritingWorkers.pod
Normal file
@@ -0,0 +1,226 @@
|
||||
=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
|
||||
|
||||
312
lib/App/Netdisco/Transport/SNMP.pm
Normal file
312
lib/App/Netdisco/Transport/SNMP.pm
Normal file
@@ -0,0 +1,312 @@
|
||||
package App::Netdisco::Transport::SNMP;
|
||||
|
||||
use Dancer qw/:syntax :script/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
use App::Netdisco::Util::SNMP 'get_communities';
|
||||
use App::Netdisco::Util::Device 'get_device';
|
||||
use App::Netdisco::Util::Permission ':all';
|
||||
|
||||
use SNMP::Info;
|
||||
use Try::Tiny;
|
||||
use Module::Load ();
|
||||
use Path::Class 'dir';
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
|
||||
use base 'Dancer::Object::Singleton';
|
||||
|
||||
=head1 NAME
|
||||
|
||||
App::Netdisco::Transport::SNMP
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Singleton for SNMP connections. Returns cached L<SNMP::Info> instance for a
|
||||
given device IP, or else undef. All methods are class methods, for example:
|
||||
|
||||
App::Netdisco::Transport::SNMP->reader_for( ... );
|
||||
|
||||
=cut
|
||||
|
||||
__PACKAGE__->attributes(qw/ readers writers /);
|
||||
|
||||
sub init {
|
||||
my ( $class, $self ) = @_;
|
||||
$self->readers( {} );
|
||||
$self->writers( {} );
|
||||
return $self;
|
||||
}
|
||||
|
||||
=head1 reader_for( $ip, $useclass? )
|
||||
|
||||
Given an IP address, returns an L<SNMP::Info> instance configured for and
|
||||
connected to that device. The IP can be any on the device, and the management
|
||||
interface will be connected to.
|
||||
|
||||
If the device is known to Netdisco and there is a cached SNMP community
|
||||
string, that community will be tried first, and then other community strings
|
||||
from the application configuration will be tried.
|
||||
|
||||
If C<$useclass> is provided, it will be used as the L<SNMP::Info> device
|
||||
class instead of the class in the Netdisco database.
|
||||
|
||||
Returns C<undef> if the connection fails.
|
||||
|
||||
=cut
|
||||
|
||||
sub reader_for {
|
||||
my ($class, $ip, $useclass) = @_;
|
||||
my $device = get_device($ip) or return undef;
|
||||
my $readers = $class->instance->readers or return undef;
|
||||
return $readers->{$device->ip} if exists $readers->{$device->ip};
|
||||
debug sprintf 'snmp reader cache warm: [%s]', $device->ip;
|
||||
return ($readers->{$device->ip}
|
||||
= _snmp_connect_generic('read', $device, $useclass));
|
||||
}
|
||||
|
||||
=head1 test_connection( $ip )
|
||||
|
||||
Similar to C<reader_for> but will use the literal IP address passed, and does
|
||||
not support specifying the device class. The purpose is to test the SNMP
|
||||
connectivity to the device before a renumber.
|
||||
|
||||
Attempts to have no side effect, however there will be a stored SNMP
|
||||
authentication hint (tag) in the database if the connection is successful.
|
||||
|
||||
Returns C<undef> if the connection fails.
|
||||
|
||||
=cut
|
||||
|
||||
sub test_connection {
|
||||
my ($class, $ip) = @_;
|
||||
my $addr = NetAddr::IP::Lite->new($ip) or return undef;
|
||||
# avoid renumbering to localhost loopbacks
|
||||
return undef if $addr->addr eq '0.0.0.0'
|
||||
or check_acl_no($addr->addr, 'group:__LOCAL_ADDRESSES__');
|
||||
my $device = schema('netdisco')->resultset('Device')
|
||||
->new_result({ ip => $addr->addr }) or return undef;
|
||||
my $readers = $class->instance->readers or return undef;
|
||||
return $readers->{$device->ip} if exists $readers->{$device->ip};
|
||||
debug sprintf 'snmp reader cache warm: [%s]', $device->ip;
|
||||
return ($readers->{$device->ip} = _snmp_connect_generic('read', $device));
|
||||
}
|
||||
|
||||
=head1 writer_for( $ip, $useclass? )
|
||||
|
||||
Same as C<reader_for> but uses the read-write community strings from the
|
||||
application configuration file.
|
||||
|
||||
Returns C<undef> if the connection fails.
|
||||
|
||||
=cut
|
||||
|
||||
sub writer_for {
|
||||
my ($class, $ip, $useclass) = @_;
|
||||
my $device = get_device($ip) or return undef;
|
||||
my $writers = $class->instance->writers or return undef;
|
||||
return $writers->{$device->ip} if exists $writers->{$device->ip};
|
||||
debug sprintf 'snmp writer cache warm: [%s]', $device->ip;
|
||||
return ($writers->{$device->ip}
|
||||
= _snmp_connect_generic('write', $device, $useclass));
|
||||
}
|
||||
|
||||
sub _snmp_connect_generic {
|
||||
my ($mode, $device, $useclass) = @_;
|
||||
$mode ||= 'read';
|
||||
|
||||
my %snmp_args = (
|
||||
AutoSpecify => 0,
|
||||
DestHost => $device->ip,
|
||||
# 0 is falsy. Using || with snmpretries equal to 0 will set retries to 2.
|
||||
# check if the setting is 0. If not, use the default value of 2.
|
||||
Retries => (setting('snmpretries') || setting('snmpretries') == 0 ? 0 : 2),
|
||||
Timeout => (setting('snmptimeout') || 1000000),
|
||||
NonIncreasing => (setting('nonincreasing') || 0),
|
||||
BulkWalk => ((defined setting('bulkwalk_off') && setting('bulkwalk_off'))
|
||||
? 0 : 1),
|
||||
BulkRepeaters => (setting('bulkwalk_repeaters') || 20),
|
||||
MibDirs => [ _build_mibdirs() ],
|
||||
IgnoreNetSNMPConf => 1,
|
||||
Debug => ($ENV{INFO_TRACE} || 0),
|
||||
DebugSNMP => ($ENV{SNMP_TRACE} || 0),
|
||||
);
|
||||
|
||||
# an override for bulkwalk
|
||||
$snmp_args{BulkWalk} = 0 if check_acl_no($device, 'bulkwalk_no');
|
||||
|
||||
# further protect against buggy Net-SNMP, and disable bulkwalk
|
||||
if ($snmp_args{BulkWalk}
|
||||
and ($SNMP::VERSION eq '5.0203' || $SNMP::VERSION eq '5.0301')) {
|
||||
|
||||
warning sprintf
|
||||
"[%s] turning off BulkWalk due to buggy Net-SNMP - please upgrade!",
|
||||
$device->ip;
|
||||
$snmp_args{BulkWalk} = 0;
|
||||
}
|
||||
|
||||
# get the community string(s)
|
||||
my @communities = get_communities($device, $mode);
|
||||
|
||||
# which SNMP versions to try and in what order
|
||||
my @versions =
|
||||
( check_acl_no($device->ip, 'snmpforce_v3') ? (3)
|
||||
: check_acl_no($device->ip, 'snmpforce_v2') ? (2)
|
||||
: check_acl_no($device->ip, 'snmpforce_v1') ? (1)
|
||||
: (reverse (1 .. (setting('snmpver') || 3))) );
|
||||
|
||||
# use existing or new device class
|
||||
my @classes = ($useclass || 'SNMP::Info');
|
||||
if ($device->snmp_class and not $useclass) {
|
||||
unshift @classes, $device->snmp_class;
|
||||
}
|
||||
|
||||
my $info = undef;
|
||||
COMMUNITY: foreach my $comm (@communities) {
|
||||
next unless $comm;
|
||||
|
||||
VERSION: foreach my $ver (@versions) {
|
||||
next unless $ver;
|
||||
|
||||
next if $ver eq 3 and exists $comm->{community};
|
||||
next if $ver ne 3 and !exists $comm->{community};
|
||||
|
||||
CLASS: foreach my $class (@classes) {
|
||||
next unless $class;
|
||||
|
||||
my %local_args = (%snmp_args, Version => $ver);
|
||||
$info = _try_connect($device, $class, $comm, $mode, \%local_args,
|
||||
($useclass ? 0 : 1) );
|
||||
last COMMUNITY if $info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub _try_connect {
|
||||
my ($device, $class, $comm, $mode, $snmp_args, $reclass) = @_;
|
||||
my %comm_args = _mk_info_commargs($comm);
|
||||
my $debug_comm = '<hidden>';
|
||||
if ($ENV{SHOW_COMMUNITY}) {
|
||||
$debug_comm = ($comm->{community} ||
|
||||
(sprintf 'v3:%s:%s/%s', ($comm->{user},
|
||||
($comm->{auth}->{proto} || 'noAuth'),
|
||||
($comm->{priv}->{proto} || 'noPriv'))) );
|
||||
}
|
||||
my $info = undef;
|
||||
|
||||
try {
|
||||
debug
|
||||
sprintf '[%s] try_connect with ver: %s, class: %s, comm: %s',
|
||||
$snmp_args->{DestHost}, $snmp_args->{Version}, $class, $debug_comm;
|
||||
Module::Load::load $class;
|
||||
|
||||
$info = $class->new(%$snmp_args, %comm_args) or return;
|
||||
$info = ($mode eq 'read' ? _try_read($info, $device, $comm)
|
||||
: _try_write($info, $device, $comm));
|
||||
|
||||
# first time a device is discovered, re-instantiate into specific class
|
||||
if ($reclass and $info and $info->device_type ne $class) {
|
||||
$class = $info->device_type;
|
||||
debug
|
||||
sprintf '[%s] try_connect with ver: %s, new class: %s, comm: %s',
|
||||
$snmp_args->{DestHost}, $snmp_args->{Version}, $class, $debug_comm;
|
||||
|
||||
Module::Load::load $class;
|
||||
$info = $class->new(%$snmp_args, %comm_args);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
debug $_;
|
||||
};
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub _try_read {
|
||||
my ($info, $device, $comm) = @_;
|
||||
|
||||
return undef unless (
|
||||
(not defined $info->error)
|
||||
and defined $info->uptime
|
||||
and ($info->layers or $info->description)
|
||||
and $info->class
|
||||
);
|
||||
|
||||
$device->in_storage
|
||||
? $device->update({snmp_ver => $info->snmp_ver})
|
||||
: $device->set_column(snmp_ver => $info->snmp_ver);
|
||||
|
||||
if ($comm->{community}) {
|
||||
$device->in_storage
|
||||
? $device->update({snmp_comm => $comm->{community}})
|
||||
: $device->set_column(snmp_comm => $comm->{community});
|
||||
}
|
||||
|
||||
# regardless of device in storage, save the hint
|
||||
$device->update_or_create_related('community',
|
||||
{snmp_auth_tag_read => $comm->{tag}}) if $comm->{tag};
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub _try_write {
|
||||
my ($info, $device, $comm) = @_;
|
||||
|
||||
my $loc = $info->load_location;
|
||||
$info->set_location($loc) or return undef;
|
||||
return undef unless ($loc eq $info->load_location);
|
||||
|
||||
$device->in_storage
|
||||
? $device->update({snmp_ver => $info->snmp_ver})
|
||||
: $device->set_column(snmp_ver => $info->snmp_ver);
|
||||
|
||||
# one of these two cols must be set
|
||||
$device->update_or_create_related('community', {
|
||||
($comm->{tag} ? (snmp_auth_tag_write => $comm->{tag}) : ()),
|
||||
($comm->{community} ? (snmp_comm_rw => $comm->{community}) : ()),
|
||||
});
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub _mk_info_commargs {
|
||||
my $comm = shift;
|
||||
return () unless ref {} eq ref $comm and scalar keys %$comm;
|
||||
|
||||
return (Community => $comm->{community})
|
||||
if exists $comm->{community};
|
||||
|
||||
my $seclevel =
|
||||
(exists $comm->{auth} ?
|
||||
(exists $comm->{priv} ? 'authPriv' : 'authNoPriv' )
|
||||
: 'noAuthNoPriv');
|
||||
|
||||
return (
|
||||
SecName => $comm->{user},
|
||||
SecLevel => $seclevel,
|
||||
( exists $comm->{auth} ? (
|
||||
AuthProto => uc ($comm->{auth}->{proto} || 'MD5'),
|
||||
AuthPass => ($comm->{auth}->{pass} || ''),
|
||||
( exists $comm->{priv} ? (
|
||||
PrivProto => uc ($comm->{priv}->{proto} || 'DES'),
|
||||
PrivPass => ($comm->{priv}->{pass} || ''),
|
||||
) : ()),
|
||||
) : ()),
|
||||
);
|
||||
}
|
||||
|
||||
sub _build_mibdirs {
|
||||
my $home = (setting('mibhome') || dir(($ENV{NETDISCO_HOME} || $ENV{HOME}), 'netdisco-mibs'));
|
||||
return map { dir($home, $_)->stringify }
|
||||
@{ setting('mibdirs') || _get_mibdirs_content($home) };
|
||||
}
|
||||
|
||||
sub _get_mibdirs_content {
|
||||
my $home = shift;
|
||||
my @list = map {s|$home/||; $_} grep {m/[a-z0-9]/} grep {-d} glob("$home/*");
|
||||
return \@list;
|
||||
}
|
||||
|
||||
true;
|
||||
@@ -192,12 +192,11 @@ sub is_discoverable_now {
|
||||
my ($ip, $remote_type) = @_;
|
||||
my $device = get_device($ip) or return 0;
|
||||
|
||||
if ($device->in_storage) {
|
||||
if ($device->since_last_discover and setting('discover_min_age')
|
||||
and $device->since_last_discover < setting('discover_min_age')) {
|
||||
if ($device->in_storage
|
||||
and $device->since_last_discover and setting('discover_min_age')
|
||||
and $device->since_last_discover < setting('discover_min_age')) {
|
||||
|
||||
return _bail_msg("is_discoverable: time since last discover less than discover_min_age");
|
||||
}
|
||||
return _bail_msg("is_discoverable: time since last discover less than discover_min_age");
|
||||
}
|
||||
|
||||
return is_discoverable(@_);
|
||||
@@ -242,10 +241,8 @@ sub is_arpnipable_now {
|
||||
my ($ip) = @_;
|
||||
my $device = get_device($ip) or return 0;
|
||||
|
||||
return _bail_msg("is_arpnipable: cannot arpnip an undiscovered device")
|
||||
if not $device->in_storage;
|
||||
|
||||
if ($device->since_last_arpnip and setting('arpnip_min_age')
|
||||
if ($device->in_storage
|
||||
and $device->since_last_arpnip and setting('arpnip_min_age')
|
||||
and $device->since_last_arpnip < setting('arpnip_min_age')) {
|
||||
|
||||
return _bail_msg("is_arpnipable: time since last arpnip less than arpnip_min_age");
|
||||
@@ -293,10 +290,8 @@ sub is_macsuckable_now {
|
||||
my ($ip) = @_;
|
||||
my $device = get_device($ip) or return 0;
|
||||
|
||||
return _bail_msg("is_macsuckable: cannot macsuck an undiscovered device")
|
||||
if not $device->in_storage;
|
||||
|
||||
if ($device->since_last_macsuck and setting('macsuck_min_age')
|
||||
if ($device->in_storage
|
||||
and $device->since_last_macsuck and setting('macsuck_min_age')
|
||||
and $device->since_last_macsuck < setting('macsuck_min_age')) {
|
||||
|
||||
return _bail_msg("is_macsuckable: time since last macsuck less than macsuck_min_age");
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package App::Netdisco::Util::Backend;
|
||||
package App::Netdisco::Util::MCE;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
@@ -1,10 +1,9 @@
|
||||
package App::Netdisco::Core::Nbtstat;
|
||||
package App::Netdisco::Util::Nbtstat;
|
||||
|
||||
use Dancer qw/:syntax :script/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
use App::Netdisco::Util::Node 'check_mac';
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
use App::Netdisco::AnyEvent::Nbtstat;
|
||||
use Encode;
|
||||
|
||||
@@ -15,7 +14,7 @@ our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||
|
||||
=head1 NAME
|
||||
|
||||
App::Netdisco::Core::Nbtstat
|
||||
App::Netdisco::Util::Nbtstat
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
@@ -130,12 +129,12 @@ sub _filter_nbname {
|
||||
$mac = $node_ip->mac;
|
||||
}
|
||||
|
||||
$hash_ref->{'ip'} = $ip;
|
||||
$hash_ref->{'mac'} = $mac;
|
||||
$hash_ref->{'nbname'} = Encode::decode('UTF-8', $nbname);
|
||||
$hash_ref->{'domain'} = Encode::decode('UTF-8', $domain);
|
||||
$hash_ref->{'server'} = $server;
|
||||
$hash_ref->{'nbuser'} = Encode::decode('UTF-8', $nbuser);
|
||||
$hash_ref->{'ip'} = $ip;
|
||||
$hash_ref->{'mac'} = $mac;
|
||||
$hash_ref->{'nbname'} = Encode::decode('UTF-8', $nbname);
|
||||
$hash_ref->{'domain'} = Encode::decode('UTF-8', $domain);
|
||||
$hash_ref->{'server'} = $server;
|
||||
$hash_ref->{'nbuser'} = Encode::decode('UTF-8', $nbuser);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -1,18 +1,13 @@
|
||||
package App::Netdisco::Util::SNMP;
|
||||
|
||||
use Dancer qw/:syntax :script/;
|
||||
use App::Netdisco::Util::Device 'get_device';
|
||||
use App::Netdisco::Util::DNS 'hostname_from_ip';
|
||||
use App::Netdisco::Util::Permission ':all';
|
||||
|
||||
use SNMP::Info;
|
||||
use Try::Tiny;
|
||||
use Module::Load ();
|
||||
use Path::Class 'dir';
|
||||
|
||||
use base 'Exporter';
|
||||
our @EXPORT = ();
|
||||
our @EXPORT_OK = qw/
|
||||
snmp_connect snmp_connect_rw snmp_comm_reindex
|
||||
fixup_device_auth get_communities snmp_comm_reindex
|
||||
/;
|
||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||
|
||||
@@ -22,337 +17,139 @@ App::Netdisco::Util::SNMP
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
A set of helper subroutines to support parts of the Netdisco application.
|
||||
Helper functions for L<SNMP::Info> instances.
|
||||
|
||||
There are no default exports, however the C<:all> tag will export all
|
||||
subroutines.
|
||||
|
||||
=head1 EXPORT_OK
|
||||
|
||||
=head2 snmp_connect( $ip )
|
||||
=head2 fixup_device_auth
|
||||
|
||||
Given an IP address, returns an L<SNMP::Info> instance configured for and
|
||||
connected to that device. The IP can be any on the device, and the management
|
||||
interface will be connected to.
|
||||
|
||||
If the device is known to Netdisco and there is a cached SNMP community
|
||||
string, this will be tried first, and then other community string(s) from the
|
||||
application configuration will be tried.
|
||||
|
||||
Returns C<undef> if the connection fails.
|
||||
Rebuilds the C<device_auth> config with missing defaults and other fixups for
|
||||
config changes over time. Returns a list which can replace C<device_auth>.
|
||||
|
||||
=cut
|
||||
|
||||
sub snmp_connect { _snmp_connect_generic('read', @_) }
|
||||
|
||||
=head2 snmp_connect_rw( $ip )
|
||||
|
||||
Same as C<snmp_connect> but uses the read-write community string(s) from the
|
||||
application configuration file.
|
||||
|
||||
Returns C<undef> if the connection fails.
|
||||
|
||||
=cut
|
||||
|
||||
sub snmp_connect_rw { _snmp_connect_generic('write', @_) }
|
||||
|
||||
sub _snmp_connect_generic {
|
||||
my ($mode, $ip, $useclass) = @_;
|
||||
$mode ||= 'read';
|
||||
|
||||
# get device details from db
|
||||
my $device = get_device($ip);
|
||||
|
||||
my %snmp_args = (
|
||||
AutoSpecify => 0,
|
||||
DestHost => $device->ip,
|
||||
# 0 is falsy. Using || with snmpretries equal to 0 will set retries to 2.
|
||||
# check if the setting is 0. If not, use the default value of 2.
|
||||
Retries => (setting('snmpretries') || setting('snmpretries') == 0 ? 0 : 2),
|
||||
Timeout => (setting('snmptimeout') || 1000000),
|
||||
NonIncreasing => (setting('nonincreasing') || 0),
|
||||
BulkWalk => ((defined setting('bulkwalk_off') && setting('bulkwalk_off'))
|
||||
? 0 : 1),
|
||||
BulkRepeaters => (setting('bulkwalk_repeaters') || 20),
|
||||
MibDirs => [ _build_mibdirs() ],
|
||||
IgnoreNetSNMPConf => 1,
|
||||
Debug => ($ENV{INFO_TRACE} || 0),
|
||||
DebugSNMP => ($ENV{SNMP_TRACE} || 0),
|
||||
);
|
||||
|
||||
# an override for bulkwalk
|
||||
$snmp_args{BulkWalk} = 0 if check_acl_no($device, 'bulkwalk_no');
|
||||
|
||||
# further protect against buggy Net-SNMP, and disable bulkwalk
|
||||
if ($snmp_args{BulkWalk}
|
||||
and ($SNMP::VERSION eq '5.0203' || $SNMP::VERSION eq '5.0301')) {
|
||||
|
||||
warning sprintf
|
||||
"[%s] turning off BulkWalk due to buggy Net-SNMP - please upgrade!",
|
||||
$device->ip;
|
||||
$snmp_args{BulkWalk} = 0;
|
||||
}
|
||||
|
||||
# get the community string(s)
|
||||
my @communities = _build_communities($device, $mode);
|
||||
|
||||
# which SNMP versions to try and in what order
|
||||
my @versions =
|
||||
( check_acl_no($device->ip, 'snmpforce_v3') ? (3)
|
||||
: check_acl_no($device->ip, 'snmpforce_v2') ? (2)
|
||||
: check_acl_no($device->ip, 'snmpforce_v1') ? (1)
|
||||
: (reverse (1 .. (setting('snmpver') || 3))) );
|
||||
|
||||
# use existing or new device class
|
||||
my @classes = ($useclass || 'SNMP::Info');
|
||||
if ($device->snmp_class and not $useclass) {
|
||||
unshift @classes, $device->snmp_class;
|
||||
}
|
||||
|
||||
my $info = undef;
|
||||
COMMUNITY: foreach my $comm (@communities) {
|
||||
next unless $comm;
|
||||
|
||||
VERSION: foreach my $ver (@versions) {
|
||||
next unless $ver;
|
||||
|
||||
next if $ver eq 3 and exists $comm->{community};
|
||||
next if $ver ne 3 and !exists $comm->{community};
|
||||
|
||||
CLASS: foreach my $class (@classes) {
|
||||
next unless $class;
|
||||
|
||||
my %local_args = (%snmp_args, Version => $ver);
|
||||
$info = _try_connect($device, $class, $comm, $mode, \%local_args,
|
||||
($useclass ? 0 : 1) );
|
||||
last COMMUNITY if $info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub _try_connect {
|
||||
my ($device, $class, $comm, $mode, $snmp_args, $reclass) = @_;
|
||||
my %comm_args = _mk_info_commargs($comm);
|
||||
my $debug_comm = '<hidden>';
|
||||
if ($ENV{SHOW_COMMUNITY}) {
|
||||
$debug_comm = ($comm->{community} ||
|
||||
(sprintf 'v3:%s:%s/%s', ($comm->{user},
|
||||
($comm->{auth}->{proto} || 'noAuth'),
|
||||
($comm->{priv}->{proto} || 'noPriv'))) );
|
||||
}
|
||||
my $info = undef;
|
||||
|
||||
try {
|
||||
debug
|
||||
sprintf '[%s] try_connect with ver: %s, class: %s, comm: %s',
|
||||
$snmp_args->{DestHost}, $snmp_args->{Version}, $class, $debug_comm;
|
||||
Module::Load::load $class;
|
||||
|
||||
$info = $class->new(%$snmp_args, %comm_args) or return;
|
||||
$info = ($mode eq 'read' ? _try_read($info, $device, $comm)
|
||||
: _try_write($info, $device, $comm));
|
||||
|
||||
# first time a device is discovered, re-instantiate into specific class
|
||||
if ($reclass and $info and $info->device_type ne $class) {
|
||||
$class = $info->device_type;
|
||||
debug
|
||||
sprintf '[%s] try_connect with ver: %s, new class: %s, comm: %s',
|
||||
$snmp_args->{DestHost}, $snmp_args->{Version}, $class, $debug_comm;
|
||||
|
||||
Module::Load::load $class;
|
||||
$info = $class->new(%$snmp_args, %comm_args);
|
||||
}
|
||||
}
|
||||
catch {
|
||||
debug $_;
|
||||
};
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub _try_read {
|
||||
my ($info, $device, $comm) = @_;
|
||||
|
||||
return undef unless (
|
||||
(not defined $info->error)
|
||||
and defined $info->uptime
|
||||
and ($info->layers or $info->description)
|
||||
and $info->class
|
||||
);
|
||||
|
||||
$device->in_storage
|
||||
? $device->update({snmp_ver => $info->snmp_ver})
|
||||
: $device->set_column(snmp_ver => $info->snmp_ver);
|
||||
|
||||
if ($comm->{community}) {
|
||||
$device->in_storage
|
||||
? $device->update({snmp_comm => $comm->{community}})
|
||||
: $device->set_column(snmp_comm => $comm->{community});
|
||||
}
|
||||
|
||||
# regardless of device in storage, save the hint
|
||||
$device->update_or_create_related('community',
|
||||
{snmp_auth_tag_read => $comm->{tag}}) if $comm->{tag};
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub _try_write {
|
||||
my ($info, $device, $comm) = @_;
|
||||
|
||||
my $loc = $info->load_location;
|
||||
$info->set_location($loc) or return undef;
|
||||
return undef unless ($loc eq $info->load_location);
|
||||
|
||||
$device->in_storage
|
||||
? $device->update({snmp_ver => $info->snmp_ver})
|
||||
: $device->set_column(snmp_ver => $info->snmp_ver);
|
||||
|
||||
# one of these two cols must be set
|
||||
$device->update_or_create_related('community', {
|
||||
($comm->{tag} ? (snmp_auth_tag_write => $comm->{tag}) : ()),
|
||||
($comm->{community} ? (snmp_comm_rw => $comm->{community}) : ()),
|
||||
});
|
||||
|
||||
return $info;
|
||||
}
|
||||
|
||||
sub _mk_info_commargs {
|
||||
my $comm = shift;
|
||||
return () unless ref {} eq ref $comm and scalar keys %$comm;
|
||||
|
||||
return (Community => $comm->{community})
|
||||
if exists $comm->{community};
|
||||
|
||||
my $seclevel =
|
||||
(exists $comm->{auth} ?
|
||||
(exists $comm->{priv} ? 'authPriv' : 'authNoPriv' )
|
||||
: 'noAuthNoPriv');
|
||||
|
||||
return (
|
||||
SecName => $comm->{user},
|
||||
SecLevel => $seclevel,
|
||||
( exists $comm->{auth} ? (
|
||||
AuthProto => uc ($comm->{auth}->{proto} || 'MD5'),
|
||||
AuthPass => ($comm->{auth}->{pass} || ''),
|
||||
( exists $comm->{priv} ? (
|
||||
PrivProto => uc ($comm->{priv}->{proto} || 'DES'),
|
||||
PrivPass => ($comm->{priv}->{pass} || ''),
|
||||
) : ()),
|
||||
) : ()),
|
||||
);
|
||||
}
|
||||
|
||||
sub _build_mibdirs {
|
||||
my $home = (setting('mibhome') || dir(($ENV{NETDISCO_HOME} || $ENV{HOME}), 'netdisco-mibs'));
|
||||
return map { dir($home, $_)->stringify }
|
||||
@{ setting('mibdirs') || _get_mibdirs_content($home) };
|
||||
}
|
||||
|
||||
sub _get_mibdirs_content {
|
||||
my $home = shift;
|
||||
# warning 'Netdisco SNMP work will be slow - loading ALL MIBs. Consider setting mibdirs.';
|
||||
my @list = map {s|$home/||; $_} grep {-d} glob("$home/*");
|
||||
return \@list;
|
||||
}
|
||||
|
||||
sub _build_communities {
|
||||
my ($device, $mode) = @_;
|
||||
$mode ||= 'read';
|
||||
my $seen_tags = {}; # for cleaning community table
|
||||
|
||||
my $config = (setting('snmp_auth') || []);
|
||||
my $tag_name = 'snmp_auth_tag_'. $mode;
|
||||
my $stored_tag = eval { $device->community->$tag_name };
|
||||
my $snmp_comm_rw = eval { $device->community->snmp_comm_rw };
|
||||
my @communities = ();
|
||||
|
||||
# try last-known-good read
|
||||
push @communities, {read => 1, community => $device->snmp_comm}
|
||||
if defined $device->snmp_comm and $mode eq 'read';
|
||||
|
||||
# try last-known-good write
|
||||
push @communities, {write => 1, community => $snmp_comm_rw}
|
||||
if $snmp_comm_rw and $mode eq 'write';
|
||||
sub fixup_device_auth {
|
||||
my $config = (setting('device_auth') || setting('snmp_auth') || []);
|
||||
my @new_stanzas = ();
|
||||
|
||||
# new style snmp config
|
||||
foreach my $stanza (@$config) {
|
||||
# user tagged
|
||||
my $tag = '';
|
||||
if (1 == scalar keys %$stanza) {
|
||||
$tag = (keys %$stanza)[0];
|
||||
$stanza = $stanza->{$tag};
|
||||
# user tagged
|
||||
my $tag = '';
|
||||
if (1 == scalar keys %$stanza) {
|
||||
$tag = (keys %$stanza)[0];
|
||||
$stanza = $stanza->{$tag};
|
||||
|
||||
# corner case: untagged lone community
|
||||
if ($tag eq 'community') {
|
||||
$tag = $stanza;
|
||||
$stanza = {community => $tag};
|
||||
}
|
||||
# corner case: untagged lone community
|
||||
if ($tag eq 'community') {
|
||||
$tag = $stanza;
|
||||
$stanza = {community => $tag};
|
||||
}
|
||||
}
|
||||
|
||||
# defaults
|
||||
$stanza->{tag} ||= $tag;
|
||||
++$seen_tags->{ $stanza->{tag} };
|
||||
$stanza->{read} = 1 if !exists $stanza->{read};
|
||||
$stanza->{no} ||= [];
|
||||
$stanza->{only} ||= ['any'];
|
||||
$stanza->{no} = [$stanza->{no}] if ref '' eq ref $stanza->{no};
|
||||
$stanza->{only} = [$stanza->{only}] if ref '' eq ref $stanza->{only};
|
||||
# defaults
|
||||
$stanza->{tag} ||= $tag;
|
||||
$stanza->{read} = 1 if !exists $stanza->{read};
|
||||
$stanza->{no} ||= [];
|
||||
$stanza->{only} ||= ['any'];
|
||||
|
||||
die "error: config: snmpv2 community in snmp_auth must be single item, not list\n"
|
||||
if ref $stanza->{community};
|
||||
die "error: config: snmpv2 community in device_auth must be single item, not list\n"
|
||||
if ref $stanza->{community};
|
||||
|
||||
die "error: config: snmpv3 stanza in snmp_auth must have a tag\n"
|
||||
if not $stanza->{tag}
|
||||
and !exists $stanza->{community};
|
||||
die "error: config: stanza in device_auth must have a tag\n"
|
||||
if not $stanza->{tag} and exists $stanza->{user};
|
||||
|
||||
if ($stanza->{$mode} and check_acl_only($device, $stanza->{only})
|
||||
and not check_acl_no($device, $stanza->{no})) {
|
||||
if ($device->in_storage and
|
||||
$stored_tag and $stored_tag eq $stanza->{tag}) {
|
||||
# last known-good by tag
|
||||
unshift @communities, $stanza
|
||||
}
|
||||
else {
|
||||
push @communities, $stanza
|
||||
}
|
||||
}
|
||||
push @new_stanzas, $stanza
|
||||
}
|
||||
|
||||
# clean the community table of obsolete tags
|
||||
if ($stored_tag and !exists $seen_tags->{ $stored_tag }) {
|
||||
eval { $device->community->update({$tag_name => undef}) };
|
||||
# legacy config
|
||||
# note: read strings tried before write
|
||||
# note: read-write is no longer used for read operations
|
||||
|
||||
push @new_stanzas, map {{
|
||||
read => 1, write => 0,
|
||||
no => [], only => ['any'],
|
||||
community => $_,
|
||||
}} @{setting('community') || []};
|
||||
|
||||
push @new_stanzas, map {{
|
||||
write => 1, read => 0,
|
||||
no => [], only => ['any'],
|
||||
community => $_,
|
||||
}} @{setting('community_rw') || []};
|
||||
|
||||
foreach my $stanza (@new_stanzas) {
|
||||
$stanza->{driver} ||= 'snmp'
|
||||
if exists $stanza->{community}
|
||||
or exists $stanza->{user};
|
||||
}
|
||||
|
||||
# legacy config (note: read strings tried before write)
|
||||
if ($mode eq 'read') {
|
||||
push @communities, map {{
|
||||
read => 1,
|
||||
community => $_,
|
||||
}} @{setting('community') || []};
|
||||
}
|
||||
else {
|
||||
push @communities, map {{
|
||||
write => 1,
|
||||
community => $_,
|
||||
}} @{setting('community_rw') || []};
|
||||
}
|
||||
return @new_stanzas;
|
||||
}
|
||||
|
||||
# but first of all, use external command if configured
|
||||
unshift @communities, _get_external_community($device, $mode)
|
||||
=head2 get_communities( $device, $mode )
|
||||
|
||||
Takes the current C<device_auth> setting and pushes onto the front of the list
|
||||
the last known good SNMP settings used for this mode (C<read> or C<write>).
|
||||
|
||||
=cut
|
||||
|
||||
sub get_communities {
|
||||
my ($device, $mode) = @_;
|
||||
$mode ||= 'read';
|
||||
|
||||
my $seen_tags = {}; # for cleaning community table
|
||||
my $config = (setting('device_auth') || []);
|
||||
my @communities = ();
|
||||
|
||||
# first of all, use external command if configured
|
||||
push @communities, _get_external_community($device, $mode)
|
||||
if setting('get_community') and length setting('get_community');
|
||||
|
||||
return @communities;
|
||||
# last known-good by tag
|
||||
my $tag_name = 'snmp_auth_tag_'. $mode;
|
||||
my $stored_tag = eval { $device->community->$tag_name };
|
||||
|
||||
if ($device->in_storage and $stored_tag) {
|
||||
foreach my $stanza (@$config) {
|
||||
if ($stanza->{tag} and $stored_tag eq $stanza->{tag}) {
|
||||
push @communities, {%$stanza, only => [$device->ip]};
|
||||
last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# try last-known-good v2 read
|
||||
push @communities, {
|
||||
read => 1, write => 0, driver => 'snmp',
|
||||
only => [$device->ip],
|
||||
community => $device->snmp_comm,
|
||||
} if defined $device->snmp_comm and $mode eq 'read';
|
||||
|
||||
# try last-known-good v2 write
|
||||
my $snmp_comm_rw = eval { $device->community->snmp_comm_rw };
|
||||
push @communities, {
|
||||
write => 1, read => 0, driver => 'snmp',
|
||||
only => [$device->ip],
|
||||
community => $snmp_comm_rw,
|
||||
} if $snmp_comm_rw and $mode eq 'write';
|
||||
|
||||
# clean the community table of obsolete tags
|
||||
eval { $device->community->update({$tag_name => undef}) }
|
||||
if $device->in_storage
|
||||
and (not $stored_tag or !exists $seen_tags->{ $stored_tag });
|
||||
|
||||
return ( @communities, @$config );
|
||||
}
|
||||
|
||||
sub _get_external_community {
|
||||
my ($device, $mode) = @_;
|
||||
my $cmd = setting('get_community');
|
||||
my $ip = $device->ip;
|
||||
my $host = $device->dns || $ip;
|
||||
my $host = ($device->dns || hostname_from_ip($ip) || $ip);
|
||||
|
||||
if (defined $cmd and length $cmd) {
|
||||
# replace variables
|
||||
@@ -368,6 +165,7 @@ sub _get_external_community {
|
||||
if (length $1 and $mode eq 'read') {
|
||||
return map {{
|
||||
read => 1,
|
||||
only => [$device->ip],
|
||||
community => $_,
|
||||
}} split(m/\s*,\s*/,$1);
|
||||
}
|
||||
@@ -376,6 +174,7 @@ sub _get_external_community {
|
||||
if (length $1 and $mode eq 'write') {
|
||||
return map {{
|
||||
write => 1,
|
||||
only => [$device->ip],
|
||||
community => $_,
|
||||
}} split(m/\s*,\s*/,$1);
|
||||
}
|
||||
@@ -391,6 +190,9 @@ sub _get_external_community {
|
||||
Takes an established L<SNMP::Info> instance and makes a fresh connection using
|
||||
community indexing, with the given C<$vlan> ID. Works for all SNMP versions.
|
||||
|
||||
Passing VLAN "C<0>" (zero) will reset the indexing to the basic v2 community
|
||||
or v3 empty context.
|
||||
|
||||
=cut
|
||||
|
||||
sub snmp_comm_reindex {
|
||||
@@ -399,7 +201,8 @@ sub snmp_comm_reindex {
|
||||
|
||||
if ($ver == 3) {
|
||||
my $prefix = '';
|
||||
my @comms = _build_communities($device, 'read');
|
||||
my @comms = get_communities($device, 'read');
|
||||
# find a context prefix configured by the user
|
||||
foreach my $c (@comms) {
|
||||
next unless $c->{tag}
|
||||
and $c->{tag} eq (eval { $device->community->snmp_auth_tag_read } || '');
|
||||
@@ -410,15 +213,17 @@ sub snmp_comm_reindex {
|
||||
debug
|
||||
sprintf '[%s] reindexing to "%s%s" (ver: %s, class: %s)',
|
||||
$device->ip, $prefix, $vlan, $ver, $snmp->class;
|
||||
$snmp->update(Context => ($prefix . $vlan));
|
||||
$vlan ? $snmp->update(Context => ($prefix . $vlan))
|
||||
: $snmp->update(Context => '');
|
||||
}
|
||||
else {
|
||||
my $comm = $snmp->snmp_comm;
|
||||
|
||||
debug sprintf '[%s] reindexing to vlan %s (ver: %s, class: %s)',
|
||||
$device->ip, $vlan, $ver, $snmp->class;
|
||||
$snmp->update(Community => $comm . '@' . $vlan);
|
||||
$vlan ? $snmp->update(Community => $comm . '@' . $vlan)
|
||||
: $snmp->update(Community => $comm);
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
true;
|
||||
|
||||
@@ -34,7 +34,7 @@ sub _load_web_plugins {
|
||||
if $plugin !~ m/^\+/;
|
||||
$plugin =~ s/^\+//;
|
||||
|
||||
debug "loading Netdisco plugin $plugin";
|
||||
$ENV{PLUGIN_LOAD_DEBUG} && debug "loading web plugin $plugin";
|
||||
Module::Load::load $plugin;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +233,8 @@ Admin Menu function (job control, manual topology, pseudo devices)
|
||||
=back
|
||||
|
||||
This document explains how to configure which plugins are loaded. See
|
||||
L<App::Netdisco::Manual::WritingPlugins> if you want to develop new plugins.
|
||||
L<App::Netdisco::Manual::WritingWebPlugins> if you want to develop new
|
||||
plugins.
|
||||
|
||||
=head1 Application Configuration
|
||||
|
||||
|
||||
55
lib/App/Netdisco/Worker/Loader.pm
Normal file
55
lib/App/Netdisco/Worker/Loader.pm
Normal file
@@ -0,0 +1,55 @@
|
||||
package App::Netdisco::Worker::Loader;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Module::Load ();
|
||||
use Dancer qw/:moose :syntax/;
|
||||
|
||||
use Moo::Role;
|
||||
use namespace::clean;
|
||||
|
||||
has [qw/workers_check
|
||||
workers_early
|
||||
workers_main
|
||||
workers_user/] => ( is => 'rw' );
|
||||
|
||||
sub load_workers {
|
||||
my $self = shift;
|
||||
my $action = $self->job->action or die "missing action\n";
|
||||
|
||||
my @core_plugins = @{ setting('worker_plugins') || [] };
|
||||
my @user_plugins = @{ setting('extra_worker_plugins') || [] };
|
||||
|
||||
# load worker plugins for our action
|
||||
foreach my $plugin (@user_plugins, @core_plugins) {
|
||||
$plugin =~ s/^X::/+App::NetdiscoX::Worker::Plugin::/;
|
||||
$plugin = 'App::Netdisco::Worker::Plugin::'. $plugin
|
||||
if $plugin !~ m/^\+/;
|
||||
$plugin =~ s/^\+//;
|
||||
|
||||
next unless $plugin =~ m/::Plugin::${action}(?:::|$)/i;
|
||||
$ENV{PLUGIN_LOAD_DEBUG} && debug "loading worker plugin $plugin";
|
||||
Module::Load::load $plugin;
|
||||
}
|
||||
|
||||
# now vars->{workers} is populated, we set the dispatch order
|
||||
my $workers = vars->{'workers'}->{$action} || {};
|
||||
#use DDP; p vars->{'workers'};
|
||||
|
||||
foreach my $phase (qw/check early main user/) {
|
||||
my $pname = "workers_${phase}";
|
||||
my @wset = ();
|
||||
|
||||
foreach my $namespace (sort keys %{ $workers->{$phase} }) {
|
||||
foreach my $priority (sort {$b <=> $a}
|
||||
keys %{ $workers->{$phase}->{$namespace} }) {
|
||||
push @wset, @{ $workers->{$phase}->{$namespace}->{$priority} };
|
||||
}
|
||||
}
|
||||
|
||||
$self->$pname( \@wset );
|
||||
}
|
||||
}
|
||||
|
||||
true;
|
||||
164
lib/App/Netdisco/Worker/Plugin.pm
Normal file
164
lib/App/Netdisco/Worker/Plugin.pm
Normal file
@@ -0,0 +1,164 @@
|
||||
package App::Netdisco::Worker::Plugin;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use Dancer::Plugin;
|
||||
|
||||
use App::Netdisco::Util::Permission qw/check_acl_no check_acl_only/;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
use Scope::Guard 'guard';
|
||||
|
||||
register 'register_worker' => sub {
|
||||
my ($self, $first, $second) = plugin_args(@_);
|
||||
|
||||
my $workerconf = (ref $first eq 'HASH' ? $first : {});
|
||||
my $code = (ref $first eq 'CODE' ? $first : $second);
|
||||
return error "bad param to register_worker"
|
||||
unless ((ref sub {} eq ref $code) and (ref {} eq ref $workerconf));
|
||||
|
||||
my $package = (caller)[0];
|
||||
if ($package =~ m/Plugin::(\w+)(?:::(\w+))?/) {
|
||||
$workerconf->{action} = lc($1);
|
||||
$workerconf->{namespace} = lc($2) if $2;
|
||||
}
|
||||
return error "failed to parse action in '$package'"
|
||||
unless $workerconf->{action};
|
||||
|
||||
$workerconf->{phase} ||= 'user';
|
||||
$workerconf->{namespace} ||= '_base_';
|
||||
$workerconf->{priority} ||= (exists $workerconf->{driver}
|
||||
? (setting('driver_priority')->{$workerconf->{driver}} || 0) : 0);
|
||||
|
||||
my $worker = sub {
|
||||
my $job = shift or die 'missing job param';
|
||||
# use DDP; p $workerconf;
|
||||
|
||||
# update job's record of namespace and priority
|
||||
# check to see if this namespace has already passed at higher priority
|
||||
return if $job->namespace_passed($workerconf);
|
||||
|
||||
my @newuserconf = ();
|
||||
my @userconf = @{ setting('device_auth') || [] };
|
||||
|
||||
# worker might be vendor/platform specific
|
||||
if (ref $job->device) {
|
||||
my $no = (exists $workerconf->{no} ? $workerconf->{no} : undef);
|
||||
my $only = (exists $workerconf->{only} ? $workerconf->{only} : undef);
|
||||
|
||||
return $job->add_status( Status->noop('worker not applicable to this device') )
|
||||
if ($no and check_acl_no($job->device, $no))
|
||||
or ($only and not check_acl_only($job->device, $only));
|
||||
|
||||
# reduce device_auth by driver and action filters
|
||||
foreach my $stanza (@userconf) {
|
||||
next if exists $stanza->{driver} and exists $workerconf->{driver}
|
||||
and (($stanza->{driver} || '') ne ($workerconf->{driver} || ''));
|
||||
|
||||
# filter here rather than in Runner as runner does not know namespace
|
||||
next if exists $stanza->{action}
|
||||
and not _find_matchaction($workerconf, lc($stanza->{action}));
|
||||
|
||||
push @newuserconf, $stanza;
|
||||
}
|
||||
|
||||
# per-device action but no device creds available
|
||||
return $job->add_status( Status->noop('worker driver or action not applicable') )
|
||||
if 0 == scalar @newuserconf;
|
||||
}
|
||||
|
||||
# back up and restore device_auth
|
||||
my $guard = guard { set(device_auth => \@userconf) };
|
||||
set(device_auth => \@newuserconf);
|
||||
# use DDP; p @newuserconf;
|
||||
|
||||
# run worker
|
||||
$code->($job, $workerconf);
|
||||
};
|
||||
|
||||
# store the built worker as Worker.pm will build the dispatch order later on
|
||||
push @{ vars->{'workers'}->{$workerconf->{action}}
|
||||
->{$workerconf->{phase}}
|
||||
->{$workerconf->{namespace}}
|
||||
->{$workerconf->{priority}} }, $worker;
|
||||
};
|
||||
|
||||
sub _find_matchaction {
|
||||
my ($conf, $action) = @_;
|
||||
return true if !defined $action;
|
||||
$action = [$action] if ref [] ne ref $action;
|
||||
|
||||
foreach my $f (@$action) {
|
||||
return true if
|
||||
$f eq $conf->{action} or $f eq "$conf->{action}::$conf->{namespace}";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
register_plugin;
|
||||
true;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
App::Netdisco::Worker::Plugin - Netdisco Workers
|
||||
|
||||
=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).
|
||||
|
||||
=head1 Application Configuration
|
||||
|
||||
The C<worker_plugins> and C<extra_worker_plugins> settings list in YAML format
|
||||
the set of Perl module names which are the plugins to be loaded.
|
||||
|
||||
Any change should go into your local C<deployment.yml> configuration file. If
|
||||
you want to view the default settings, see the C<share/config.yml> file in the
|
||||
C<App::Netdisco> distribution.
|
||||
|
||||
=head1 How to Configure
|
||||
|
||||
The C<extra_worker_plugins> setting is empty, and used when you want to add
|
||||
new plugins and not change the set enabled by default. If you do want to add
|
||||
to or remove from the default set, then create a version of C<worker_plugins>
|
||||
instead.
|
||||
|
||||
Netdisco prepends "C<App::Netdisco::Worker::Plugin::>" to any entry in the
|
||||
list. For example, "C<Discover::Wireless::UniFi>" will load the
|
||||
C<App::Netdisco::Worker::Plugin::Discover::Wireless::UniFi> package.
|
||||
|
||||
You can prepend module names with "C<X::>" as shorthand for the "Netdisco
|
||||
extension" namespace. For example, "C<X::Macsuck::WirelessNodes::UniFi>" will
|
||||
load the L<App::NetdiscoX::Worker::Plugin::Macsuck::WirelessNodes::UniFi>
|
||||
module.
|
||||
|
||||
If an entry in the list starts with a "C<+>" (plus) sign then Netdisco attemps
|
||||
to load the module as-is, without prepending anything to the name. This allows
|
||||
you to have worker plugins in any namespace.
|
||||
|
||||
Plugin modules can either ship with the App::Netdisco distribution itself, or
|
||||
be installed separately. Perl uses the standard C<@INC> path searching
|
||||
mechanism to load the plugin modules. See the C<include_paths> and
|
||||
C<site_local_files> settings in order to modify C<@INC> for loading local
|
||||
plugins.
|
||||
|
||||
As an example, if you set C<site_local_files> to be true, set
|
||||
C<extra_worker_plugins> to be C<'X::MyPluginName'> (the plugin package is
|
||||
"App::NetdiscoX::Worker::Plugin::MyPluginName") then your plugin lives at:
|
||||
|
||||
~netdisco/nd-site-local/lib/App/NetdiscoX/Worker/Plugin/MyPluginName.pm
|
||||
|
||||
The order of the entries is significant, workers being executed in the order
|
||||
which they appear in C<extra_worker_plugins> followed by C<worker_plugins>.
|
||||
|
||||
See L<App::Netdisco::Manual::WritingWorkers> for further details.
|
||||
=cut
|
||||
|
||||
31
lib/App/Netdisco/Worker/Plugin/Arpnip.pm
Normal file
31
lib/App/Netdisco/Worker/Plugin/Arpnip.pm
Normal file
@@ -0,0 +1,31 @@
|
||||
package App::Netdisco::Worker::Plugin::Arpnip;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Util::Device 'is_arpnipable_now';
|
||||
|
||||
register_worker({ phase => 'check' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
my $device = $job->device;
|
||||
|
||||
return Status->error('arpnip failed: unable to interpret device param')
|
||||
unless defined $device;
|
||||
|
||||
return Status->error("arpnip skipped: $device not yet discovered")
|
||||
unless $device->in_storage;
|
||||
|
||||
return Status->defer("arpnip skipped: $device is pseudo-device")
|
||||
if $device->is_pseudo;
|
||||
|
||||
return Status->defer("arpnip skipped: $device has no layer 3 capability")
|
||||
unless $device->has_layer(3);
|
||||
|
||||
return Status->defer("arpnip deferred: $device is not arpnipable")
|
||||
unless is_arpnipable_now($device);
|
||||
|
||||
return Status->done('arpnip is able to run');
|
||||
});
|
||||
|
||||
true;
|
||||
@@ -1,58 +1,27 @@
|
||||
package App::Netdisco::Core::Arpnip;
|
||||
package App::Netdisco::Worker::Plugin::Arpnip::Nodes;
|
||||
|
||||
use Dancer qw/:syntax :script/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP ();
|
||||
use App::Netdisco::Util::Node 'check_mac';
|
||||
use App::Netdisco::Util::Permission 'check_acl_no';
|
||||
use App::Netdisco::Util::FastResolver 'hostnames_resolve_async';
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
use Time::HiRes 'gettimeofday';
|
||||
use NetAddr::MAC ();
|
||||
|
||||
use base 'Exporter';
|
||||
our @EXPORT = ();
|
||||
our @EXPORT_OK = qw/ do_arpnip store_arp /;
|
||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
App::Netdisco::Core::Arpnip
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Helper subroutines to support parts of the Netdisco application.
|
||||
|
||||
There are no default exports, however the C<:all> tag will export all
|
||||
subroutines.
|
||||
|
||||
=head1 EXPORT_OK
|
||||
|
||||
=head2 do_arpnip( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, connect to a
|
||||
device and discover its ARP cache for IPv4 and Neighbor cache for IPv6.
|
||||
|
||||
Will also discover subnets in use on the device and update the Subnets table.
|
||||
|
||||
=cut
|
||||
|
||||
sub do_arpnip {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
unless ($device->in_storage) {
|
||||
debug sprintf ' [%s] arpnip - skipping device not yet discovered', $device->ip;
|
||||
return;
|
||||
}
|
||||
my $device = $job->device;
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||||
or return Status->defer("arpnip failed: could not SNMP connect to $device");
|
||||
|
||||
# get v4 arp table
|
||||
my $v4 = _get_arps($device, $snmp->at_paddr, $snmp->at_netaddr);
|
||||
my $v4 = get_arps($device, $snmp->at_paddr, $snmp->at_netaddr);
|
||||
# get v6 neighbor cache
|
||||
my $v6 = _get_arps($device, $snmp->ipv6_n2p_mac, $snmp->ipv6_n2p_addr);
|
||||
|
||||
# get directly connected networks
|
||||
my @subnets = _gather_subnets($device, $snmp);
|
||||
# TODO: IPv6 subnets
|
||||
my $v6 = get_arps($device, $snmp->ipv6_n2p_mac, $snmp->ipv6_n2p_addr);
|
||||
|
||||
# would be possible just to use now() on updated records, but by using this
|
||||
# same value for them all, we _can_ if we want add a job at the end to
|
||||
@@ -68,15 +37,12 @@ sub do_arpnip {
|
||||
debug sprintf ' [%s] arpnip - processed %s IPv6 Neighbor Cache entries',
|
||||
$device->ip, scalar @$v6;
|
||||
|
||||
_store_subnet($_, $now) for @subnets;
|
||||
debug sprintf ' [%s] arpnip - processed %s Subnet entries',
|
||||
$device->ip, scalar @subnets;
|
||||
|
||||
$device->update({last_arpnip => \$now});
|
||||
}
|
||||
return Status->done("Ended arpnip for $device");
|
||||
});
|
||||
|
||||
# get an arp table (v4 or v6)
|
||||
sub _get_arps {
|
||||
sub get_arps {
|
||||
my ($device, $paddr, $netaddr) = @_;
|
||||
my @arps = ();
|
||||
|
||||
@@ -92,7 +58,7 @@ sub _get_arps {
|
||||
}
|
||||
|
||||
debug sprintf ' resolving %d ARP entries with max %d outstanding requests',
|
||||
scalar @arps, $ENV{'PERL_ANYEVENT_MAX_OUTSTANDING_DNS'};
|
||||
scalar @arps, $ENV{'PERL_ANYEVENT_MAX_OUTSTANDING_DNS'};
|
||||
my $resolved_ips = hostnames_resolve_async(\@arps);
|
||||
|
||||
return $resolved_ips;
|
||||
@@ -148,44 +114,4 @@ sub store_arp {
|
||||
});
|
||||
}
|
||||
|
||||
# gathers device subnets
|
||||
sub _gather_subnets {
|
||||
my ($device, $snmp) = @_;
|
||||
my @subnets = ();
|
||||
|
||||
my $ip_netmask = $snmp->ip_netmask;
|
||||
foreach my $entry (keys %$ip_netmask) {
|
||||
my $ip = NetAddr::IP::Lite->new($entry);
|
||||
my $addr = $ip->addr;
|
||||
|
||||
next if $addr eq '0.0.0.0';
|
||||
next if check_acl_no($ip, 'group:__LOCAL_ADDRESSES__');
|
||||
next if setting('ignore_private_nets') and $ip->is_rfc1918;
|
||||
|
||||
my $netmask = $ip_netmask->{$addr};
|
||||
next if $netmask eq '255.255.255.255' or $netmask eq '0.0.0.0';
|
||||
|
||||
my $cidr = NetAddr::IP::Lite->new($addr, $netmask)->network->cidr;
|
||||
|
||||
debug sprintf ' [%s] arpnip - found subnet %s', $device->ip, $cidr;
|
||||
push @subnets, $cidr;
|
||||
}
|
||||
|
||||
return @subnets;
|
||||
}
|
||||
|
||||
# update subnets with new networks
|
||||
sub _store_subnet {
|
||||
my ($subnet, $now) = @_;
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
schema('netdisco')->resultset('Subnet')->update_or_create(
|
||||
{
|
||||
net => $subnet,
|
||||
last_discover => \$now,
|
||||
},
|
||||
{ for => 'update' });
|
||||
});
|
||||
}
|
||||
|
||||
1;
|
||||
true;
|
||||
74
lib/App/Netdisco/Worker/Plugin/Arpnip/Subnets.pm
Normal file
74
lib/App/Netdisco/Worker/Plugin/Arpnip/Subnets.pm
Normal file
@@ -0,0 +1,74 @@
|
||||
package App::Netdisco::Worker::Plugin::Arpnip::Subnets;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP ();
|
||||
use App::Netdisco::Util::Permission 'check_acl_no';
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
use Time::HiRes 'gettimeofday';
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
my $device = $job->device;
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||||
or return Status->defer("arpnip failed: could not SNMP connect to $device");
|
||||
|
||||
# get directly connected networks
|
||||
my @subnets = gather_subnets($device);
|
||||
# TODO: IPv6 subnets
|
||||
|
||||
my $now = 'to_timestamp('. (join '.', gettimeofday) .')';
|
||||
store_subnet($_, $now) for @subnets;
|
||||
|
||||
return Status->noop(sprintf ' [%s] arpnip - processed %s Subnet entries',
|
||||
$device->ip, scalar @subnets);
|
||||
});
|
||||
|
||||
# gathers device subnets
|
||||
sub gather_subnets {
|
||||
my $device = shift;
|
||||
my @subnets = ();
|
||||
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||||
or return (); # already checked!
|
||||
|
||||
my $ip_netmask = $snmp->ip_netmask;
|
||||
foreach my $entry (keys %$ip_netmask) {
|
||||
my $ip = NetAddr::IP::Lite->new($entry);
|
||||
my $addr = $ip->addr;
|
||||
|
||||
next if $addr eq '0.0.0.0';
|
||||
next if check_acl_no($ip, 'group:__LOCAL_ADDRESSES__');
|
||||
next if setting('ignore_private_nets') and $ip->is_rfc1918;
|
||||
|
||||
my $netmask = $ip_netmask->{$addr};
|
||||
next if $netmask eq '255.255.255.255' or $netmask eq '0.0.0.0';
|
||||
|
||||
my $cidr = NetAddr::IP::Lite->new($addr, $netmask)->network->cidr;
|
||||
|
||||
debug sprintf ' [%s] arpnip - found subnet %s', $device->ip, $cidr;
|
||||
push @subnets, $cidr;
|
||||
}
|
||||
|
||||
return @subnets;
|
||||
}
|
||||
|
||||
# update subnets with new networks
|
||||
sub store_subnet {
|
||||
my ($subnet, $now) = @_;
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
schema('netdisco')->resultset('Subnet')->update_or_create(
|
||||
{
|
||||
net => $subnet,
|
||||
last_discover => \$now,
|
||||
},
|
||||
{ for => 'update' });
|
||||
});
|
||||
}
|
||||
|
||||
true;
|
||||
31
lib/App/Netdisco/Worker/Plugin/Arpwalk.pm
Normal file
31
lib/App/Netdisco/Worker/Plugin/Arpwalk.pm
Normal file
@@ -0,0 +1,31 @@
|
||||
package App::Netdisco::Worker::Plugin::Arpwalk;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::JobQueue qw/jq_queued jq_insert/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
register_worker({ phase => 'main' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
my %queued = map {$_ => 1} jq_queued('arpnip');
|
||||
my @devices = schema('netdisco')->resultset('Device')->search({
|
||||
-or => [ 'vendor' => undef, 'vendor' => { '!=' => 'netdisco' }],
|
||||
})->has_layer('3')->get_column('ip')->all;
|
||||
my @filtered_devices = grep {!exists $queued{$_}} @devices;
|
||||
|
||||
jq_insert([
|
||||
map {{
|
||||
device => $_,
|
||||
action => 'arpnip',
|
||||
username => $job->username,
|
||||
userip => $job->userip,
|
||||
}} (@filtered_devices)
|
||||
]);
|
||||
|
||||
return Status->done('Queued arpnip job for all devices');
|
||||
});
|
||||
|
||||
true;
|
||||
43
lib/App/Netdisco/Worker/Plugin/Contact.pm
Normal file
43
lib/App/Netdisco/Worker/Plugin/Contact.pm
Normal file
@@ -0,0 +1,43 @@
|
||||
package App::Netdisco::Worker::Plugin::Contact;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP;
|
||||
|
||||
register_worker({ phase => 'check' }, sub {
|
||||
return Status->error('Missing device (-d).')
|
||||
unless defined shift->device;
|
||||
return Status->done('Contact is able to run');
|
||||
});
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
my ($device, $data) = map {$job->$_} qw/device extra/;
|
||||
|
||||
# snmp connect using rw community
|
||||
my $snmp = App::Netdisco::Transport::SNMP->writer_for($device)
|
||||
or return Status->defer("failed to connect to $device to update contact");
|
||||
|
||||
my $rv = $snmp->set_contact($data);
|
||||
|
||||
if (!defined $rv) {
|
||||
return Status->error(
|
||||
"failed to set contact on $device: ". ($snmp->error || ''));
|
||||
}
|
||||
|
||||
# confirm the set happened
|
||||
$snmp->clear_cache;
|
||||
my $new_data = ($snmp->contact || '');
|
||||
if ($new_data ne $data) {
|
||||
return Status->error("verify of contact failed on $device: $new_data");
|
||||
}
|
||||
|
||||
# update netdisco DB
|
||||
$device->update({contact => $data});
|
||||
|
||||
return Status->done("Updated contact on $device to [$data]");
|
||||
});
|
||||
|
||||
true;
|
||||
24
lib/App/Netdisco/Worker/Plugin/Delete.pm
Normal file
24
lib/App/Netdisco/Worker/Plugin/Delete.pm
Normal file
@@ -0,0 +1,24 @@
|
||||
package App::Netdisco::Worker::Plugin::Delete;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Util::Device 'delete_device';
|
||||
|
||||
register_worker({ phase => 'check' }, sub {
|
||||
return Status->error('Missing device (-d).')
|
||||
unless defined shift->device;
|
||||
return Status->done('Delete is able to run');
|
||||
});
|
||||
|
||||
register_worker({ phase => 'main' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
my ($device, $port, $extra) = map {$job->$_} qw/device port extra/;
|
||||
|
||||
$port = ($port ? 1 : 0);
|
||||
delete_device($device, $port, $extra);
|
||||
return Status->done("Deleted device: $device");
|
||||
});
|
||||
|
||||
true;
|
||||
28
lib/App/Netdisco/Worker/Plugin/Discover.pm
Normal file
28
lib/App/Netdisco/Worker/Plugin/Discover.pm
Normal file
@@ -0,0 +1,28 @@
|
||||
package App::Netdisco::Worker::Plugin::Discover;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Util::Device 'is_discoverable_now';
|
||||
|
||||
register_worker({ phase => 'check' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
my $device = $job->device;
|
||||
|
||||
return Status->error('discover failed: unable to interpret device param')
|
||||
unless defined $device;
|
||||
|
||||
return Status->error("discover failed: no device param (need -d ?)")
|
||||
if $device->ip eq '0.0.0.0';
|
||||
|
||||
return Status->defer("discover skipped: $device is pseudo-device")
|
||||
if $device->is_pseudo;
|
||||
|
||||
return Status->defer("discover deferred: $device is not discoverable")
|
||||
unless is_discoverable_now($device);
|
||||
|
||||
return Status->done('Discover is able to run.');
|
||||
});
|
||||
|
||||
true;
|
||||
80
lib/App/Netdisco/Worker/Plugin/Discover/CanonicalIP.pm
Normal file
80
lib/App/Netdisco/Worker/Plugin/Discover/CanonicalIP.pm
Normal file
@@ -0,0 +1,80 @@
|
||||
package App::Netdisco::Worker::Plugin::Discover::CanonicalIP;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP ();
|
||||
use App::Netdisco::Util::Permission 'check_acl_only';
|
||||
use App::Netdisco::Util::DNS 'ipv4_from_hostname';
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
my $device = $job->device;
|
||||
return unless $device->in_storage;
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||||
or return Status->defer("discover failed: could not SNMP connect to $device");
|
||||
|
||||
my $old_ip = $device->ip;
|
||||
my $new_ip = $old_ip;
|
||||
my $revofname = ipv4_from_hostname($snmp->name);
|
||||
|
||||
if (setting('reverse_sysname') and $revofname) {
|
||||
if (App::Netdisco::Transport::SNMP->test_connection( $new_ip )) {
|
||||
$new_ip = $revofname;
|
||||
}
|
||||
else {
|
||||
debug sprintf ' [%s] device - cannot renumber to %s - SNMP connect failed',
|
||||
$old_ip, $revofname;
|
||||
}
|
||||
}
|
||||
|
||||
if (scalar @{ setting('device_identity') }) {
|
||||
my @idmaps = @{ setting('device_identity') };
|
||||
my $devips = $device->device_ips->order_by('alias');
|
||||
|
||||
ALIAS: while (my $alias = $devips->next) {
|
||||
next if $alias->alias eq $old_ip;
|
||||
|
||||
foreach my $map (@idmaps) {
|
||||
next unless ref {} eq ref $map;
|
||||
|
||||
foreach my $key (sort keys %$map) {
|
||||
# lhs matches device, rhs matches device_ip
|
||||
if (check_acl_only($device, $key)
|
||||
and check_acl_only($alias, $map->{$key})) {
|
||||
|
||||
if (App::Netdisco::Transport::SNMP->test_connection( $alias->alias )) {
|
||||
$new_ip = $alias->alias;
|
||||
last ALIAS;
|
||||
}
|
||||
else {
|
||||
debug sprintf ' [%s] device - cannot renumber to %s - SNMP connect failed',
|
||||
$old_ip, $alias->alias;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} # ALIAS
|
||||
}
|
||||
|
||||
return if $new_ip eq $old_ip;
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
# delete target device with the same vendor and serial number
|
||||
schema('netdisco')->resultset('Device')->search({
|
||||
ip => $new_ip, vendor => $device->vendor, serial => $device->serial,
|
||||
})->delete;
|
||||
|
||||
# if target device exists then this will die
|
||||
$device->renumber($new_ip)
|
||||
or die "cannot renumber to: $new_ip"; # rollback
|
||||
|
||||
return Status->noop(sprintf ' [%s] device - changed IP to %s (%s)',
|
||||
$old_ip, $device->ip, ($device->dns || ''));
|
||||
});
|
||||
});
|
||||
|
||||
true;
|
||||
95
lib/App/Netdisco/Worker/Plugin/Discover/Entities.pm
Normal file
95
lib/App/Netdisco/Worker/Plugin/Discover/Entities.pm
Normal file
@@ -0,0 +1,95 @@
|
||||
package App::Netdisco::Worker::Plugin::Discover::Entities;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP ();
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
use Encode;
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
my $device = $job->device;
|
||||
return unless $device->in_storage;
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||||
or return Status->defer("discover failed: could not SNMP connect to $device");
|
||||
|
||||
my $e_index = $snmp->e_index;
|
||||
|
||||
if (!defined $e_index) {
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->modules->delete;
|
||||
debug sprintf ' [%s] modules - removed %d chassis modules',
|
||||
$device->ip, $gone;
|
||||
|
||||
$device->modules->update_or_create({
|
||||
ip => $device->ip,
|
||||
index => 1,
|
||||
parent => 0,
|
||||
name => 'chassis',
|
||||
class => 'chassis',
|
||||
pos => -1,
|
||||
# too verbose and link doesn't work anyway
|
||||
# description => $device->description,
|
||||
sw_ver => $device->os_ver,
|
||||
serial => $device->serial,
|
||||
model => $device->model,
|
||||
fru => \'false',
|
||||
last_discover => \'now()',
|
||||
});
|
||||
});
|
||||
|
||||
return Status->noop(
|
||||
sprintf ' [%s] modules - 0 chassis components (added one pseudo for chassis)',
|
||||
$device->ip);
|
||||
}
|
||||
|
||||
my $e_descr = $snmp->e_descr;
|
||||
my $e_type = $snmp->e_type;
|
||||
my $e_parent = $snmp->e_parent;
|
||||
my $e_name = $snmp->e_name;
|
||||
my $e_class = $snmp->e_class;
|
||||
my $e_pos = $snmp->e_pos;
|
||||
my $e_hwver = $snmp->e_hwver;
|
||||
my $e_fwver = $snmp->e_fwver;
|
||||
my $e_swver = $snmp->e_swver;
|
||||
my $e_model = $snmp->e_model;
|
||||
my $e_serial = $snmp->e_serial;
|
||||
my $e_fru = $snmp->e_fru;
|
||||
|
||||
# build device modules list for DBIC
|
||||
my (@modules, %seen_idx);
|
||||
foreach my $entry (keys %$e_index) {
|
||||
next if $seen_idx{ $e_index->{$entry} }++;
|
||||
push @modules, {
|
||||
index => $e_index->{$entry},
|
||||
type => $e_type->{$entry},
|
||||
parent => $e_parent->{$entry},
|
||||
name => Encode::decode('UTF-8', $e_name->{$entry}),
|
||||
class => $e_class->{$entry},
|
||||
pos => $e_pos->{$entry},
|
||||
hw_ver => Encode::decode('UTF-8', $e_hwver->{$entry}),
|
||||
fw_ver => Encode::decode('UTF-8', $e_fwver->{$entry}),
|
||||
sw_ver => Encode::decode('UTF-8', $e_swver->{$entry}),
|
||||
model => Encode::decode('UTF-8', $e_model->{$entry}),
|
||||
serial => Encode::decode('UTF-8', $e_serial->{$entry}),
|
||||
fru => $e_fru->{$entry},
|
||||
description => Encode::decode('UTF-8', $e_descr->{$entry}),
|
||||
last_discover => \'now()',
|
||||
};
|
||||
}
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->modules->delete;
|
||||
debug sprintf ' [%s] modules - removed %d chassis modules',
|
||||
$device->ip, $gone;
|
||||
$device->modules->populate(\@modules);
|
||||
|
||||
return Status->noop(sprintf ' [%s] modules - added %d new chassis modules',
|
||||
$device->ip, scalar @modules);
|
||||
});
|
||||
});
|
||||
|
||||
true;
|
||||
338
lib/App/Netdisco/Worker/Plugin/Discover/Neighbors.pm
Normal file
338
lib/App/Netdisco/Worker/Plugin/Discover/Neighbors.pm
Normal file
@@ -0,0 +1,338 @@
|
||||
package App::Netdisco::Worker::Plugin::Discover::Neighbors;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP ();
|
||||
use App::Netdisco::Util::Device
|
||||
qw/get_device match_devicetype is_discoverable/;
|
||||
use App::Netdisco::Util::Permission 'check_acl_no';
|
||||
use App::Netdisco::JobQueue 'jq_insert';
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
use List::MoreUtils ();
|
||||
use NetAddr::MAC;
|
||||
use Encode;
|
||||
|
||||
=head2 discover_new_neighbors( )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store the device's port neighbors information.
|
||||
|
||||
Entries in the Topology database table will override any discovered device
|
||||
port relationships.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
Any discovered neighbor unknown to Netdisco will have a C<discover> job
|
||||
immediately queued (subject to the filtering by the C<discover_*> settings).
|
||||
|
||||
=cut
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
my $device = $job->device;
|
||||
return unless $device->in_storage;
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||||
or return Status->defer("discover failed: could not SNMP connect to $device");
|
||||
|
||||
my @to_discover = store_neighbors($device);
|
||||
|
||||
# only enqueue if device is not already discovered,
|
||||
# discover_* config permits the discovery
|
||||
foreach my $neighbor (@to_discover) {
|
||||
my ($ip, $remote_type) = @$neighbor;
|
||||
|
||||
my $device = get_device($ip);
|
||||
next if $device->in_storage;
|
||||
|
||||
if (not is_discoverable($device, $remote_type)) {
|
||||
debug sprintf
|
||||
' queue - %s, type [%s] excluded by discover_* config',
|
||||
$ip, ($remote_type || '');
|
||||
next;
|
||||
}
|
||||
|
||||
jq_insert({
|
||||
device => $ip,
|
||||
action => 'discover',
|
||||
subaction => 'with-nodes',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
=head2 store_neighbors( $device )
|
||||
|
||||
returns: C<@to_discover>
|
||||
|
||||
Given a Device database object, and a working SNMP connection, discover and
|
||||
store the device's port neighbors information.
|
||||
|
||||
Entries in the Topology database table will override any discovered device
|
||||
port relationships.
|
||||
|
||||
The Device database object can be a fresh L<DBIx::Class::Row> object which is
|
||||
not yet stored to the database.
|
||||
|
||||
A list of discovererd neighbors will be returned as [C<$ip>, C<$type>] tuples.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_neighbors {
|
||||
my $device = shift;
|
||||
my @to_discover = ();
|
||||
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||||
or return (); # already checked!
|
||||
|
||||
# first allow any manually configured topology to be set
|
||||
set_manual_topology($device);
|
||||
|
||||
if (!defined $snmp->has_topo) {
|
||||
debug sprintf ' [%s] neigh - neighbor protocols are not enabled', $device->ip;
|
||||
return @to_discover;
|
||||
}
|
||||
|
||||
my $interfaces = $snmp->interfaces;
|
||||
my $c_if = $snmp->c_if;
|
||||
my $c_port = $snmp->c_port;
|
||||
my $c_id = $snmp->c_id;
|
||||
my $c_platform = $snmp->c_platform;
|
||||
my $c_cap = $snmp->c_cap;
|
||||
|
||||
# v4 and v6 neighbor tables
|
||||
my $c_ip = ($snmp->c_ip || {});
|
||||
my %c_ipv6 = %{ ($snmp->can('hasLLDP') and $snmp->hasLLDP)
|
||||
? ($snmp->lldp_ipv6 || {}) : {} };
|
||||
|
||||
# remove keys with undef values, as c_ip does
|
||||
delete @c_ipv6{ grep { not defined $c_ipv6{$_} } keys %c_ipv6 };
|
||||
# now combine them, v6 wins
|
||||
$c_ip = { %$c_ip, %c_ipv6 };
|
||||
|
||||
foreach my $entry (sort (List::MoreUtils::uniq( (keys %$c_ip), (keys %$c_cap) ))) {
|
||||
if (!defined $c_if->{$entry} or !defined $interfaces->{ $c_if->{$entry} }) {
|
||||
debug sprintf ' [%s] neigh - port for IID:%s not resolved, skipping',
|
||||
$device->ip, $entry;
|
||||
next;
|
||||
}
|
||||
|
||||
my $port = $interfaces->{ $c_if->{$entry} };
|
||||
my $portrow = schema('netdisco')->resultset('DevicePort')
|
||||
->single({ip => $device->ip, port => $port});
|
||||
|
||||
if (!defined $portrow) {
|
||||
info sprintf ' [%s] neigh - local port %s not in database!',
|
||||
$device->ip, $port;
|
||||
next;
|
||||
}
|
||||
|
||||
if (ref $c_ip->{$entry}) {
|
||||
error sprintf ' [%s] neigh - Error! port %s has multiple neighbors - skipping',
|
||||
$device->ip, $port;
|
||||
next;
|
||||
}
|
||||
|
||||
my $remote_ip = $c_ip->{$entry};
|
||||
my $remote_port = undef;
|
||||
my $remote_type = Encode::decode('UTF-8', $c_platform->{$entry} || '');
|
||||
my $remote_id = Encode::decode('UTF-8', $c_id->{$entry});
|
||||
my $remote_cap = $c_cap->{$entry} || [];
|
||||
|
||||
# IP Phone and WAP detection type fixup
|
||||
if (scalar @$remote_cap or $remote_type) {
|
||||
my $phone_flag = grep {match_devicetype($_, 'phone_capabilities')}
|
||||
@$remote_cap;
|
||||
my $ap_flag = grep {match_devicetype($_, 'wap_capabilities')}
|
||||
@$remote_cap;
|
||||
|
||||
if ($phone_flag or match_devicetype($remote_type, 'phone_platforms')) {
|
||||
$remote_type = 'IP Phone: '. $remote_type
|
||||
if $remote_type !~ /ip.phone/i;
|
||||
}
|
||||
elsif ($ap_flag or match_devicetype($remote_type, 'wap_platforms')) {
|
||||
$remote_type = 'AP: '. $remote_type;
|
||||
}
|
||||
|
||||
$portrow->update({remote_type => $remote_type});
|
||||
}
|
||||
|
||||
if ($portrow->manual_topo) {
|
||||
info sprintf ' [%s] neigh - %s has manually defined topology',
|
||||
$device->ip, $port;
|
||||
next;
|
||||
}
|
||||
|
||||
next unless $remote_ip;
|
||||
|
||||
# a bunch of heuristics to search known devices if we don't have a
|
||||
# useable remote IP...
|
||||
|
||||
if ($remote_ip eq '0.0.0.0' or
|
||||
check_acl_no($remote_ip, 'group:__LOCAL_ADDRESSES__')) {
|
||||
|
||||
if ($remote_id) {
|
||||
my $devices = schema('netdisco')->resultset('Device');
|
||||
my $neigh = $devices->single({name => $remote_id});
|
||||
info sprintf
|
||||
' [%s] neigh - bad address %s on port %s, searching for %s instead',
|
||||
$device->ip, $remote_ip, $port, $remote_id;
|
||||
|
||||
if (!defined $neigh) {
|
||||
my $mac = NetAddr::MAC->new(mac => $remote_id);
|
||||
if ($mac and not $mac->errstr) {
|
||||
$neigh = $devices->single({mac => $mac->as_ieee});
|
||||
}
|
||||
}
|
||||
|
||||
# some HP switches send 127.0.0.1 as remote_ip if no ip address
|
||||
# on default vlan for HP switches remote_ip looks like
|
||||
# "myswitchname(012345-012345)"
|
||||
if (!defined $neigh) {
|
||||
(my $tmpid = $remote_id) =~ s/.*\(([0-9a-f]{6})-([0-9a-f]{6})\).*/$1$2/;
|
||||
my $mac = NetAddr::MAC->new(mac => $tmpid);
|
||||
if ($mac and not $mac->errstr) {
|
||||
info sprintf
|
||||
'[%s] neigh - found neighbor %s by MAC %s',
|
||||
$device->ip, $remote_id, $mac->as_ieee;
|
||||
$neigh = $devices->single({mac => $mac->as_ieee});
|
||||
}
|
||||
}
|
||||
|
||||
if (!defined $neigh) {
|
||||
(my $shortid = $remote_id) =~ s/\..*//;
|
||||
$neigh = $devices->single({name => { -ilike => "${shortid}%" }});
|
||||
}
|
||||
|
||||
if ($neigh) {
|
||||
$remote_ip = $neigh->ip;
|
||||
info sprintf ' [%s] neigh - found %s with IP %s',
|
||||
$device->ip, $remote_id, $remote_ip;
|
||||
}
|
||||
else {
|
||||
info sprintf ' [%s] neigh - could not find %s, skipping',
|
||||
$device->ip, $remote_id;
|
||||
next;
|
||||
}
|
||||
}
|
||||
else {
|
||||
info sprintf ' [%s] neigh - skipping unuseable address %s on port %s',
|
||||
$device->ip, $remote_ip, $port;
|
||||
next;
|
||||
}
|
||||
}
|
||||
|
||||
# what we came here to do.... discover the neighbor
|
||||
debug sprintf
|
||||
' [%s] neigh - adding neighbor %s, type [%s], on %s to discovery queue',
|
||||
$device->ip, $remote_ip, ($remote_type || ''), $port;
|
||||
push @to_discover, [$remote_ip, $remote_type];
|
||||
|
||||
$remote_port = $c_port->{$entry};
|
||||
if (defined $remote_port) {
|
||||
# clean weird characters
|
||||
$remote_port =~ s/[^\d\/\.,()\w:-]+//gi;
|
||||
}
|
||||
else {
|
||||
info sprintf ' [%s] neigh - no remote port found for port %s at %s',
|
||||
$device->ip, $port, $remote_ip;
|
||||
}
|
||||
|
||||
$portrow->update({
|
||||
remote_ip => $remote_ip,
|
||||
remote_port => $remote_port,
|
||||
remote_type => $remote_type,
|
||||
remote_id => $remote_id,
|
||||
is_uplink => \"true",
|
||||
manual_topo => \"false",
|
||||
});
|
||||
|
||||
# update master of our aggregate to be a neighbor of
|
||||
# the master on our peer device (a lot of iffs to get there...).
|
||||
# & cannot use ->neighbor prefetch because this is the port insert!
|
||||
if (defined $portrow->slave_of) {
|
||||
|
||||
my $peer_device = get_device($remote_ip);
|
||||
my $master = schema('netdisco')->resultset('DevicePort')->single({
|
||||
ip => $device->ip,
|
||||
port => $portrow->slave_of
|
||||
});
|
||||
|
||||
if ($peer_device and $peer_device->in_storage and $master
|
||||
and not ($portrow->is_master or defined $master->slave_of)) {
|
||||
|
||||
my $peer_port = schema('netdisco')->resultset('DevicePort')->single({
|
||||
ip => $peer_device->ip,
|
||||
port => $portrow->remote_port,
|
||||
});
|
||||
|
||||
$master->update({
|
||||
remote_ip => ($peer_device->ip || $remote_ip),
|
||||
remote_port => ($peer_port ? $peer_port->slave_of : undef ),
|
||||
is_uplink => \"true",
|
||||
is_master => \"true",
|
||||
manual_topo => \"false",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return @to_discover;
|
||||
}
|
||||
|
||||
# take data from the topology table and update remote_ip and remote_port
|
||||
# in the devices table. only use root_ips and skip any bad topo entries.
|
||||
sub set_manual_topology {
|
||||
my $device = shift;
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device) or return;
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
# clear manual topology flags
|
||||
schema('netdisco')->resultset('DevicePort')
|
||||
->search({ip => $device->ip})->update({manual_topo => \'false'});
|
||||
|
||||
my $topo_links = schema('netdisco')->resultset('Topology')
|
||||
->search({-or => [dev1 => $device->ip, dev2 => $device->ip]});
|
||||
debug sprintf ' [%s] neigh - setting manual topology links', $device->ip;
|
||||
|
||||
while (my $link = $topo_links->next) {
|
||||
# could fail for broken topo, but we ignore to try the rest
|
||||
try {
|
||||
schema('netdisco')->txn_do(sub {
|
||||
# only work on root_ips
|
||||
my $left = get_device($link->dev1);
|
||||
my $right = get_device($link->dev2);
|
||||
|
||||
# skip bad entries
|
||||
return unless ($left->in_storage and $right->in_storage);
|
||||
|
||||
$left->ports
|
||||
->single({port => $link->port1})
|
||||
->update({
|
||||
remote_ip => $right->ip,
|
||||
remote_port => $link->port2,
|
||||
remote_type => undef,
|
||||
remote_id => undef,
|
||||
is_uplink => \"true",
|
||||
manual_topo => \"true",
|
||||
});
|
||||
|
||||
$right->ports
|
||||
->single({port => $link->port2})
|
||||
->update({
|
||||
remote_ip => $left->ip,
|
||||
remote_port => $link->port1,
|
||||
remote_type => undef,
|
||||
remote_id => undef,
|
||||
is_uplink => \"true",
|
||||
manual_topo => \"true",
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
true;
|
||||
81
lib/App/Netdisco/Worker/Plugin/Discover/PortPower.pm
Normal file
81
lib/App/Netdisco/Worker/Plugin/Discover/PortPower.pm
Normal file
@@ -0,0 +1,81 @@
|
||||
package App::Netdisco::Worker::Plugin::Discover::PortPower;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP ();
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
my $device = $job->device;
|
||||
return unless $device->in_storage;
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||||
or return Status->defer("discover failed: could not SNMP connect to $device");
|
||||
|
||||
my $p_watts = $snmp->peth_power_watts;
|
||||
my $p_status = $snmp->peth_power_status;
|
||||
|
||||
if (!defined $p_watts) {
|
||||
return Status->noop(sprintf ' [%s] power - 0 power modules', $device->ip);
|
||||
}
|
||||
|
||||
# build device module power info suitable for DBIC
|
||||
my @devicepower;
|
||||
foreach my $entry (keys %$p_watts) {
|
||||
push @devicepower, {
|
||||
module => $entry,
|
||||
power => $p_watts->{$entry},
|
||||
status => $p_status->{$entry},
|
||||
};
|
||||
}
|
||||
|
||||
my $interfaces = $snmp->interfaces;
|
||||
my $p_ifindex = $snmp->peth_port_ifindex;
|
||||
my $p_admin = $snmp->peth_port_admin;
|
||||
my $p_pstatus = $snmp->peth_port_status;
|
||||
my $p_class = $snmp->peth_port_class;
|
||||
my $p_power = $snmp->peth_port_power;
|
||||
|
||||
# build device port power info suitable for DBIC
|
||||
my @portpower;
|
||||
foreach my $entry (keys %$p_ifindex) {
|
||||
my $port = $interfaces->{ $p_ifindex->{$entry} };
|
||||
next unless $port;
|
||||
|
||||
my ($module) = split m/\./, $entry;
|
||||
|
||||
push @portpower, {
|
||||
port => $port,
|
||||
module => $module,
|
||||
admin => $p_admin->{$entry},
|
||||
status => $p_pstatus->{$entry},
|
||||
class => $p_class->{$entry},
|
||||
power => $p_power->{$entry},
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->power_modules->delete;
|
||||
debug sprintf ' [%s] power - removed %d power modules',
|
||||
$device->ip, $gone;
|
||||
$device->power_modules->populate(\@devicepower);
|
||||
debug sprintf ' [%s] power - added %d new power modules',
|
||||
$device->ip, scalar @devicepower;
|
||||
});
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->powered_ports->delete;
|
||||
debug sprintf ' [%s] power - removed %d PoE capable ports',
|
||||
$device->ip, $gone;
|
||||
$device->powered_ports->populate(\@portpower);
|
||||
|
||||
return Status->noop(sprintf ' [%s] power - added %d new PoE capable ports',
|
||||
$device->ip, scalar @portpower);
|
||||
});
|
||||
});
|
||||
|
||||
true;
|
||||
245
lib/App/Netdisco/Worker/Plugin/Discover/Properties.pm
Normal file
245
lib/App/Netdisco/Worker/Plugin/Discover/Properties.pm
Normal file
@@ -0,0 +1,245 @@
|
||||
package App::Netdisco::Worker::Plugin::Discover::Properties;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP ();
|
||||
use App::Netdisco::Util::Permission 'check_acl_no';
|
||||
use App::Netdisco::Util::FastResolver 'hostnames_resolve_async';
|
||||
use App::Netdisco::Util::DNS 'hostname_from_ip';
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
use NetAddr::IP::Lite ':lower';
|
||||
use Encode;
|
||||
|
||||
register_worker({ phase => 'early', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
my $device = $job->device;
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||||
or return Status->defer("discover failed: could not SNMP connect to $device");
|
||||
|
||||
my $ip_index = $snmp->ip_index;
|
||||
my $interfaces = $snmp->interfaces;
|
||||
my $ip_netmask = $snmp->ip_netmask;
|
||||
|
||||
# build device aliases suitable for DBIC
|
||||
my @aliases;
|
||||
foreach my $entry (keys %$ip_index) {
|
||||
my $ip = NetAddr::IP::Lite->new($entry)
|
||||
or next;
|
||||
my $addr = $ip->addr;
|
||||
|
||||
next if $addr eq '0.0.0.0';
|
||||
next if check_acl_no($ip, 'group:__LOCAL_ADDRESSES__');
|
||||
next if setting('ignore_private_nets') and $ip->is_rfc1918;
|
||||
|
||||
my $iid = $ip_index->{$addr};
|
||||
my $port = $interfaces->{$iid};
|
||||
my $subnet = $ip_netmask->{$addr}
|
||||
? NetAddr::IP::Lite->new($addr, $ip_netmask->{$addr})->network->cidr
|
||||
: undef;
|
||||
|
||||
debug sprintf ' [%s] device - aliased as %s', $device->ip, $addr;
|
||||
push @aliases, {
|
||||
alias => $addr,
|
||||
port => $port,
|
||||
subnet => $subnet,
|
||||
dns => undef,
|
||||
};
|
||||
}
|
||||
|
||||
debug sprintf ' resolving %d aliases with max %d outstanding requests',
|
||||
scalar @aliases, $ENV{'PERL_ANYEVENT_MAX_OUTSTANDING_DNS'};
|
||||
my $resolved_aliases = hostnames_resolve_async(\@aliases);
|
||||
|
||||
# fake one aliases entry for devices not providing ip_index
|
||||
push @$resolved_aliases, { alias => $device->ip, dns => $device->dns }
|
||||
if 0 == scalar @aliases;
|
||||
|
||||
# VTP Management Domain -- assume only one.
|
||||
my $vtpdomains = $snmp->vtp_d_name;
|
||||
my $vtpdomain;
|
||||
if (defined $vtpdomains and scalar values %$vtpdomains) {
|
||||
$device->set_column( vtp_domain => (values %$vtpdomains)[-1] );
|
||||
}
|
||||
|
||||
my $hostname = hostname_from_ip($device->ip);
|
||||
$device->set_column( dns => $hostname ) if $hostname;
|
||||
|
||||
my @properties = qw/
|
||||
snmp_ver
|
||||
description uptime name
|
||||
layers ports mac
|
||||
ps1_type ps2_type ps1_status ps2_status
|
||||
fan slots
|
||||
vendor os os_ver
|
||||
/;
|
||||
|
||||
foreach my $property (@properties) {
|
||||
$device->set_column( $property => $snmp->$property );
|
||||
}
|
||||
|
||||
$device->set_column( model => Encode::decode('UTF-8', $snmp->model) );
|
||||
$device->set_column( serial => Encode::decode('UTF-8', $snmp->serial) );
|
||||
$device->set_column( contact => Encode::decode('UTF-8', $snmp->contact) );
|
||||
$device->set_column( location => Encode::decode('UTF-8', $snmp->location) );
|
||||
|
||||
|
||||
$device->set_column( snmp_class => $snmp->class );
|
||||
$device->set_column( last_discover => \'now()' );
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->device_ips->delete;
|
||||
debug sprintf ' [%s] device - removed %d aliases',
|
||||
$device->ip, $gone;
|
||||
$device->update_or_insert(undef, {for => 'update'});
|
||||
$device->device_ips->populate($resolved_aliases);
|
||||
debug sprintf ' [%s] device - added %d new aliases',
|
||||
$device->ip, scalar @aliases;
|
||||
});
|
||||
|
||||
return Status->done("Ended discover for $device");
|
||||
});
|
||||
|
||||
register_worker({ phase => 'early', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
my $device = $job->device;
|
||||
return unless $device->in_storage;
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||||
or return Status->defer("discover failed: could not SNMP connect to $device");
|
||||
|
||||
my $interfaces = $snmp->interfaces;
|
||||
my $i_type = $snmp->i_type;
|
||||
my $i_ignore = $snmp->i_ignore;
|
||||
my $i_descr = $snmp->i_description;
|
||||
my $i_mtu = $snmp->i_mtu;
|
||||
my $i_speed = $snmp->i_speed;
|
||||
my $i_mac = $snmp->i_mac;
|
||||
my $i_up = $snmp->i_up;
|
||||
my $i_up_admin = $snmp->i_up_admin;
|
||||
my $i_name = $snmp->i_name;
|
||||
my $i_duplex = $snmp->i_duplex;
|
||||
my $i_duplex_admin = $snmp->i_duplex_admin;
|
||||
my $i_stp_state = $snmp->i_stp_state;
|
||||
my $i_vlan = $snmp->i_vlan;
|
||||
my $i_lastchange = $snmp->i_lastchange;
|
||||
my $agg_ports = $snmp->agg_ports;
|
||||
|
||||
# clear the cached uptime and get a new one
|
||||
my $dev_uptime = $snmp->load_uptime;
|
||||
if (!defined $dev_uptime) {
|
||||
error sprintf ' [%s] interfaces - Error! Failed to get uptime from device!',
|
||||
$device->ip;
|
||||
return Status->error("discover failed: no uptime from device $device!");
|
||||
}
|
||||
|
||||
# used to track how many times the device uptime wrapped
|
||||
my $dev_uptime_wrapped = 0;
|
||||
|
||||
# use SNMP-FRAMEWORK-MIB::snmpEngineTime if available to
|
||||
# fix device uptime if wrapped
|
||||
if (defined $snmp->snmpEngineTime) {
|
||||
$dev_uptime_wrapped = int( $snmp->snmpEngineTime * 100 / 2**32 );
|
||||
if ($dev_uptime_wrapped > 0) {
|
||||
info sprintf ' [%s] interface - device uptime wrapped %d times - correcting',
|
||||
$device->ip, $dev_uptime_wrapped;
|
||||
$device->uptime( $dev_uptime + $dev_uptime_wrapped * 2**32 );
|
||||
}
|
||||
}
|
||||
|
||||
# build device interfaces suitable for DBIC
|
||||
my %interfaces;
|
||||
foreach my $entry (keys %$interfaces) {
|
||||
my $port = $interfaces->{$entry};
|
||||
|
||||
if (not $port) {
|
||||
debug sprintf ' [%s] interfaces - ignoring %s (no port mapping)',
|
||||
$device->ip, $entry;
|
||||
next;
|
||||
}
|
||||
|
||||
if (scalar grep {$port =~ m/^$_$/} @{setting('ignore_interfaces') || []}) {
|
||||
debug sprintf
|
||||
' [%s] interfaces - ignoring %s (%s) (config:ignore_interfaces)',
|
||||
$device->ip, $entry, $port;
|
||||
next;
|
||||
}
|
||||
|
||||
if (exists $i_ignore->{$entry}) {
|
||||
debug sprintf ' [%s] interfaces - ignoring %s (%s) (%s)',
|
||||
$device->ip, $entry, $port, $i_type->{$entry};
|
||||
next;
|
||||
}
|
||||
|
||||
my $lc = $i_lastchange->{$entry} || 0;
|
||||
if (not $dev_uptime_wrapped and $lc > $dev_uptime) {
|
||||
info sprintf ' [%s] interfaces - device uptime wrapped (%s) - correcting',
|
||||
$device->ip, $port;
|
||||
$device->uptime( $dev_uptime + 2**32 );
|
||||
$dev_uptime_wrapped = 1;
|
||||
}
|
||||
|
||||
if ($device->is_column_changed('uptime') and $lc) {
|
||||
if ($lc < $dev_uptime) {
|
||||
# ambiguous: lastchange could be sysUptime before or after wrap
|
||||
if ($dev_uptime > 30000 and $lc < 30000) {
|
||||
# uptime wrap more than 5min ago but lastchange within 5min
|
||||
# assume lastchange was directly after boot -> no action
|
||||
}
|
||||
else {
|
||||
# uptime wrap less than 5min ago or lastchange > 5min ago
|
||||
# to be on safe side, assume lastchange after counter wrap
|
||||
debug sprintf
|
||||
' [%s] interfaces - correcting LastChange for %s, assuming sysUptime wrap',
|
||||
$device->ip, $port;
|
||||
$lc += $dev_uptime_wrapped * 2**32;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$interfaces{$port} = {
|
||||
port => $port,
|
||||
descr => $i_descr->{$entry},
|
||||
up => $i_up->{$entry},
|
||||
up_admin => $i_up_admin->{$entry},
|
||||
mac => $i_mac->{$entry},
|
||||
speed => $i_speed->{$entry},
|
||||
mtu => $i_mtu->{$entry},
|
||||
name => Encode::decode('UTF-8', $i_name->{$entry}),
|
||||
duplex => $i_duplex->{$entry},
|
||||
duplex_admin => $i_duplex_admin->{$entry},
|
||||
stp => $i_stp_state->{$entry},
|
||||
type => $i_type->{$entry},
|
||||
vlan => $i_vlan->{$entry},
|
||||
pvid => $i_vlan->{$entry},
|
||||
is_master => 'false',
|
||||
slave_of => undef,
|
||||
lastchange => $lc,
|
||||
};
|
||||
}
|
||||
|
||||
# must do this after building %interfaces so that we can set is_master
|
||||
foreach my $sidx (keys %$agg_ports) {
|
||||
my $slave = $interfaces->{$sidx} or next;
|
||||
my $master = $interfaces->{ $agg_ports->{$sidx} } or next;
|
||||
next unless exists $interfaces{$slave} and exists $interfaces{$master};
|
||||
|
||||
$interfaces{$slave}->{slave_of} = $master;
|
||||
$interfaces{$master}->{is_master} = 'true';
|
||||
}
|
||||
|
||||
schema('netdisco')->resultset('DevicePort')->txn_do_locked(sub {
|
||||
my $gone = $device->ports->delete({keep_nodes => 1});
|
||||
debug sprintf ' [%s] interfaces - removed %d interfaces',
|
||||
$device->ip, $gone;
|
||||
$device->update_or_insert(undef, {for => 'update'});
|
||||
$device->ports->populate([values %interfaces]);
|
||||
|
||||
return Status->noop(sprintf ' [%s] interfaces - added %d new interfaces',
|
||||
$device->ip, scalar values %interfaces);
|
||||
});
|
||||
});
|
||||
|
||||
true;
|
||||
95
lib/App/Netdisco/Worker/Plugin/Discover/VLANs.pm
Normal file
95
lib/App/Netdisco/Worker/Plugin/Discover/VLANs.pm
Normal file
@@ -0,0 +1,95 @@
|
||||
package App::Netdisco::Worker::Plugin::Discover::VLANs;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP ();
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
my $device = $job->device;
|
||||
return unless $device->in_storage;
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||||
or return Status->defer("discover failed: could not SNMP connect to $device");
|
||||
|
||||
my $v_name = $snmp->v_name;
|
||||
my $v_index = $snmp->v_index;
|
||||
|
||||
# build device vlans suitable for DBIC
|
||||
my %v_seen = ();
|
||||
my @devicevlans;
|
||||
foreach my $entry (keys %$v_name) {
|
||||
my $vlan = $v_index->{$entry};
|
||||
next unless defined $vlan and $vlan;
|
||||
++$v_seen{$vlan};
|
||||
|
||||
push @devicevlans, {
|
||||
vlan => $vlan,
|
||||
description => $v_name->{$entry},
|
||||
last_discover => \'now()',
|
||||
};
|
||||
}
|
||||
|
||||
my $i_vlan = $snmp->i_vlan;
|
||||
my $i_vlan_membership = $snmp->i_vlan_membership;
|
||||
my $i_vlan_type = $snmp->i_vlan_type;
|
||||
my $interfaces = $snmp->interfaces;
|
||||
|
||||
# build device port vlans suitable for DBIC
|
||||
my @portvlans = ();
|
||||
foreach my $entry (keys %$i_vlan_membership) {
|
||||
my %port_vseen = ();
|
||||
my $port = $interfaces->{$entry};
|
||||
next unless defined $port;
|
||||
|
||||
my $type = $i_vlan_type->{$entry};
|
||||
|
||||
foreach my $vlan (@{ $i_vlan_membership->{$entry} }) {
|
||||
next unless defined $vlan and $vlan;
|
||||
next if ++$port_vseen{$vlan} > 1;
|
||||
|
||||
my $native = ((defined $i_vlan->{$entry}) and ($vlan eq $i_vlan->{$entry})) ? "t" : "f";
|
||||
push @portvlans, {
|
||||
port => $port,
|
||||
vlan => $vlan,
|
||||
native => $native,
|
||||
vlantype => $type,
|
||||
last_discover => \'now()',
|
||||
};
|
||||
|
||||
next if $v_seen{$vlan};
|
||||
|
||||
# also add an unnamed vlan to the device
|
||||
push @devicevlans, {
|
||||
vlan => $vlan,
|
||||
description => (sprintf "VLAN %d", $vlan),
|
||||
last_discover => \'now()',
|
||||
};
|
||||
++$v_seen{$vlan};
|
||||
}
|
||||
}
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->vlans->delete;
|
||||
debug sprintf ' [%s] vlans - removed %d device VLANs',
|
||||
$device->ip, $gone;
|
||||
$device->vlans->populate(\@devicevlans);
|
||||
debug sprintf ' [%s] vlans - added %d new device VLANs',
|
||||
$device->ip, scalar @devicevlans;
|
||||
});
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->port_vlans->delete;
|
||||
debug sprintf ' [%s] vlans - removed %d port VLANs',
|
||||
$device->ip, $gone;
|
||||
$device->port_vlans->populate(\@portvlans);
|
||||
|
||||
return Status->noop(sprintf ' [%s] vlans - added %d new port VLANs',
|
||||
$device->ip, scalar @portvlans);
|
||||
});
|
||||
});
|
||||
|
||||
true;
|
||||
85
lib/App/Netdisco/Worker/Plugin/Discover/Wireless.pm
Normal file
85
lib/App/Netdisco/Worker/Plugin/Discover/Wireless.pm
Normal file
@@ -0,0 +1,85 @@
|
||||
package App::Netdisco::Worker::Plugin::Discover::Wireless;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP ();
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
my $device = $job->device;
|
||||
return unless $device->in_storage;
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||||
or return Status->defer("discover failed: could not SNMP connect to $device");
|
||||
|
||||
my $ssidlist = $snmp->i_ssidlist;
|
||||
return unless scalar keys %$ssidlist;
|
||||
|
||||
my $interfaces = $snmp->interfaces;
|
||||
my $ssidbcast = $snmp->i_ssidbcast;
|
||||
my $ssidmac = $snmp->i_ssidmac;
|
||||
my $channel = $snmp->i_80211channel;
|
||||
my $power = $snmp->dot11_cur_tx_pwr_mw;
|
||||
|
||||
# build device ssid list suitable for DBIC
|
||||
my @ssids;
|
||||
foreach my $entry (keys %$ssidlist) {
|
||||
(my $iid = $entry) =~ s/\.\d+$//;
|
||||
my $port = $interfaces->{$iid};
|
||||
|
||||
if (not $port) {
|
||||
debug sprintf ' [%s] wireless - ignoring %s (no port mapping)',
|
||||
$device->ip, $iid;
|
||||
next;
|
||||
}
|
||||
|
||||
push @ssids, {
|
||||
port => $port,
|
||||
ssid => $ssidlist->{$entry},
|
||||
broadcast => $ssidbcast->{$entry},
|
||||
bssid => $ssidmac->{$entry},
|
||||
};
|
||||
}
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->ssids->delete;
|
||||
debug sprintf ' [%s] wireless - removed %d SSIDs',
|
||||
$device->ip, $gone;
|
||||
$device->ssids->populate(\@ssids);
|
||||
debug sprintf ' [%s] wireless - added %d new SSIDs',
|
||||
$device->ip, scalar @ssids;
|
||||
});
|
||||
|
||||
# build device channel list suitable for DBIC
|
||||
my @channels;
|
||||
foreach my $entry (keys %$channel) {
|
||||
my $port = $interfaces->{$entry};
|
||||
|
||||
if (not $port) {
|
||||
debug sprintf ' [%s] wireless - ignoring %s (no port mapping)',
|
||||
$device->ip, $entry;
|
||||
next;
|
||||
}
|
||||
|
||||
push @channels, {
|
||||
port => $port,
|
||||
channel => $channel->{$entry},
|
||||
power => $power->{$entry},
|
||||
};
|
||||
}
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
my $gone = $device->wireless_ports->delete;
|
||||
debug sprintf ' [%s] wireless - removed %d wireless channels',
|
||||
$device->ip, $gone;
|
||||
$device->wireless_ports->populate(\@channels);
|
||||
|
||||
return Status->noop(sprintf ' [%s] wireless - added %d new wireless channels',
|
||||
$device->ip, scalar @channels);
|
||||
});
|
||||
});
|
||||
|
||||
true;
|
||||
38
lib/App/Netdisco/Worker/Plugin/Discover/WithNodes.pm
Normal file
38
lib/App/Netdisco/Worker/Plugin/Discover/WithNodes.pm
Normal file
@@ -0,0 +1,38 @@
|
||||
package App::Netdisco::Worker::Plugin::Discover::WithNodes;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::JobQueue 'jq_insert';
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
register_worker({ phase => 'main' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
my $device = $job->device;
|
||||
|
||||
# if requested, and the device has not yet been
|
||||
# arpniped/macsucked, queue those jobs now
|
||||
return unless $device->in_storage
|
||||
and $job->subaction and $job->subaction eq 'with-nodes';
|
||||
|
||||
if (!defined $device->last_macsuck and $device->has_layer(2)) {
|
||||
jq_insert({
|
||||
device => $device->ip,
|
||||
action => 'macsuck',
|
||||
username => $job->username,
|
||||
userip => $job->userip,
|
||||
});
|
||||
}
|
||||
|
||||
if (!defined $device->last_arpnip and $device->has_layer(3)) {
|
||||
jq_insert({
|
||||
device => $device->ip,
|
||||
action => 'arpnip',
|
||||
username => $job->username,
|
||||
userip => $job->userip,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
true;
|
||||
31
lib/App/Netdisco/Worker/Plugin/DiscoverAll.pm
Normal file
31
lib/App/Netdisco/Worker/Plugin/DiscoverAll.pm
Normal file
@@ -0,0 +1,31 @@
|
||||
package App::Netdisco::Worker::Plugin::DiscoverAll;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::JobQueue qw/jq_queued jq_insert/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
register_worker({ phase => 'main' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
my %queued = map {$_ => 1} jq_queued('discover');
|
||||
my @devices = schema('netdisco')->resultset('Device')->search({
|
||||
-or => [ 'vendor' => undef, 'vendor' => { '!=' => 'netdisco' }],
|
||||
})->get_column('ip')->all;
|
||||
my @filtered_devices = grep {!exists $queued{$_}} @devices;
|
||||
|
||||
jq_insert([
|
||||
map {{
|
||||
device => $_,
|
||||
action => 'discover',
|
||||
username => $job->username,
|
||||
userip => $job->userip,
|
||||
}} (@filtered_devices)
|
||||
]);
|
||||
|
||||
return Status->done('Queued discover job for all devices');
|
||||
});
|
||||
|
||||
true;
|
||||
@@ -1,17 +1,14 @@
|
||||
package App::Netdisco::Backend::Worker::Poller::Expiry;
|
||||
package App::Netdisco::Worker::Plugin::Expire;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use Dancer qw/:moose :syntax :script/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
use App::Netdisco::Backend::Util ':all';
|
||||
use App::Netdisco::Util::Statistics 'update_stats';
|
||||
|
||||
use Role::Tiny;
|
||||
use namespace::clean;
|
||||
|
||||
# expire devices and nodes according to config
|
||||
sub expire {
|
||||
my ($self, $job) = @_;
|
||||
register_worker({ phase => 'main' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
if (setting('expire_devices') and setting('expire_devices') > 0) {
|
||||
schema('netdisco')->txn_do(sub {
|
||||
@@ -54,25 +51,7 @@ sub expire {
|
||||
# now update stats
|
||||
update_stats();
|
||||
|
||||
return job_done("Checked expiry and updated stats");
|
||||
}
|
||||
return Status->done('Checked expiry and updated stats');
|
||||
});
|
||||
|
||||
# expire nodes for a specific device
|
||||
sub expirenodes {
|
||||
my ($self, $job) = @_;
|
||||
|
||||
return job_error('Missing device') unless $job->device;
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
schema('netdisco')->resultset('Node')->search({
|
||||
switch => $job->device->ip,
|
||||
($job->port ? (port => $job->port) : ()),
|
||||
})->delete(
|
||||
($job->extra ? () : ({ archive_nodes => 1 }))
|
||||
);
|
||||
});
|
||||
|
||||
return job_done("Expired nodes for ". $job->device->ip);
|
||||
}
|
||||
|
||||
1;
|
||||
true;
|
||||
30
lib/App/Netdisco/Worker/Plugin/ExpireNodes.pm
Normal file
30
lib/App/Netdisco/Worker/Plugin/ExpireNodes.pm
Normal file
@@ -0,0 +1,30 @@
|
||||
package App::Netdisco::Worker::Plugin::ExpireNodes;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
register_worker({ phase => 'check' }, sub {
|
||||
return Status->error('Missing device (-d).')
|
||||
unless defined shift->device;
|
||||
return Status->done('ExpireNodes is able to run');
|
||||
});
|
||||
|
||||
register_worker({ phase => 'main' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
schema('netdisco')->resultset('Node')->search({
|
||||
switch => $job->device->ip,
|
||||
($job->port ? (port => $job->port) : ()),
|
||||
})->delete(
|
||||
($job->extra ? () : ({ archive_nodes => 1 }))
|
||||
);
|
||||
});
|
||||
|
||||
return Status->done('Expired nodes for '. $job->device->ip);
|
||||
});
|
||||
|
||||
true;
|
||||
14
lib/App/Netdisco/Worker/Plugin/Graph.pm
Normal file
14
lib/App/Netdisco/Worker/Plugin/Graph.pm
Normal file
@@ -0,0 +1,14 @@
|
||||
package App::Netdisco::Worker::Plugin::Graph;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Util::Graph ();
|
||||
|
||||
register_worker({ phase => 'main' }, sub {
|
||||
App::Netdisco::Util::Graph::graph();
|
||||
return Status->done('Generated graph data');
|
||||
});
|
||||
|
||||
true;
|
||||
43
lib/App/Netdisco/Worker/Plugin/Location.pm
Normal file
43
lib/App/Netdisco/Worker/Plugin/Location.pm
Normal file
@@ -0,0 +1,43 @@
|
||||
package App::Netdisco::Worker::Plugin::Location;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP;
|
||||
|
||||
register_worker({ phase => 'check' }, sub {
|
||||
return Status->error('Missing device (-d).')
|
||||
unless defined shift->device;
|
||||
return Status->done('Location is able to run');
|
||||
});
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
my ($device, $data) = map {$job->$_} qw/device extra/;
|
||||
|
||||
# snmp connect using rw community
|
||||
my $snmp = App::Netdisco::Transport::SNMP->writer_for($device)
|
||||
or return Status->defer("failed to connect to $device to update location");
|
||||
|
||||
my $rv = $snmp->set_location($data);
|
||||
|
||||
if (!defined $rv) {
|
||||
return Status->error(
|
||||
"failed to set location on $device: ". ($snmp->error || ''));
|
||||
}
|
||||
|
||||
# confirm the set happened
|
||||
$snmp->clear_cache;
|
||||
my $new_data = ($snmp->location || '');
|
||||
if ($new_data ne $data) {
|
||||
return Status->error("verify of location failed on $device: $new_data");
|
||||
}
|
||||
|
||||
# update netdisco DB
|
||||
$device->update({location => $data});
|
||||
|
||||
return Status->done("Updated location on $device to [$data]");
|
||||
});
|
||||
|
||||
true;
|
||||
31
lib/App/Netdisco/Worker/Plugin/Macsuck.pm
Normal file
31
lib/App/Netdisco/Worker/Plugin/Macsuck.pm
Normal file
@@ -0,0 +1,31 @@
|
||||
package App::Netdisco::Worker::Plugin::Macsuck;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Util::Device 'is_macsuckable_now';
|
||||
|
||||
register_worker({ phase => 'check' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
my $device = $job->device;
|
||||
|
||||
return Status->error('macsuck failed: unable to interpret device param')
|
||||
unless defined $device;
|
||||
|
||||
return Status->error("macsuck skipped: $device not yet discovered")
|
||||
unless $device->in_storage;
|
||||
|
||||
return Status->defer("macsuck skipped: $device is pseudo-device")
|
||||
if $device->is_pseudo;
|
||||
|
||||
return Status->defer("arpnip skipped: $device has no layer 2 capability")
|
||||
unless $device->has_layer(2);
|
||||
|
||||
return Status->defer("macsuck deferred: $device is not macsuckable")
|
||||
unless is_macsuckable_now($device);
|
||||
|
||||
return Status->done('Macsuck is able to run.');
|
||||
});
|
||||
|
||||
true;
|
||||
@@ -1,62 +1,25 @@
|
||||
package App::Netdisco::Core::Macsuck;
|
||||
package App::Netdisco::Worker::Plugin::Macsuck::Nodes;
|
||||
|
||||
use Dancer qw/:syntax :script/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP ();
|
||||
use App::Netdisco::Util::Permission 'check_acl_no';
|
||||
use App::Netdisco::Util::PortMAC 'get_port_macs';
|
||||
use App::Netdisco::Util::Device 'match_devicetype';
|
||||
use App::Netdisco::Util::Node 'check_mac';
|
||||
use App::Netdisco::Util::SNMP 'snmp_comm_reindex';
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
use Time::HiRes 'gettimeofday';
|
||||
use Scope::Guard 'guard';
|
||||
|
||||
use base 'Exporter';
|
||||
our @EXPORT = ();
|
||||
our @EXPORT_OK = qw/
|
||||
do_macsuck
|
||||
store_node
|
||||
store_wireless_client_info
|
||||
/;
|
||||
our %EXPORT_TAGS = (all => \@EXPORT_OK);
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
=head1 NAME
|
||||
|
||||
App::Netdisco::Core::Macsuck
|
||||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
Helper subroutines to support parts of the Netdisco application.
|
||||
|
||||
There are no default exports, however the C<:all> tag will export all
|
||||
subroutines.
|
||||
|
||||
=head1 EXPORT_OK
|
||||
|
||||
=head2 do_macsuck( $device, $snmp )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, connect to a
|
||||
device and discover the MAC addresses listed against each physical port
|
||||
without a neighbor.
|
||||
|
||||
If the device has VLANs, C<do_macsuck> will walk each VLAN to get the MAC
|
||||
addresses from there.
|
||||
|
||||
It will also gather wireless client information if C<store_wireless_clients>
|
||||
configuration setting is enabled.
|
||||
|
||||
=cut
|
||||
|
||||
sub do_macsuck {
|
||||
my ($device, $snmp) = @_;
|
||||
|
||||
unless ($device->in_storage) {
|
||||
debug sprintf
|
||||
' [%s] macsuck - skipping device not yet discovered',
|
||||
$device->ip;
|
||||
return;
|
||||
}
|
||||
|
||||
my $ip = $device->ip;
|
||||
my $device = $job->device;
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||||
or return Status->defer("macsuck failed: could not SNMP connect to $device");
|
||||
|
||||
# would be possible just to use now() on updated records, but by using this
|
||||
# same value for them all, we can if we want add a job at the end to
|
||||
@@ -64,9 +27,6 @@ sub do_macsuck {
|
||||
my $now = 'to_timestamp('. (join '.', gettimeofday) .')';
|
||||
my $total_nodes = 0;
|
||||
|
||||
# do this before we start messing with the snmp community string
|
||||
store_wireless_client_info($device, $snmp, $now);
|
||||
|
||||
# cache the device ports to save hitting the database for many single rows
|
||||
my $device_ports = {map {($_->port => $_)}
|
||||
$device->ports(undef, {prefetch => {neighbor_alias => 'device'}})->all};
|
||||
@@ -74,14 +34,18 @@ sub do_macsuck {
|
||||
my $interfaces = $snmp->interfaces;
|
||||
|
||||
# get forwarding table data via basic snmp connection
|
||||
my $fwtable = _walk_fwtable($device, $snmp, $interfaces, $port_macs, $device_ports);
|
||||
my $fwtable = walk_fwtable($device, $interfaces, $port_macs, $device_ports);
|
||||
|
||||
# ...then per-vlan if supported
|
||||
my @vlan_list = _get_vlan_list($device, $snmp);
|
||||
foreach my $vlan (@vlan_list) {
|
||||
my @vlan_list = get_vlan_list($device);
|
||||
{
|
||||
my $guard = guard { snmp_comm_reindex($snmp, $device, 0) };
|
||||
foreach my $vlan (@vlan_list) {
|
||||
snmp_comm_reindex($snmp, $device, $vlan);
|
||||
my $pv_fwtable = _walk_fwtable($device, $snmp, $interfaces, $port_macs, $device_ports, $vlan);
|
||||
my $pv_fwtable =
|
||||
walk_fwtable($device, $interfaces, $port_macs, $device_ports, $vlan);
|
||||
$fwtable = {%$fwtable, %$pv_fwtable};
|
||||
}
|
||||
}
|
||||
|
||||
# now it's time to call store_node for every node discovered
|
||||
@@ -91,7 +55,7 @@ sub do_macsuck {
|
||||
foreach my $vlan (reverse sort keys %$fwtable) {
|
||||
foreach my $port (keys %{ $fwtable->{$vlan} }) {
|
||||
debug sprintf ' [%s] macsuck - port %s vlan %s : %s nodes',
|
||||
$ip, $port, $vlan, scalar keys %{ $fwtable->{$vlan}->{$port} };
|
||||
$device->ip, $port, $vlan, scalar keys %{ $fwtable->{$vlan}->{$port} };
|
||||
|
||||
# make sure this port is UP in netdisco (unless it's a lag master,
|
||||
# because we can still see nodes without a functioning aggregate)
|
||||
@@ -105,30 +69,31 @@ sub do_macsuck {
|
||||
for keys %{ $fwtable->{0} };
|
||||
|
||||
++$total_nodes;
|
||||
store_node($ip, $vlan, $port, $mac, $now);
|
||||
store_node($device->ip, $vlan, $port, $mac, $now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debug sprintf ' [%s] macsuck - %s updated forwarding table entries',
|
||||
$ip, $total_nodes;
|
||||
$device->ip, $total_nodes;
|
||||
|
||||
# a use for $now ... need to archive dissapeared nodes
|
||||
my $archived = 0;
|
||||
|
||||
if (setting('node_freshness')) {
|
||||
$archived = schema('netdisco')->resultset('Node')->search({
|
||||
switch => $ip,
|
||||
time_last => \[ "< ($now - ?::interval)",
|
||||
setting('node_freshness') .' minutes' ],
|
||||
})->update({ active => \'false' });
|
||||
$archived = schema('netdisco')->resultset('Node')->search({
|
||||
switch => $device->ip,
|
||||
time_last => \[ "< ($now - ?::interval)",
|
||||
setting('node_freshness') .' minutes' ],
|
||||
})->update({ active => \'false' });
|
||||
}
|
||||
|
||||
debug sprintf ' [%s] macsuck - removed %d fwd table entries to archive',
|
||||
$ip, $archived;
|
||||
$device->ip, $archived;
|
||||
|
||||
$device->update({last_macsuck => \$now});
|
||||
}
|
||||
return Status->done("Ended macsuck for $device");
|
||||
});
|
||||
|
||||
=head2 store_node( $ip, $vlan, $port, $mac, $now? )
|
||||
|
||||
@@ -183,8 +148,11 @@ sub store_node {
|
||||
}
|
||||
|
||||
# return a list of vlan numbers which are OK to macsuck on this device
|
||||
sub _get_vlan_list {
|
||||
my ($device, $snmp) = @_;
|
||||
sub get_vlan_list {
|
||||
my $device = shift;
|
||||
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||||
or return (); # already checked!
|
||||
|
||||
return () unless $snmp->cisco_comm_indexing;
|
||||
|
||||
@@ -295,11 +263,14 @@ sub _get_vlan_list {
|
||||
|
||||
# walks the forwarding table (BRIDGE-MIB) for the device and returns a
|
||||
# table of node entries.
|
||||
sub _walk_fwtable {
|
||||
my ($device, $snmp, $interfaces, $port_macs, $device_ports, $comm_vlan) = @_;
|
||||
sub walk_fwtable {
|
||||
my ($device, $interfaces, $port_macs, $device_ports, $comm_vlan) = @_;
|
||||
my $skiplist = {}; # ports through which we can see another device
|
||||
my $cache = {};
|
||||
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||||
or return $cache; # already checked!
|
||||
|
||||
my $fw_mac = $snmp->fw_mac;
|
||||
my $fw_port = $snmp->fw_port;
|
||||
my $fw_vlan = $snmp->qb_fw_vlan;
|
||||
@@ -368,8 +339,8 @@ sub _walk_fwtable {
|
||||
# do not expose MAC address tables via SNMP. relies on prefetched
|
||||
# neighbors otherwise it would kill the DB with device lookups.
|
||||
my $neigh_cannot_macsuck = eval { # can fail
|
||||
check_acl_no(($device_port->neighbor || "0 but true"), 'macsuck_unsupported') ||
|
||||
match_devicetype($device_port->remote_type, 'macsuck_unsupported_type') };
|
||||
check_acl_no(($device_port->neighbor || "0 but true"), 'macsuck_unsupported') ||
|
||||
match_devicetype($device_port->remote_type, 'macsuck_unsupported_type') };
|
||||
|
||||
if ($device_port->is_uplink) {
|
||||
if ($neigh_cannot_macsuck) {
|
||||
@@ -455,90 +426,4 @@ sub _walk_fwtable {
|
||||
return $cache;
|
||||
}
|
||||
|
||||
=head2 store_wireless_client_info( $device, $snmp, $now? )
|
||||
|
||||
Given a Device database object, and a working SNMP connection, connect to a
|
||||
device and discover 802.11 related information for all connected wireless
|
||||
clients.
|
||||
|
||||
If the device doesn't support the 802.11 MIBs, then this will silently return.
|
||||
|
||||
If the device does support the 802.11 MIBs but Netdisco's configuration
|
||||
does not permit polling (C<store_wireless_clients> must be true) then a debug
|
||||
message is logged and the subroutine returns.
|
||||
|
||||
Otherwise, client information is gathered and stored to the database.
|
||||
|
||||
Optionally, a third argument can be the literal string passed to the time_last
|
||||
field of the database record. If not provided, it defauls to C<now()>.
|
||||
|
||||
=cut
|
||||
|
||||
sub store_wireless_client_info {
|
||||
my ($device, $snmp, $now) = @_;
|
||||
$now ||= 'now()';
|
||||
|
||||
my $cd11_txrate = $snmp->cd11_txrate;
|
||||
return unless $cd11_txrate and scalar keys %$cd11_txrate;
|
||||
|
||||
if (setting('store_wireless_clients')) {
|
||||
debug sprintf ' [%s] macsuck - gathering wireless client info',
|
||||
$device->ip;
|
||||
}
|
||||
else {
|
||||
debug sprintf ' [%s] macsuck - dot11 info available but skipped due to config',
|
||||
$device->ip;
|
||||
return;
|
||||
}
|
||||
|
||||
my $cd11_rateset = $snmp->cd11_rateset();
|
||||
my $cd11_uptime = $snmp->cd11_uptime();
|
||||
my $cd11_sigstrength = $snmp->cd11_sigstrength();
|
||||
my $cd11_sigqual = $snmp->cd11_sigqual();
|
||||
my $cd11_mac = $snmp->cd11_mac();
|
||||
my $cd11_port = $snmp->cd11_port();
|
||||
my $cd11_rxpkt = $snmp->cd11_rxpkt();
|
||||
my $cd11_txpkt = $snmp->cd11_txpkt();
|
||||
my $cd11_rxbyte = $snmp->cd11_rxbyte();
|
||||
my $cd11_txbyte = $snmp->cd11_txbyte();
|
||||
my $cd11_ssid = $snmp->cd11_ssid();
|
||||
|
||||
while (my ($idx, $txrates) = each %$cd11_txrate) {
|
||||
my $rates = $cd11_rateset->{$idx};
|
||||
my $mac = $cd11_mac->{$idx};
|
||||
next unless defined $mac; # avoid null entries
|
||||
# there can be more rows in txrate than other tables
|
||||
|
||||
my $txrate = defined $txrates->[$#$txrates]
|
||||
? int($txrates->[$#$txrates])
|
||||
: undef;
|
||||
|
||||
my $maxrate = defined $rates->[$#$rates]
|
||||
? int($rates->[$#$rates])
|
||||
: undef;
|
||||
|
||||
my $ssid = $cd11_ssid->{$idx} || 'unknown';
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
schema('netdisco')->resultset('NodeWireless')
|
||||
->search({ 'me.mac' => $mac, 'me.ssid' => $ssid })
|
||||
->update_or_create({
|
||||
txrate => $txrate,
|
||||
maxrate => $maxrate,
|
||||
uptime => $cd11_uptime->{$idx},
|
||||
rxpkt => $cd11_rxpkt->{$idx},
|
||||
txpkt => $cd11_txpkt->{$idx},
|
||||
rxbyte => $cd11_rxbyte->{$idx},
|
||||
txbyte => $cd11_txbyte->{$idx},
|
||||
sigqual => $cd11_sigqual->{$idx},
|
||||
sigstrength => $cd11_sigstrength->{$idx},
|
||||
time_last => \$now,
|
||||
}, {
|
||||
order_by => [qw/mac ssid/],
|
||||
for => 'update',
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
1;
|
||||
true;
|
||||
82
lib/App/Netdisco/Worker/Plugin/Macsuck/WirelessNodes.pm
Normal file
82
lib/App/Netdisco/Worker/Plugin/Macsuck/WirelessNodes.pm
Normal file
@@ -0,0 +1,82 @@
|
||||
package App::Netdisco::Worker::Plugin::Macsuck::WirelessNodes;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP ();
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
use Time::HiRes 'gettimeofday';
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
my $device = $job->device;
|
||||
my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
|
||||
or return Status->defer("macsuck failed: could not SNMP connect to $device");
|
||||
|
||||
my $now = 'to_timestamp('. (join '.', gettimeofday) .')';
|
||||
|
||||
my $cd11_txrate = $snmp->cd11_txrate;
|
||||
return unless $cd11_txrate and scalar keys %$cd11_txrate;
|
||||
|
||||
if (setting('store_wireless_clients')) {
|
||||
debug sprintf ' [%s] macsuck - gathering wireless client info',
|
||||
$device->ip;
|
||||
}
|
||||
else {
|
||||
return Status->noop(sprintf ' [%s] macsuck - dot11 info available but skipped due to config',
|
||||
$device->ip);
|
||||
}
|
||||
|
||||
my $cd11_rateset = $snmp->cd11_rateset();
|
||||
my $cd11_uptime = $snmp->cd11_uptime();
|
||||
my $cd11_sigstrength = $snmp->cd11_sigstrength();
|
||||
my $cd11_sigqual = $snmp->cd11_sigqual();
|
||||
my $cd11_mac = $snmp->cd11_mac();
|
||||
my $cd11_port = $snmp->cd11_port();
|
||||
my $cd11_rxpkt = $snmp->cd11_rxpkt();
|
||||
my $cd11_txpkt = $snmp->cd11_txpkt();
|
||||
my $cd11_rxbyte = $snmp->cd11_rxbyte();
|
||||
my $cd11_txbyte = $snmp->cd11_txbyte();
|
||||
my $cd11_ssid = $snmp->cd11_ssid();
|
||||
|
||||
while (my ($idx, $txrates) = each %$cd11_txrate) {
|
||||
my $rates = $cd11_rateset->{$idx};
|
||||
my $mac = $cd11_mac->{$idx};
|
||||
next unless defined $mac; # avoid null entries
|
||||
# there can be more rows in txrate than other tables
|
||||
|
||||
my $txrate = defined $txrates->[$#$txrates]
|
||||
? int($txrates->[$#$txrates])
|
||||
: undef;
|
||||
|
||||
my $maxrate = defined $rates->[$#$rates]
|
||||
? int($rates->[$#$rates])
|
||||
: undef;
|
||||
|
||||
my $ssid = $cd11_ssid->{$idx} || 'unknown';
|
||||
|
||||
schema('netdisco')->txn_do(sub {
|
||||
schema('netdisco')->resultset('NodeWireless')
|
||||
->search({ 'me.mac' => $mac, 'me.ssid' => $ssid })
|
||||
->update_or_create({
|
||||
txrate => $txrate,
|
||||
maxrate => $maxrate,
|
||||
uptime => $cd11_uptime->{$idx},
|
||||
rxpkt => $cd11_rxpkt->{$idx},
|
||||
txpkt => $cd11_txpkt->{$idx},
|
||||
rxbyte => $cd11_rxbyte->{$idx},
|
||||
txbyte => $cd11_txbyte->{$idx},
|
||||
sigqual => $cd11_sigqual->{$idx},
|
||||
sigstrength => $cd11_sigstrength->{$idx},
|
||||
time_last => \$now,
|
||||
}, {
|
||||
order_by => [qw/mac ssid/],
|
||||
for => 'update',
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
true;
|
||||
31
lib/App/Netdisco/Worker/Plugin/Macwalk.pm
Normal file
31
lib/App/Netdisco/Worker/Plugin/Macwalk.pm
Normal file
@@ -0,0 +1,31 @@
|
||||
package App::Netdisco::Worker::Plugin::Macwalk;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::JobQueue qw/jq_queued jq_insert/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
register_worker({ phase => 'main' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
my %queued = map {$_ => 1} jq_queued('macsuck');
|
||||
my @devices = schema('netdisco')->resultset('Device')->search({
|
||||
-or => [ 'vendor' => undef, 'vendor' => { '!=' => 'netdisco' }],
|
||||
})->has_layer('2')->get_column('ip')->all;
|
||||
my @filtered_devices = grep {!exists $queued{$_}} @devices;
|
||||
|
||||
jq_insert([
|
||||
map {{
|
||||
device => $_,
|
||||
action => 'macsuck',
|
||||
username => $job->username,
|
||||
userip => $job->userip,
|
||||
}} (@filtered_devices)
|
||||
]);
|
||||
|
||||
return Status->done('Queued macsuck job for all devices');
|
||||
});
|
||||
|
||||
true;
|
||||
14
lib/App/Netdisco/Worker/Plugin/Monitor.pm
Normal file
14
lib/App/Netdisco/Worker/Plugin/Monitor.pm
Normal file
@@ -0,0 +1,14 @@
|
||||
package App::Netdisco::Worker::Plugin::Monitor;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Util::NodeMonitor ();
|
||||
|
||||
register_worker({ phase => 'main' }, sub {
|
||||
App::Netdisco::Util::NodeMonitor::monitor();
|
||||
return Status->done('Generated monitor data');
|
||||
});
|
||||
|
||||
true;
|
||||
21
lib/App/Netdisco/Worker/Plugin/Nbtstat.pm
Normal file
21
lib/App/Netdisco/Worker/Plugin/Nbtstat.pm
Normal file
@@ -0,0 +1,21 @@
|
||||
package App::Netdisco::Worker::Plugin::Nbtstat;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Util::Device 'is_macsuckable';
|
||||
|
||||
register_worker({ phase => 'check' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
return Status->error('nbtstat failed: unable to interpret device param')
|
||||
unless defined $job->device;
|
||||
|
||||
return Status->defer(sprintf 'nbtstat deferred: %s is not macsuckable', $job->device->ip)
|
||||
unless is_macsuckable($job->device);
|
||||
|
||||
return Status->done('Nbtstat is able to run.');
|
||||
});
|
||||
|
||||
true;
|
||||
50
lib/App/Netdisco/Worker/Plugin/Nbtstat/Core.pm
Normal file
50
lib/App/Netdisco/Worker/Plugin/Nbtstat/Core.pm
Normal file
@@ -0,0 +1,50 @@
|
||||
package App::Netdisco::Worker::Plugin::Nbtstat::Core;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Util::Nbtstat qw/nbtstat_resolve_async store_nbt/;
|
||||
use App::Netdisco::Util::Node 'is_nbtstatable';
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
use Time::HiRes 'gettimeofday';
|
||||
|
||||
register_worker({ phase => 'main' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
my $host = $job->device->ip;
|
||||
|
||||
# get list of nodes on device
|
||||
my $interval = (setting('nbtstat_max_age') || 7) . ' day';
|
||||
my $rs = schema('netdisco')->resultset('NodeIp')->search({
|
||||
-bool => 'me.active',
|
||||
-bool => 'nodes.active',
|
||||
'nodes.switch' => $host,
|
||||
'me.time_last' => \[ '>= now() - ?::interval', $interval ],
|
||||
},{
|
||||
join => 'nodes',
|
||||
columns => 'ip',
|
||||
distinct => 1,
|
||||
})->ip_version(4);
|
||||
|
||||
my @ips = map {+{'ip' => $_}}
|
||||
grep { is_nbtstatable( $_ ) }
|
||||
$rs->get_column('ip')->all;
|
||||
|
||||
# Unless we have IPs don't bother
|
||||
if (scalar @ips) {
|
||||
my $now = 'to_timestamp('. (join '.', gettimeofday) .')';
|
||||
my $resolved_nodes = nbtstat_resolve_async(\@ips);
|
||||
|
||||
# update node_nbt with status entries
|
||||
foreach my $result (@$resolved_nodes) {
|
||||
if (defined $result->{'nbname'}) {
|
||||
store_nbt($result, $now);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Status->done("Ended nbtstat for $host");
|
||||
});
|
||||
|
||||
true;
|
||||
|
||||
31
lib/App/Netdisco/Worker/Plugin/Nbtwalk.pm
Normal file
31
lib/App/Netdisco/Worker/Plugin/Nbtwalk.pm
Normal file
@@ -0,0 +1,31 @@
|
||||
package App::Netdisco::Worker::Plugin::Nbtwalk;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::JobQueue qw/jq_queued jq_insert/;
|
||||
use Dancer::Plugin::DBIC 'schema';
|
||||
|
||||
register_worker({ phase => 'main' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
|
||||
my %queued = map {$_ => 1} jq_queued('nbtstat');
|
||||
my @devices = schema('netdisco')->resultset('Device')->search({
|
||||
-or => [ 'vendor' => undef, 'vendor' => { '!=' => 'netdisco' }],
|
||||
})->has_layer('2')->get_column('ip')->all;
|
||||
my @filtered_devices = grep {!exists $queued{$_}} @devices;
|
||||
|
||||
jq_insert([
|
||||
map {{
|
||||
device => $_,
|
||||
action => 'nbtstat',
|
||||
username => $job->username,
|
||||
userip => $job->userip,
|
||||
}} (@filtered_devices)
|
||||
]);
|
||||
|
||||
return Status->done('Queued nbtstat job for all devices');
|
||||
});
|
||||
|
||||
true;
|
||||
76
lib/App/Netdisco/Worker/Plugin/PortControl.pm
Normal file
76
lib/App/Netdisco/Worker/Plugin/PortControl.pm
Normal file
@@ -0,0 +1,76 @@
|
||||
package App::Netdisco::Worker::Plugin::PortControl;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP;
|
||||
use App::Netdisco::Util::Port ':all';
|
||||
|
||||
register_worker({ phase => 'check' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
return Status->error('Missing device (-d).') unless defined $job->device;
|
||||
return Status->error('Missing port (-p).') unless defined $job->port;
|
||||
return Status->error('Missing status (-e).') unless defined $job->subaction;
|
||||
|
||||
vars->{'port'} = get_port($job->device, $job->port)
|
||||
or return Status->error(sprintf "Unknown port name [%s] on device %s",
|
||||
$job->port, $job->device);
|
||||
|
||||
my $vlan_reconfig_check = vlan_reconfig_check(vars->{'port'});
|
||||
return Status->error("Cannot alter vlan: $vlan_reconfig_check")
|
||||
if $vlan_reconfig_check;
|
||||
|
||||
return Status->done('PortControl is able to run');
|
||||
});
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
my ($device, $pn) = map {$job->$_} qw/device port/;
|
||||
|
||||
# need to remove "-other" which appears for power/portcontrol
|
||||
(my $sa = $job->subaction) =~ s/-\w+//;
|
||||
$job->subaction($sa);
|
||||
|
||||
if ($sa eq 'bounce') {
|
||||
$job->subaction('down');
|
||||
my $status = _action($job);
|
||||
return $status if $status->not_ok;
|
||||
$job->subaction('up');
|
||||
}
|
||||
|
||||
return _action($job);
|
||||
});
|
||||
|
||||
sub _action {
|
||||
my $job = shift;
|
||||
my ($device, $pn, $data) = map {$job->$_} qw/device port subaction/;
|
||||
|
||||
# snmp connect using rw community
|
||||
my $snmp = App::Netdisco::Transport::SNMP->writer_for($device)
|
||||
or return Status->defer("failed to connect to $device to update up_admin");
|
||||
|
||||
my $iid = get_iid($snmp, vars->{'port'})
|
||||
or return Status->error("Failed to get port ID for [$pn] from $device");
|
||||
|
||||
my $rv = $snmp->set_i_up_admin($data, $iid);
|
||||
|
||||
if (!defined $rv) {
|
||||
return Status->error(sprintf 'Failed to set [%s] up_admin to [%s] on $device: %s',
|
||||
$pn, $data, ($snmp->error || ''));
|
||||
}
|
||||
|
||||
# confirm the set happened
|
||||
$snmp->clear_cache;
|
||||
my $state = ($snmp->i_up_admin($iid) || '');
|
||||
if (ref {} ne ref $state or $state->{$iid} ne $data) {
|
||||
return Status->error("Verify of [$pn] up_admin failed on $device");
|
||||
}
|
||||
|
||||
# update netdisco DB
|
||||
vars->{'port'}->update({up_admin => $data});
|
||||
|
||||
return Status->done("Updated [$pn] up_admin on [$device] to [$data]");
|
||||
}
|
||||
|
||||
true;
|
||||
54
lib/App/Netdisco/Worker/Plugin/PortName.pm
Normal file
54
lib/App/Netdisco/Worker/Plugin/PortName.pm
Normal file
@@ -0,0 +1,54 @@
|
||||
package App::Netdisco::Worker::Plugin::PortName;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP;
|
||||
use App::Netdisco::Util::Port ':all';
|
||||
|
||||
register_worker({ phase => 'check' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
return Status->error('Missing device (-d).') unless defined $job->device;
|
||||
return Status->error('Missing port (-p).') unless defined $job->port;
|
||||
return Status->error('Missing name (-e).') unless defined $job->subaction;
|
||||
|
||||
vars->{'port'} = get_port($job->device, $job->port)
|
||||
or return Status->error(sprintf "Unknown port name [%s] on device %s",
|
||||
$job->port, $job->device);
|
||||
|
||||
return Status->done('PortName is able to run');
|
||||
});
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
my ($device, $pn, $data) = map {$job->$_} qw/device port extra/;
|
||||
|
||||
# snmp connect using rw community
|
||||
my $snmp = App::Netdisco::Transport::SNMP->writer_for($device)
|
||||
or return Status->defer("failed to connect to $device to update alias");
|
||||
|
||||
my $iid = get_iid($snmp, vars->{'port'})
|
||||
or return Status->error("Failed to get port ID for [$pn] from $device");
|
||||
|
||||
my $rv = $snmp->set_i_alias($data, $iid);
|
||||
|
||||
if (!defined $rv) {
|
||||
return Status->error(sprintf 'Failed to set [%s] alias to [%s] on $device: %s',
|
||||
$pn, $data, ($snmp->error || ''));
|
||||
}
|
||||
|
||||
# confirm the set happened
|
||||
$snmp->clear_cache;
|
||||
my $state = ($snmp->i_alias($iid) || '');
|
||||
if (ref {} ne ref $state or $state->{$iid} ne $data) {
|
||||
return Status->error("Verify of [$pn] alias failed on $device");
|
||||
}
|
||||
|
||||
# update netdisco DB
|
||||
vars->{'port'}->update({name => $data});
|
||||
|
||||
return Status->done("Updated [$pn] alias on [$device] to [$data]");
|
||||
});
|
||||
|
||||
true;
|
||||
69
lib/App/Netdisco/Worker/Plugin/Power.pm
Normal file
69
lib/App/Netdisco/Worker/Plugin/Power.pm
Normal file
@@ -0,0 +1,69 @@
|
||||
package App::Netdisco::Worker::Plugin::Power;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP;
|
||||
use App::Netdisco::Util::Port ':all';
|
||||
|
||||
register_worker({ phase => 'check' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
return Status->error('Missing device (-d).') unless defined $job->device;
|
||||
return Status->error('Missing port (-p).') unless defined $job->port;
|
||||
return Status->error('Missing status (-e).') unless defined $job->subaction;
|
||||
|
||||
vars->{'port'} = get_port($job->device, $job->port)
|
||||
or return Status->error(sprintf "Unknown port name [%s] on device %s",
|
||||
$job->port, $job->device);
|
||||
|
||||
my $vlan_reconfig_check = vlan_reconfig_check(vars->{'port'});
|
||||
return Status->error("Cannot alter vlan: $vlan_reconfig_check")
|
||||
if $vlan_reconfig_check;
|
||||
|
||||
return Status->error("No PoE service on port [$pn] on device $device")
|
||||
unless vars->{'port'}->power;
|
||||
|
||||
return Status->done('Power is able to run');
|
||||
});
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
my ($device, $pn) = map {$job->$_} qw/device port/;
|
||||
|
||||
# munge data
|
||||
(my $data = $job->subaction) =~ s/-\w+//; # remove -other
|
||||
$data = 'true' if $data =~ m/^(on|yes|up)$/;
|
||||
$data = 'false' if $data =~ m/^(off|no|down)$/;
|
||||
|
||||
# snmp connect using rw community
|
||||
my $snmp = App::Netdisco::Transport::SNMP->writer_for($device)
|
||||
or return Status->defer("failed to connect to $device to update vlan");
|
||||
|
||||
my $powerid = get_powerid($snmp, vars->{'port'})
|
||||
or return Status->error("failed to get power ID for [$pn] from $device");
|
||||
|
||||
my $rv = $snmp->set_peth_port_admin($data, $powerid);
|
||||
|
||||
if (!defined $rv) {
|
||||
return Status->error(sprintf 'failed to set [%s] power to [%s] on [%s]: %s',
|
||||
$pn, $data, $device, ($snmp->error || ''));
|
||||
}
|
||||
|
||||
# confirm the set happened
|
||||
$snmp->clear_cache;
|
||||
my $state = ($snmp->peth_port_admin($powerid) || '');
|
||||
if (ref {} ne ref $state or $state->{$powerid} ne $data) {
|
||||
return Status->error("Verify of [$pn] power failed on $device");
|
||||
}
|
||||
|
||||
# update netdisco DB
|
||||
vars->{'port'}->power->update({
|
||||
admin => $data,
|
||||
status => ($data eq 'false' ? 'disabled' : 'searching'),
|
||||
});
|
||||
|
||||
return Status->done("Updated [$pn] power status on $device to [$data]");
|
||||
});
|
||||
|
||||
true;
|
||||
39
lib/App/Netdisco/Worker/Plugin/Psql.pm
Normal file
39
lib/App/Netdisco/Worker/Plugin/Psql.pm
Normal file
@@ -0,0 +1,39 @@
|
||||
package App::Netdisco::Worker::Plugin::Psql;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
register_worker({ phase => 'main' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
my ($device, $port, $extra) = map {$job->$_} qw/device port extra/;
|
||||
|
||||
my $name = ($ENV{NETDISCO_DBNAME} || setting('database')->{name} || 'netdisco');
|
||||
my $host = setting('database')->{host};
|
||||
my $user = setting('database')->{user};
|
||||
my $pass = setting('database')->{pass};
|
||||
|
||||
my $portnum = undef;
|
||||
if ($host and $host =~ m/([^;]+);port=(\d+)/) {
|
||||
$host = $1;
|
||||
$portnum = $2;
|
||||
}
|
||||
|
||||
$ENV{PGHOST} = $host if $host;
|
||||
$ENV{PGPORT} = $portnum if defined $portnum;
|
||||
$ENV{PGDATABASE} = $name;
|
||||
$ENV{PGUSER} = $user;
|
||||
$ENV{PGPASSWORD} = $pass;
|
||||
$ENV{PGCLIENTENCODING} = 'UTF8';
|
||||
|
||||
if ($extra) {
|
||||
system('psql', '-c', $extra);
|
||||
}
|
||||
else {
|
||||
system('psql');
|
||||
}
|
||||
|
||||
return Status->done('psql session closed.');
|
||||
});
|
||||
|
||||
true;
|
||||
39
lib/App/Netdisco/Worker/Plugin/Renumber.pm
Normal file
39
lib/App/Netdisco/Worker/Plugin/Renumber.pm
Normal file
@@ -0,0 +1,39 @@
|
||||
package App::Netdisco::Worker::Plugin::Renumber;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use NetAddr::IP qw/:rfc3021 :lower/;
|
||||
use App::Netdisco::Util::Device qw/get_device renumber_device/;
|
||||
|
||||
register_worker({ phase => 'check' }, sub {
|
||||
return Status->error('Missing device (-d).')
|
||||
unless defined shift->device;
|
||||
|
||||
my $new_ip = NetAddr::IP->new($job->extra);
|
||||
unless ($new_ip and $new_ip->addr ne '0.0.0.0') {
|
||||
return Status->error("Bad host or IP: ".($job->extra || '0.0.0.0'));
|
||||
}
|
||||
|
||||
return Status->done('Renumber is able to run');
|
||||
});
|
||||
|
||||
register_worker({ phase => 'main' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
my ($device, $port, $extra) = map {$job->$_} qw/device port extra/;
|
||||
|
||||
my $old_ip = $device->ip;
|
||||
my $new_ip = NetAddr::IP->new($extra);
|
||||
|
||||
my $new_dev = get_device($new_ip->addr);
|
||||
if ($new_dev and $new_dev->in_storage and ($new_dev->ip ne $device->ip)) {
|
||||
return Status->error(sprintf "Already know new device as: %s.", $new_dev->ip);
|
||||
}
|
||||
|
||||
renumber_device($device, $new_ip);
|
||||
return Status->done(sprintf 'Renumbered device %s to %s (%s).',
|
||||
$device->ip, $new_ip, ($device->dns || ''));
|
||||
});
|
||||
|
||||
true;
|
||||
37
lib/App/Netdisco/Worker/Plugin/Show.pm
Normal file
37
lib/App/Netdisco/Worker/Plugin/Show.pm
Normal file
@@ -0,0 +1,37 @@
|
||||
package App::Netdisco::Worker::Plugin::Show;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use Data::Printer ();
|
||||
use App::Netdisco::Transport::SNMP;
|
||||
|
||||
register_worker({ phase => 'check' }, sub {
|
||||
return Status->error('Missing device (-d).')
|
||||
unless defined shift->device;
|
||||
return Status->done('Show is able to run');
|
||||
});
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
my ($device, $port, $extra) = map {$job->$_} qw/device port extra/;
|
||||
|
||||
$extra ||= 'interfaces'; my $class = undef;
|
||||
($class, $extra) = split(/::([^:]+)$/, $extra);
|
||||
if ($class and $extra) {
|
||||
$class = 'SNMP::Info::'.$class;
|
||||
}
|
||||
else {
|
||||
$extra = $class;
|
||||
undef $class;
|
||||
}
|
||||
|
||||
my $i = App::Netdisco::Transport::SNMP->reader_for($device, $class);
|
||||
Data::Printer::p($i->$extra($port));
|
||||
|
||||
return Status->done(
|
||||
sprintf "Showed %s response from %s", $extra, $device->ip);
|
||||
});
|
||||
|
||||
true;
|
||||
14
lib/App/Netdisco/Worker/Plugin/Stats.pm
Normal file
14
lib/App/Netdisco/Worker/Plugin/Stats.pm
Normal file
@@ -0,0 +1,14 @@
|
||||
package App::Netdisco::Worker::Plugin::Stats;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Util::Statistics ();
|
||||
|
||||
register_worker({ phase => 'main' }, sub {
|
||||
App::Netdisco::Util::Statistics::update_stats();
|
||||
return Status->done('Updated statistics');
|
||||
});
|
||||
|
||||
true;
|
||||
30
lib/App/Netdisco/Worker/Plugin/Vlan.pm
Normal file
30
lib/App/Netdisco/Worker/Plugin/Vlan.pm
Normal file
@@ -0,0 +1,30 @@
|
||||
package App::Netdisco::Worker::Plugin::Vlan;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Util::Port ':all';
|
||||
|
||||
register_worker({ phase => 'check' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
my ($device, $pn, $data) = map {$job->$_} qw/device port extra/;
|
||||
return Status->error('Missing device (-d).') if !defined $device;
|
||||
return Status->error('Missing port (-p).') if !defined $pn;
|
||||
return Status->error('Missing vlan (-e).') if !defined $data;
|
||||
|
||||
vars->{'port'} = get_port($device, $pn)
|
||||
or return Status->error("Unknown port name [$pn] on device $device");
|
||||
|
||||
my $port_reconfig_check = port_reconfig_check(vars->{'port'});
|
||||
return Status->error("Cannot alter port: $port_reconfig_check")
|
||||
if $port_reconfig_check;
|
||||
|
||||
my $vlan_reconfig_check = vlan_reconfig_check(vars->{'port'});
|
||||
return Status->error("Cannot alter vlan: $vlan_reconfig_check")
|
||||
if $vlan_reconfig_check;
|
||||
|
||||
return Status->done("Vlan is able to run.");
|
||||
});
|
||||
|
||||
true;
|
||||
61
lib/App/Netdisco/Worker/Plugin/Vlan/Core.pm
Normal file
61
lib/App/Netdisco/Worker/Plugin/Vlan/Core.pm
Normal file
@@ -0,0 +1,61 @@
|
||||
package App::Netdisco::Worker::Plugin::Vlan::Native;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use App::Netdisco::Transport::SNMP;
|
||||
use App::Netdisco::Util::Port ':all';
|
||||
|
||||
register_worker({ phase => 'early', driver => 'snmp' }, sub {
|
||||
my ($job, $workerconf) = @_;
|
||||
my ($device, $pn) = map {$job->$_} qw/device port/;
|
||||
|
||||
# snmp connect using rw community
|
||||
my $snmp = App::Netdisco::Transport::SNMP->writer_for($device)
|
||||
or return Status->defer("failed to connect to $device to update vlan/pvid");
|
||||
|
||||
vars->{'iid'} = get_iid($snmp, vars->{'port'})
|
||||
or return Status->error("Failed to get port ID for [$pn] from $device");
|
||||
|
||||
return Status->noop("Vlan set can continue.");
|
||||
});
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
return unless defined vars->{'iid'};
|
||||
_action($job, 'pvid');
|
||||
return _action($job, 'vlan');
|
||||
}
|
||||
|
||||
sub _action {
|
||||
my ($job, $slot) = @_;
|
||||
my ($device, $pn, $data) = map {$job->$_} qw/device port extra/;
|
||||
|
||||
my $getter = "i_${slot}";
|
||||
my $setter = "set_i_${slot}";
|
||||
|
||||
# snmp connect using rw community
|
||||
my $snmp = App::Netdisco::Transport::SNMP->writer_for($device)
|
||||
or return Status->defer("failed to connect to $device to update $slot");
|
||||
|
||||
my $rv = $snmp->$setter($data, vars->{'iid'});
|
||||
|
||||
if (!defined $rv) {
|
||||
return Status->error(sprintf 'Failed to set [%s] %s to [%s] on $device: %s',
|
||||
$pn, $slot, $data, ($snmp->error || ''));
|
||||
}
|
||||
|
||||
# confirm the set happened
|
||||
$snmp->clear_cache;
|
||||
my $state = ($snmp->$getter(vars->{'iid'}) || '');
|
||||
if (ref {} ne ref $state or $state->{ vars->{'iid'} } ne $data) {
|
||||
return Status->error("Verify of [$pn] $slot failed on $device");
|
||||
}
|
||||
|
||||
# update netdisco DB
|
||||
vars->{'port'}->update({$slot => $data});
|
||||
|
||||
return Status->done("Updated [$pn] $slot on [$device] to [$data]");
|
||||
});
|
||||
|
||||
true;
|
||||
85
lib/App/Netdisco/Worker/Runner.pm
Normal file
85
lib/App/Netdisco/Worker/Runner.pm
Normal file
@@ -0,0 +1,85 @@
|
||||
package App::Netdisco::Worker::Runner;
|
||||
|
||||
use Dancer qw/:moose :syntax/;
|
||||
use App::Netdisco::Util::Device 'get_device';
|
||||
use App::Netdisco::Util::Permission qw/check_acl_no check_acl_only/;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
use Try::Tiny;
|
||||
use Module::Load ();
|
||||
use Scope::Guard 'guard';
|
||||
|
||||
use Moo::Role;
|
||||
use namespace::clean;
|
||||
|
||||
with 'App::Netdisco::Worker::Loader';
|
||||
has 'job' => ( is => 'rw' );
|
||||
|
||||
# mixin code to run workers loaded via plugins
|
||||
sub run {
|
||||
my ($self, $job) = @_;
|
||||
|
||||
die 'cannot reuse a worker' if $self->job;
|
||||
die 'bad job to run()'
|
||||
unless ref $job eq 'App::Netdisco::Backend::Job';
|
||||
|
||||
$self->job($job);
|
||||
$job->device( get_device($job->device) );
|
||||
$self->load_workers();
|
||||
|
||||
# finalise job status when we exit
|
||||
my $statusguard = guard { $job->finalise_status };
|
||||
|
||||
my @newuserconf = ();
|
||||
my @userconf = @{ setting('device_auth') || [] };
|
||||
|
||||
# reduce device_auth by only/no
|
||||
if (ref $job->device) {
|
||||
foreach my $stanza (@userconf) {
|
||||
my $no = (exists $stanza->{no} ? $stanza->{no} : undef);
|
||||
my $only = (exists $stanza->{only} ? $stanza->{only} : undef);
|
||||
|
||||
next if $no and check_acl_no($job->device, $no);
|
||||
next if $only and not check_acl_only($job->device, $only);
|
||||
|
||||
push @newuserconf, $stanza;
|
||||
}
|
||||
|
||||
# per-device action but no device creds available
|
||||
return $job->add_status( Status->defer('deferred job with no device creds') )
|
||||
if 0 == scalar @newuserconf;
|
||||
}
|
||||
|
||||
# back up and restore device_auth
|
||||
my $configguard = guard { set(device_auth => \@userconf) };
|
||||
set(device_auth => \@newuserconf);
|
||||
|
||||
# run check phase and if there are workers then one MUST be successful
|
||||
$self->run_workers('workers_check');
|
||||
return if not $job->check_passed;
|
||||
|
||||
# run other phases
|
||||
$self->run_workers("workers_${_}") for qw/early main user/;
|
||||
}
|
||||
|
||||
sub run_workers {
|
||||
my $self = shift;
|
||||
my $job = $self->job or die error 'no job in worker job slot';
|
||||
|
||||
my $set = shift
|
||||
or return $job->add_status( Status->error('missing set param') );
|
||||
return unless ref [] eq ref $self->$set and 0 < scalar @{ $self->$set };
|
||||
|
||||
(my $phase = $set) =~ s/^workers_//;
|
||||
$job->enter_phase($phase);
|
||||
|
||||
foreach my $worker (@{ $self->$set }) {
|
||||
try { $job->add_status( $worker->($job) ) }
|
||||
catch {
|
||||
debug "=> $_" if $_;
|
||||
$job->add_status( Status->error($_) );
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
true;
|
||||
105
lib/App/Netdisco/Worker/Status.pm
Normal file
105
lib/App/Netdisco/Worker/Status.pm
Normal file
@@ -0,0 +1,105 @@
|
||||
package App::Netdisco::Worker::Status;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
|
||||
use Dancer qw/:moose :syntax !error/;
|
||||
|
||||
use Moo;
|
||||
use namespace::clean;
|
||||
|
||||
has 'status' => (
|
||||
is => 'rw',
|
||||
default => undef,
|
||||
);
|
||||
|
||||
has [qw/log phase/] => (
|
||||
is => 'rw',
|
||||
default => '',
|
||||
);
|
||||
|
||||
=head1 INTRODUCTION
|
||||
|
||||
The status can be:
|
||||
|
||||
=over 4
|
||||
|
||||
=item * C<done>
|
||||
|
||||
At C<check> phase, indicates the action may continue. At other phases,
|
||||
indicates the worker has completed without error or has no work to do.
|
||||
|
||||
=item * C<error>
|
||||
|
||||
Indicates that there is an error condition. Also used to quit a worker without
|
||||
side effects that C<done> and C<defer> have.
|
||||
|
||||
=item * C<defer>
|
||||
|
||||
Quits a worker. If the final recorded outcome for a device is C<defer> several
|
||||
times in a row, then it may be skipped from further jobs.
|
||||
|
||||
=back
|
||||
|
||||
=head1 METHODS
|
||||
|
||||
=head2 done, error, defer
|
||||
|
||||
Shorthand for new() with setting param, accepts log as arg.
|
||||
|
||||
=cut
|
||||
|
||||
sub _make_new {
|
||||
my ($self, $status, $log) = @_;
|
||||
die unless $status;
|
||||
my $new = (ref $self ? $self : $self->new());
|
||||
$new->log($log);
|
||||
$new->status($status);
|
||||
return $new;
|
||||
}
|
||||
|
||||
sub error { shift->_make_new('error', @_) }
|
||||
sub done { shift->_make_new('done', @_) }
|
||||
sub defer { shift->_make_new('defer', @_) }
|
||||
|
||||
=head2 noop
|
||||
|
||||
Simply logs a message at debug level if passed, and returns true. Used for
|
||||
consistency with other Status class methods but really does nothing.
|
||||
|
||||
=cut
|
||||
|
||||
sub noop {
|
||||
debug $_[1] if $_[1];
|
||||
return true;
|
||||
}
|
||||
|
||||
=head2 is_ok
|
||||
|
||||
Returns true if status is C<done>.
|
||||
|
||||
=cut
|
||||
|
||||
sub is_ok { return $_[0]->status eq 'done' }
|
||||
|
||||
=head2 not_ok
|
||||
|
||||
Returns true if status is C<error> or C<defer>.
|
||||
|
||||
=cut
|
||||
|
||||
sub not_ok { return (not $_[0]->is_ok) }
|
||||
|
||||
=head2 level
|
||||
|
||||
A numeric constant for the status, to allow comparison.
|
||||
|
||||
=cut
|
||||
|
||||
sub level {
|
||||
my $self = shift;
|
||||
return (($self->status eq 'done') ? 3
|
||||
: ($self->status eq 'defer') ? 2 : 1);
|
||||
}
|
||||
|
||||
1;
|
||||
@@ -190,6 +190,11 @@ ignore_interfaces:
|
||||
- 'Virtual-Template\d+'
|
||||
- 'Virtual-Access\d+'
|
||||
- '(E|T)\d \d\/\d\/\d'
|
||||
- 'InLoopback0'
|
||||
- 'NULL\d'
|
||||
- 'Register-Tunnel\d'
|
||||
- 'Blade-Aggregation\d'
|
||||
- 'M-GigabitEthernet\d/\d/\d'
|
||||
ignore_private_nets: false
|
||||
reverse_sysname: false
|
||||
phone_capabilities:
|
||||
@@ -253,6 +258,55 @@ job_prio:
|
||||
- nbtstat
|
||||
- expire
|
||||
|
||||
worker_plugins:
|
||||
- 'Arpnip'
|
||||
- 'Arpnip::Nodes'
|
||||
- 'Arpnip::Subnets'
|
||||
- 'Arpwalk'
|
||||
- 'Contact'
|
||||
- 'Delete'
|
||||
- 'Discover'
|
||||
- 'Discover::CanonicalIP'
|
||||
- 'Discover::Entities'
|
||||
- 'Discover::Neighbors'
|
||||
- 'Discover::PortPower'
|
||||
- 'Discover::Properties'
|
||||
- 'Discover::VLANs'
|
||||
- 'Discover::Wireless'
|
||||
- 'Discover::WithNodes'
|
||||
- 'DiscoverAll'
|
||||
- 'Expire'
|
||||
- 'ExpireNodes'
|
||||
- 'Graph'
|
||||
- 'Location'
|
||||
- 'Macsuck'
|
||||
- 'Macsuck::Nodes'
|
||||
- 'Macsuck::WirelessNodes'
|
||||
- 'Macwalk'
|
||||
- 'Monitor'
|
||||
- 'Nbtstat'
|
||||
- 'Nbtstat::Core'
|
||||
- 'Nbtwalk'
|
||||
- 'PortControl'
|
||||
- 'PortName'
|
||||
- 'Power'
|
||||
- 'Psql'
|
||||
- 'Renumber'
|
||||
- 'Show'
|
||||
- 'Stats'
|
||||
- 'Vlan'
|
||||
- 'Vlan::Core'
|
||||
|
||||
extra_worker_plugins: []
|
||||
# - Discover::ConfigBackup::CLI
|
||||
|
||||
driver_priority:
|
||||
restconf: 500
|
||||
netconf: 400
|
||||
eapi: 300
|
||||
cli: 200
|
||||
snmp: 100
|
||||
|
||||
# ---------------
|
||||
# GraphViz Export
|
||||
# ---------------
|
||||
|
||||
@@ -30,7 +30,7 @@ safe_password_store: true
|
||||
|
||||
# SNMP community string(s)
|
||||
# ````````````````````````
|
||||
snmp_auth:
|
||||
device_auth:
|
||||
- tag: 'default_v2_readonly'
|
||||
community: 'public'
|
||||
read: true
|
||||
|
||||
118
xt/30-backend-workers.t
Normal file
118
xt/30-backend-workers.t
Normal file
@@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env perl
|
||||
|
||||
use strict; use warnings FATAL => 'all';
|
||||
use Test::More 0.88;
|
||||
|
||||
use lib 'xt/lib';
|
||||
|
||||
use App::Netdisco;
|
||||
use App::Netdisco::Backend::Job;
|
||||
|
||||
use Try::Tiny;
|
||||
use Dancer qw/:moose :script !pass/;
|
||||
|
||||
# configure logging to force console output
|
||||
my $CONFIG = config();
|
||||
$CONFIG->{logger} = 'console';
|
||||
$CONFIG->{log} = 'error';
|
||||
Dancer::Logger->init('console', $CONFIG);
|
||||
|
||||
{
|
||||
package MyWorker;
|
||||
use Moo;
|
||||
with 'App::Netdisco::Worker::Runner';
|
||||
}
|
||||
|
||||
# clear user device_auth and set our own
|
||||
config->{'device_auth'} = [{driver => 'snmp'}, {driver => 'cli'}];
|
||||
|
||||
# TESTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
my $j1 = do_job('TestOne');
|
||||
is($j1->status, 'done', 'status is done');
|
||||
is($j1->log, 'OK: SNMP driver is successful.',
|
||||
'workers are run in decreasing priority until done');
|
||||
|
||||
my $j2 = do_job('TestTwo');
|
||||
is($j2->status, 'done', 'status is done');
|
||||
is($j2->log, 'OK: CLI driver is successful.',
|
||||
'lower priority driver not run if higher is successful');
|
||||
|
||||
config->{'device_auth'} = [];
|
||||
|
||||
my $j3 = do_job('TestOne');
|
||||
is($j3->status, 'defer', 'status is defer');
|
||||
is($j3->log, 'deferred job with no device creds',
|
||||
'no matching config for workers');
|
||||
|
||||
config->{'device_auth'} = [{driver => 'snmp'}];
|
||||
|
||||
my $j4 = do_job('TestThree');
|
||||
is($j4->status, 'done', 'status is done');
|
||||
is($j4->log, 'OK: SNMP driver is successful.',
|
||||
'respect user config filtering the driver');
|
||||
|
||||
config->{'device_auth'} = [
|
||||
{driver => 'snmp', action => 'testthree'},
|
||||
{driver => 'cli', action => 'foo'},
|
||||
];
|
||||
|
||||
my $j5 = do_job('TestThree');
|
||||
is($j5->status, 'done', 'status is done');
|
||||
is($j5->log, 'OK: SNMP driver is successful.',
|
||||
'respect user config filtering the action');
|
||||
|
||||
config->{'device_auth'} = [
|
||||
{driver => 'snmp', action => 'testthree::_base_'},
|
||||
{driver => 'cli', action => 'testthree::foo'},
|
||||
];
|
||||
|
||||
my $j6 = do_job('TestThree');
|
||||
is($j6->status, 'done', 'status is done');
|
||||
is($j6->log, 'OK: SNMP driver is successful.',
|
||||
'respect user config filtering the namespace');
|
||||
|
||||
config->{'device_auth'} = [{driver => 'snmp'}];
|
||||
|
||||
my $j7 = do_job('TestFour');
|
||||
is($j7->status, 'done', 'status is done');
|
||||
is($j7->log, 'OK: custom driver is successful.',
|
||||
'override an action');
|
||||
|
||||
config->{'device_auth'} = [{driver => 'snmp'}];
|
||||
|
||||
my $j8 = do_job('TestFive');
|
||||
is($j8->status, 'done', 'status is done');
|
||||
is((scalar @{$j8->_statuslist}), 2, 'two workers ran');
|
||||
is($j8->_last_priority, 100, 'priority is for snmp');
|
||||
is($j8->log, 'OK: SNMP driver is successful.',
|
||||
'add to an action');
|
||||
|
||||
done_testing;
|
||||
|
||||
# TESTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
sub do_job {
|
||||
my $pkg = shift;
|
||||
|
||||
# include local plugins
|
||||
config->{'extra_worker_plugins'} = ["X::${pkg}"];
|
||||
|
||||
my $job = App::Netdisco::Backend::Job->new({
|
||||
job => 0,
|
||||
device => '192.0.2.1',
|
||||
action => lc($pkg),
|
||||
});
|
||||
|
||||
try {
|
||||
#info sprintf 'test: started at %s', scalar localtime;
|
||||
MyWorker->new()->run($job);
|
||||
#info sprintf 'test: %s: %s', $job->status, $job->log;
|
||||
}
|
||||
catch {
|
||||
$job->status('error');
|
||||
$job->log("error running job: $_");
|
||||
};
|
||||
|
||||
return $job;
|
||||
}
|
||||
17
xt/lib/App/NetdiscoX/Worker/Plugin/TestFive.pm
Normal file
17
xt/lib/App/NetdiscoX/Worker/Plugin/TestFive.pm
Normal file
@@ -0,0 +1,17 @@
|
||||
package App::NetdiscoX::Worker::Plugin::TestFive;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
# info 'test: add to an action';
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
return Status->error('NOT OK: additional worker at SNMP level.');
|
||||
});
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
return Status->done('OK: SNMP driver is successful.');
|
||||
});
|
||||
|
||||
true;
|
||||
17
xt/lib/App/NetdiscoX/Worker/Plugin/TestFour.pm
Normal file
17
xt/lib/App/NetdiscoX/Worker/Plugin/TestFour.pm
Normal file
@@ -0,0 +1,17 @@
|
||||
package App::NetdiscoX::Worker::Plugin::TestFour;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
# info 'test: override an action';
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
return Status->done('NOT OK: SNMP driver should NOT be run.');
|
||||
});
|
||||
|
||||
register_worker({ phase => 'main', priority => 120 }, sub {
|
||||
return Status->done('OK: custom driver is successful.');
|
||||
});
|
||||
|
||||
true;
|
||||
17
xt/lib/App/NetdiscoX/Worker/Plugin/TestOne.pm
Normal file
17
xt/lib/App/NetdiscoX/Worker/Plugin/TestOne.pm
Normal file
@@ -0,0 +1,17 @@
|
||||
package App::NetdiscoX::Worker::Plugin::TestOne;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
# info 'test: workers are run in decreasing priority until done';
|
||||
|
||||
register_worker({ phase => 'main', driver => 'cli' }, sub {
|
||||
return Status->noop('NOT OK: CLI driver is not the winner here.');
|
||||
});
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
return Status->done('OK: SNMP driver is successful.');
|
||||
});
|
||||
|
||||
true;
|
||||
17
xt/lib/App/NetdiscoX/Worker/Plugin/TestThree.pm
Normal file
17
xt/lib/App/NetdiscoX/Worker/Plugin/TestThree.pm
Normal file
@@ -0,0 +1,17 @@
|
||||
package App::NetdiscoX::Worker::Plugin::TestThree;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
# info 'test: respect user config filtering the driver, action and namespace';
|
||||
|
||||
register_worker({ phase => 'main', driver => 'cli' }, sub {
|
||||
return Status->done('NOT OK: CLI driver should NOT be run.');
|
||||
});
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
return Status->done('OK: SNMP driver is successful.');
|
||||
});
|
||||
|
||||
true;
|
||||
17
xt/lib/App/NetdiscoX/Worker/Plugin/TestTwo.pm
Normal file
17
xt/lib/App/NetdiscoX/Worker/Plugin/TestTwo.pm
Normal file
@@ -0,0 +1,17 @@
|
||||
package App::NetdiscoX::Worker::Plugin::TestTwo;
|
||||
|
||||
use Dancer ':syntax';
|
||||
use App::Netdisco::Worker::Plugin;
|
||||
use aliased 'App::Netdisco::Worker::Status';
|
||||
|
||||
# info 'test: lower priority driver not run if higher is successful';
|
||||
|
||||
register_worker({ phase => 'main', driver => 'snmp' }, sub {
|
||||
return Status->done('NOT OK: SNMP driver should NOT be run.');
|
||||
});
|
||||
|
||||
register_worker({ phase => 'main', driver => 'cli' }, sub {
|
||||
return Status->done('OK: CLI driver is successful.');
|
||||
});
|
||||
|
||||
true;
|
||||
Reference in New Issue
Block a user