1package App::Netdisco::Worker::Plugin::Discover::PortProperties;
2
3use Dancer ':syntax';
4use App::Netdisco::Worker::Plugin;
5use aliased 'App::Netdisco::Worker::Status';
6
7use App::Netdisco::Transport::SNMP ();
8use Dancer::Plugin::DBIC 'schema';
9
10use Encode;
11use App::Netdisco::Util::Device 'match_to_setting';
12
13register_worker({ phase => 'main', driver => 'snmp' }, sub {
14  my ($job, $workerconf) = @_;
15
16  my $device = $job->device;
17  return unless $device->in_storage;
18  my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
19    or return Status->defer("discover failed: could not SNMP connect to $device");
20
21  my $interfaces = $snmp->interfaces || {};
22  my %properties = ();
23
24  # cache the device ports to save hitting the database for many single rows
25  my $device_ports = vars->{'device_ports'}
26    || { map {($_->port => $_)} $device->ports->all };
27
28  my $raw_speed = $snmp->i_speed_raw || {};
29
30  foreach my $idx (keys %$raw_speed) {
31    my $port = $interfaces->{$idx} or next;
32    if (!defined $device_ports->{$port}) {
33        debug sprintf ' [%s] properties/speed - local port %s already skipped, ignoring',
34          $device->ip, $port;
35        next;
36    }
37
38    $properties{ $port }->{raw_speed} = $raw_speed->{$idx};
39  }
40
41  my $err_cause = $snmp->i_err_disable_cause || {};
42
43  foreach my $idx (keys %$err_cause) {
44    my $port = $interfaces->{$idx} or next;
45    if (!defined $device_ports->{$port}) {
46        debug sprintf ' [%s] properties/errdis - local port %s already skipped, ignoring',
47          $device->ip, $port;
48        next;
49    }
50
51    $properties{ $port }->{error_disable_cause} = $err_cause->{$idx};
52  }
53
54  my $faststart = $snmp->i_faststart_enabled || {};
55
56  foreach my $idx (keys %$faststart) {
57    my $port = $interfaces->{$idx} or next;
58    if (!defined $device_ports->{$port}) {
59        debug sprintf ' [%s] properties/faststart - local port %s already skipped, ignoring',
60          $device->ip, $port;
61        next;
62    }
63
64    $properties{ $port }->{faststart} = $faststart->{$idx};
65  }
66
67  my $c_if  = $snmp->c_if  || {};
68  my $c_cap = $snmp->c_cap || {};
69  my $c_platform = $snmp->c_platform || {};
70
71  my $rem_media_cap = $snmp->lldp_media_cap || {};
72  my $rem_vendor = $snmp->lldp_rem_vendor || {};
73  my $rem_model  = $snmp->lldp_rem_model  || {};
74  my $rem_os_ver = $snmp->lldp_rem_sw_rev || {};
75  my $rem_serial = $snmp->lldp_rem_serial || {};
76
77  foreach my $idx (keys %$c_if) {
78    my $port = $interfaces->{ $c_if->{$idx} } or next;
79    if (!defined $device_ports->{$port}) {
80        debug sprintf ' [%s] properties/lldpcap - local port %s already skipped, ignoring',
81          $device->ip, $port;
82        next;
83    }
84
85    my $remote_cap  = $c_cap->{$idx} || [];
86    my $remote_type = Encode::decode('UTF-8', $c_platform->{$idx} || '');
87
88    $properties{ $port }->{remote_is_wap} = 'true'
89      if scalar grep {match_to_setting($_, 'wap_capabilities')} @$remote_cap
90         or match_to_setting($remote_type, 'wap_platforms');
91
92    $properties{ $port }->{remote_is_phone} = 'true'
93      if scalar grep {match_to_setting($_, 'phone_capabilities')} @$remote_cap
94         or match_to_setting($remote_type, 'phone_platforms');
95
96    next unless scalar grep {defined && m/^inventory$/} @{ $rem_media_cap->{$idx} };
97
98    $properties{ $port }->{remote_vendor} = $rem_vendor->{ $idx };
99    $properties{ $port }->{remote_model}  = $rem_model->{ $idx };
100    $properties{ $port }->{remote_os_ver} = $rem_os_ver->{ $idx };
101    $properties{ $port }->{remote_serial} = $rem_serial->{ $idx };
102  }
103
104  foreach my $idx (keys %$interfaces) {
105    next unless defined $idx;
106    my $port = $interfaces->{$idx} or next;
107
108    if (!defined $device_ports->{$port}) {
109        debug sprintf ' [%s] properties/ifindex - local port %s already skipped, ignoring',
110          $device->ip, $port;
111        next;
112    }
113
114    if ($idx !~ m/^[0-9]+$/) {
115        debug sprintf ' [%s] properties/ifindex - port %s ifindex %s is not an integer',
116          $device->ip, $port, $idx;
117        next;
118    }
119
120    $properties{ $port }->{ifindex} = $idx;
121  }
122
123  return Status->info(" [$device] no port properties to record")
124    unless scalar keys %properties;
125
126  schema('netdisco')->txn_do(sub {
127    my $gone = $device->properties_ports->delete;
128    debug sprintf ' [%s] properties - removed %d ports with properties',
129      $device->ip, $gone;
130    $device->properties_ports->populate(
131      [map {{ port => $_, %{ $properties{$_} } }} keys %properties] );
132
133    return Status->info(sprintf ' [%s] properties - added %d new port properties',
134      $device->ip, scalar keys %properties);
135  });
136});
137
138true;
139