File Coverage

blib/lib/App/Manoc/Netwalker/Poller/DeviceTask.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package App::Manoc::Netwalker::Poller::DeviceTask;
2             #ABSTRACT: Device poller task
3              
4 1     1   2748 use Moose;
  1         12  
  1         6  
5              
6             our $VERSION = '2.99.2'; ##TRIAL VERSION
7              
8 1     1   5618 use Try::Tiny;
  1         2  
  1         52  
9              
10 1     1   345 use App::Manoc::Netwalker::Poller::TaskReport;
  1         3  
  1         35  
11 1     1   442 use App::Manoc::Manifold;
  1         3  
  1         22  
12 1     1   338 use App::Manoc::IPAddress::IPv4;
  0            
  0            
13              
14              
15             has 'device_id' => (
16             is => 'ro',
17             isa => 'Int',
18             required => 1
19             );
20              
21              
22             has 'device_entry' => (
23             is => 'ro',
24             isa => 'Maybe[Object]',
25             lazy => 1,
26             builder => '_build_device_entry',
27             );
28              
29              
30             has 'nwinfo' => (
31             is => 'ro',
32             isa => 'Object',
33             lazy => 1,
34             builder => '_build_nwinfo',
35             );
36              
37              
38             has 'device_set' => (
39             is => 'ro',
40             isa => 'HashRef',
41             builder => '_build_device_set',
42             );
43              
44              
45             has 'source' => (
46             is => 'ro',
47             lazy => 1,
48             builder => '_build_source',
49             );
50              
51              
52             has 'config_source' => (
53             is => 'ro',
54             lazy => 1,
55             builder => '_build_config_source',
56             );
57              
58              
59             has 'task_report' => (
60             is => 'ro',
61             required => 0,
62             builder => '_build_task_report',
63             );
64              
65              
66             has 'uplinks' => (
67             is => 'ro',
68             lazy => 1,
69             builder => '_build_uplinks',
70             );
71              
72              
73             has 'native_vlan' => (
74             is => 'ro',
75             lazy => 1,
76             builder => '_build_native_vlan',
77             );
78              
79              
80             has 'arp_vlan' => (
81             is => 'ro',
82             lazy => 1,
83             builder => '_build_arp_vlan',
84             );
85              
86             with 'App::Manoc::Netwalker::Poller::BaseTask', 'App::Manoc::Logger::Role';
87              
88             #----------------------------------------------------------------------#
89             # #
90             # A t t r i b u t e s B u i l d e r #
91             # #
92             #----------------------------------------------------------------------#
93              
94             sub _build_arp_vlan {
95             my $self = shift;
96              
97             my $vlan = $self->nwinfo->arp_vlan->id ||
98             $self->config->default_vlan;
99             return defined($vlan) ? $vlan : 1;
100             }
101              
102             sub _build_device_entry {
103             my $self = shift;
104             my $device_id = $self->device_id;
105              
106             return $self->schema->resultset('Device')->find( $self->device_id );
107             }
108              
109             sub _build_device_set {
110             my $self = shift;
111              
112             # columns are not inflated
113             my @addresses = $self->schema->resultset('Device')->get_column('mng_address')->all;
114             my %addr_set = map { App::Manoc::IPAddress::IPv4->new($_)->unpadded => 1 } @addresses;
115             return \%addr_set;
116             }
117              
118             sub _build_native_vlan {
119             my $self = shift;
120              
121             my $vlan = $self->nwinfo->mat_native_vlan->id ||
122             $self->config->default_vlan;
123             return defined($vlan) ? $vlan : 1;
124             }
125              
126             sub _build_nwinfo {
127             my $self = shift;
128              
129             return $self->device_entry->netwalker_info;
130             }
131              
132             sub _create_manifold {
133             my $self = shift;
134             my $manifold_name = shift;
135             my %params = @_;
136              
137             my $manifold;
138             try {
139             $manifold = App::Manoc::Manifold->new_manifold( $manifold_name, %params );
140             }
141             catch {
142             my $error = "Internal error while creating manifold $manifold_name: $_";
143             $self->log->debug($error);
144             return;
145             };
146              
147             $manifold or $self->log->debug("Manifold constructor returned undef");
148             return $manifold;
149             }
150              
151             sub _build_config_source {
152             my $self = shift;
153             my $entry = $self->device_entry;
154             my $nwinfo = $self->nwinfo;
155              
156             my $manifold_name = $nwinfo->config_manifold;
157             if ( !defined($manifold_name) || $manifold_name eq $nwinfo->manifold ) {
158             $self->log->debug("Using common Manifold for config");
159             return $self->source;
160             }
161              
162             $self->log->debug("Using Manifold $manifold_name for config");
163             my $host = $entry->mng_address->unpadded;
164              
165             my %params = (
166             host => $host,
167             credentials => $self->credentials
168             );
169             my $source = $self->_create_manifold( $manifold_name, %params );
170              
171             if ( !$source ) {
172             my $error = "Cannot create config source with manifold $manifold_name";
173             $self->log->error($error);
174             $self->task_report->add_error($error);
175             return;
176             }
177              
178             # auto connect
179             if ( !$source->connect() ) {
180             my $error = "Cannot connect to $host";
181             $self->log->error($error);
182             $self->task_report->add_error($error);
183             return;
184             }
185             return $source;
186             }
187              
188             sub _build_source {
189             my $self = shift;
190              
191             my $entry = $self->device_entry;
192             my $nwinfo = $self->nwinfo;
193              
194             my $host = $entry->mng_address->unpadded;
195              
196             my $manifold_name = $nwinfo->manifold;
197             $self->log->debug("Using Manifold $manifold_name");
198              
199             my %params = (
200             host => $host,
201             credentials => $self->credentials,
202             extra_params => {
203             mat_force_vlan => $self->config->mat_force_vlan,
204             }
205             );
206              
207             my $source = $self->_create_manifold( $manifold_name, %params );
208              
209             if ( !$source ) {
210             my $error = "Cannot create source with manifold $manifold_name";
211             $self->log->error($error);
212             $self->task_report->add_error($error);
213             return;
214             }
215              
216             # auto connect
217             if ( !$source->connect() ) {
218             my $error = "Cannot connect to $host";
219             $self->log->error($error);
220             $self->task_report->add_error($error);
221             return;
222             }
223             return $source;
224             }
225              
226             sub _build_task_report {
227             my $self = shift;
228              
229             $self->device_entry or return;
230             my $device_address = $self->device_entry->mng_address->address;
231             return App::Manoc::Netwalker::Poller::TaskReport->new( host => $device_address );
232             }
233              
234             sub _build_uplinks {
235             my $self = shift;
236              
237             my $entry = $self->device_entry;
238             my $source = $self->source;
239             my $device_set = $self->device_set;
240              
241             my %uplinks;
242              
243             # get uplink from CDP
244             my $neighbors = $source->neighbors;
245              
246             # filter CDP links
247             while ( my ( $p, $n ) = each(%$neighbors) ) {
248             foreach my $s (@$n) {
249              
250             # only links to a switch
251             next unless $s->{type}->{'Switch'};
252             # only links to a kwnown device
253             next unless $device_set->{ $s->{addr} };
254              
255             $uplinks{$p} = 1;
256             }
257             }
258              
259             # get uplinks from DB and merge them
260             foreach ( $entry->uplinks->all ) {
261             $uplinks{ $_->interface } = 1;
262             }
263              
264             return \%uplinks;
265             }
266              
267             #----------------------------------------------------------------------#
268             # #
269             # D a t a u p d a t e #
270             # #
271             #----------------------------------------------------------------------#
272              
273              
274             sub update {
275             my $self = shift;
276              
277             # check if there is a device object in the DB
278             my $entry = $self->device_entry;
279             unless ($entry) {
280             $self->log->error( "Cannot find device id ", $self->device_id );
281             return;
282             }
283              
284             # load netwalker info from DB
285             my $nwinfo = $self->nwinfo;
286             unless ($nwinfo) {
287             $self->log->error( "No netwalker info for device", $entry->name );
288             return;
289             }
290              
291             # try to connect and update nwinfo accordingly
292             $self->log->info( "Connecting to device ", $entry->name, " ", $entry->mng_address );
293             if ( !$self->source ) {
294              
295             # TODO update nwinfo with connection messages
296             $self->reschedule_on_failure();
297             $nwinfo->offline(1);
298             $nwinfo->update();
299             return;
300             }
301              
302             $self->update_device_info;
303              
304             # if full_update_interval is elapsed update interface table
305             my $full_update_interval = $self->config->full_update_interval;
306             my $elapsed_full_update = $self->timestamp - $nwinfo->last_full_update;
307             if ( $elapsed_full_update >= $full_update_interval ) {
308             $self->update_if_table;
309              
310             # update nwinfo
311             $nwinfo->last_full_update( $self->timestamp );
312             }
313              
314             # always update CPD info
315             $self->update_cdp_neighbors;
316              
317             # update required information
318             $nwinfo->get_mat and $self->update_mat;
319             $nwinfo->get_arp and $self->update_arp_table;
320             $nwinfo->get_vtp and $self->update_vtp_database;
321             $nwinfo->get_dot11 and $self->update_dot11;
322              
323             $nwinfo->get_config and $self->update_config;
324              
325             $self->reschedule_on_success;
326             $nwinfo->last_visited( $self->timestamp );
327             $nwinfo->offline(0);
328             $nwinfo->update();
329              
330             $self->log->debug( "Device ", $entry->name, " ", $entry->mng_address, "updated" );
331             return 1;
332             }
333              
334              
335             sub update_device_info {
336             my $self = shift;
337              
338             my $source = $self->source;
339             my $dev_entry = $self->device_entry;
340             my $nw_entry = $self->nwinfo;
341              
342             my $name = $source->name;
343             $nw_entry->name($name);
344             if ( defined($name) && $name ne $dev_entry->name ) {
345             if ( $dev_entry->name ) {
346             my $msg = "Name mismatch " . $dev_entry->name . " $name";
347             $self->log->warn($msg);
348             }
349             else {
350             $dev_entry->name($name);
351             $dev_entry->update;
352             }
353             }
354              
355             $nw_entry->model( $source->model );
356             $nw_entry->os( $source->os );
357             $nw_entry->os_ver( $source->os_ver );
358             $nw_entry->vendor( $source->vendor );
359             $nw_entry->serial( $source->serial );
360              
361             $nw_entry->vtp_domain( $source->vtp_domain );
362             $nw_entry->boottime( $source->boottime || 0 );
363              
364             $nw_entry->update;
365             }
366              
367              
368             sub update_cdp_neighbors {
369             my $self = shift;
370              
371             my $source = $self->source;
372             my $entry = $self->device_entry;
373             my $schema = $self->schema;
374             my $neighbors = $source->neighbors;
375             my $new_dev = 0;
376             my $cdp_entries = 0;
377              
378             while ( my ( $p, $n ) = each(%$neighbors) ) {
379             foreach my $s (@$n) {
380             my $from_dev_id = $entry->id;
381             my $to_dev_obj = App::Manoc::IPAddress::IPv4->new( $s->{addr} );
382              
383             my @cdp_entries = $self->schema->resultset('CDPNeigh')->search(
384             {
385             from_device_id => $from_dev_id,
386             from_interface => $p,
387             to_device => $to_dev_obj->padded,
388             to_interface => $s->{port},
389             }
390             );
391              
392             unless ( scalar(@cdp_entries) ) {
393             $self->schema->resultset('CDPNeigh')->create(
394             {
395             from_device_id => $from_dev_id,
396             from_interface => $p,
397             to_device => $to_dev_obj,
398             to_interface => $s->{port},
399             remote_id => $s->{remote_id},
400             remote_type => $s->{remote_type},
401             last_seen => $self->timestamp,
402             }
403             );
404             $new_dev++;
405             $cdp_entries++;
406             $self->task_report->add_warning(
407             "New neighbor " . $s->{addr} . " at " . $entry->name );
408             next;
409             }
410             my $link = $cdp_entries[0];
411             $link->last_seen( $self->timestamp );
412             $link->update;
413             $cdp_entries++;
414             }
415             }
416             $self->task_report->cdp_entries($cdp_entries);
417             $self->task_report->new_devices($new_dev);
418             }
419              
420              
421             sub update_if_table {
422             my $self = shift;
423              
424             my $source = $self->source;
425             my $entry = $self->device_entry;
426             my $iface_filter = $self->config->{iface_filter};
427              
428             my $ifstatus_table = $source->ifstatus_table;
429              
430             # delete old infos
431             $entry->ifstatus()->delete;
432             # update
433             foreach my $port ( keys %$ifstatus_table ) {
434             $iface_filter && lc($port) =~ /^(vlan|null|unrouted vlan)/o and next;
435             my $ifstatus = $ifstatus_table->{$port};
436             $entry->add_to_ifstatus(
437             {
438             interface => $port,
439             %$ifstatus
440             }
441             );
442             }
443             }
444              
445              
446             sub update_mat {
447             my $self = shift;
448              
449             my $source = $self->source;
450             my $entry = $self->device_entry;
451             my $schema = $self->schema;
452              
453             my $uplinks = $self->uplinks;
454             $self->log->debug( "device uplinks: ", join( ",", keys %$uplinks ) );
455              
456             my $timestamp = $self->timestamp;
457             my $device_id = $self->device_id;
458              
459             my $ignore_portchannel = $self->config->{ignore_portchannel};
460              
461             my $mat = $source->mat() or return;
462              
463             my $mat_count = 0;
464              
465             while ( my ( $vlan, $entries ) = each(%$mat) ) {
466             $self->log->debug("updating mat vlan $vlan");
467              
468             if ( $vlan eq 'default' ) {
469             $vlan = $self->native_vlan;
470             }
471             while ( my ( $m, $p ) = each %$entries ) {
472             next if $uplinks->{$p};
473             next if $ignore_portchannel && lc($p) =~ /^port-channel/;
474              
475             $self->schema->resultset('Mat')->register_tuple(
476             macaddr => $m,
477             device_id => $device_id,
478             interface => $p,
479             timestamp => $timestamp,
480             vlan => $vlan,
481             );
482              
483             } # end of entries loop
484              
485             } # end of mat loop
486              
487             $self->task_report->mat_entries($mat_count);
488             }
489              
490              
491             sub update_vtp_database {
492             my $self = shift;
493              
494             my $source = $self->source;
495             my $entry = $self->device_entry;
496              
497             my $vlan_db = $source->vtp_database;
498              
499             $self->log->info( "getting vtp info from ", $entry->mng_address );
500             if ( !defined($vlan_db) ) {
501             $self->log->error("cannot retrieve vtp info");
502             $self->task_report->add_error("cannot retrieve vtp info");
503             return;
504             }
505              
506             $self->task_report->add_warning("Vtp Vlan DB up to date");
507              
508             my $rs = $self->schema->resultset('VlanVtp');
509             $rs->delete();
510             while ( my ( $id, $name ) = each(%$vlan_db) ) {
511             $rs->find_or_create(
512             {
513             'id' => $id,
514             'name' => $name
515             }
516             );
517             }
518             my $vtp_last_update =
519             $self->schema->resultset('System')->find_or_create("netwalker.vtp_update");
520             $vtp_last_update->value( $self->timestamp );
521             $vtp_last_update->update();
522              
523             }
524              
525              
526             sub update_arp_table {
527             my $self = shift;
528              
529             my $source = $self->source;
530             my $entry = $self->device_entry;
531             my $timestamp = $self->timestamp;
532             my $vlan = $self->arp_vlan;
533              
534             $self->log->debug("Fetching arp table ");
535             my $arp_table = $source->arp_table;
536              
537             # TODO log error
538             $arp_table or return;
539              
540             my $arp_count = 0;
541             my ( $ip_addr, $mac_addr );
542             while ( ( $ip_addr, $mac_addr ) = each(%$arp_table) ) {
543             $self->log->debug( sprintf( "Arp table: %15s at %17s\n", $ip_addr, $mac_addr ) );
544              
545             $self->schema->resultset('Arp')->register_tuple(
546             ipaddr => $ip_addr,
547             macaddr => $mac_addr,
548             vlan => $vlan,
549             timestamp => $timestamp,
550             );
551             $arp_count++;
552             }
553              
554             $self->task_report->arp_entries($arp_count);
555             }
556              
557              
558             sub update_config {
559             my $self = shift;
560              
561             my $device_entry = $self->device_entry;
562             my $config_date = $device_entry->get_config_date;
563             my $update_interval = $self->config->config_update_interval;
564             my $timestamp = $self->timestamp;
565             my $config_source = $self->config_source;
566              
567             unless ( $config_source->does('App::Manoc::ManifoldRole::FetchConfig') ) {
568             $self->log->warnings("Config source does not support fetchconfig");
569             return;
570             }
571              
572             if ( !defined($config_date) || $timestamp > $config_date + $update_interval ) {
573             $self->log->info( "Fetching configuration from ", $device_entry->mng_address );
574             my $config_text = $config_source->configuration;
575             if ( !defined($config_text) ) {
576             $self->log->error( "Cannot fetch configuration from ", $device_entry->mng_address );
577             return;
578             }
579              
580             $self->device_entry->update_config( $config_text, $timestamp );
581             }
582             }
583              
584             1;
585              
586             # Local Variables:
587             # mode: cperl
588             # indent-tabs-mode: nil
589             # cperl-indent-level: 4
590             # cperl-indent-parens-as-block: t
591             # End:
592              
593             __END__
594              
595             =pod
596              
597             =head1 NAME
598              
599             App::Manoc::Netwalker::Poller::DeviceTask - Device poller task
600              
601             =head1 VERSION
602              
603             version 2.99.2
604              
605             =head1 ATTRIBUTES
606              
607             =head2 device_id
608              
609             The id in Manoc DB of the device to update.
610              
611             =head2 device_entry
612              
613             The Device row in Manoc DB identified by C<device_id>.
614              
615             =head2 nwinfo
616              
617             NWInfo associated to the current C<device_entry>.
618              
619             =head2 device_set
620              
621             A set (hash) of all mng_address known to Manoc used to to discover
622             neighbors and uplinks.
623              
624             =head2 source
625              
626             The source for information about the device: a connected Manifold object.
627              
628             =head2 config_source
629              
630             The Manifold to use as a source for device configuration backup.
631              
632             =head2 task_report
633              
634             =head2 uplinks
635              
636             =head2 native_vlan
637              
638             =head2 arp_vlan
639              
640             =head1 METHODS
641              
642             =head2 update
643              
644             Update device information
645              
646             =head2 update_device_info
647              
648             Updates device information (model, os, vendor) in C<nwinfo>.
649              
650             =head2 update_cdp_neighbors
651              
652             Update neighbor list in CDPNeigh
653              
654             =head2 update_if_table
655              
656             Update interface information.
657              
658             =head2 update_mat
659              
660             Update mac address table.
661              
662             =head2 update_vtp_database
663              
664             =head2 update_arp_table
665              
666             =head2 update_config
667              
668             =head1 AUTHORS
669              
670             =over 4
671              
672             =item *
673              
674             Gabriele Mambrini <gmambro@cpan.org>
675              
676             =item *
677              
678             Enrico Liguori
679              
680             =back
681              
682             =head1 COPYRIGHT AND LICENSE
683              
684             This software is copyright (c) 2017 by Gabriele Mambrini.
685              
686             This is free software; you can redistribute it and/or modify it under
687             the same terms as the Perl 5 programming language system itself.
688              
689             =cut