1package App::Netdisco::Worker::Plugin::Discover::VLANs;
2
3use Dancer ':syntax';
4use Dancer::Plugin::DBIC 'schema';
5
6use App::Netdisco::Worker::Plugin;
7use App::Netdisco::Transport::SNMP ();
8
9use aliased 'App::Netdisco::Worker::Status';
10use List::MoreUtils 'uniq';
11
12register_worker({ phase => 'main', driver => 'snmp' }, sub {
13  my ($job, $workerconf) = @_;
14
15  my $device = $job->device;
16  return unless $device->in_storage;
17  my $snmp = App::Netdisco::Transport::SNMP->reader_for($device)
18    or return Status->defer("discover failed: could not SNMP connect to $device");
19
20  my $v_name  = $snmp->v_name;
21  my $v_index = $snmp->v_index;
22
23  # cache the device ports to save hitting the database for many single rows
24  my $device_ports = vars->{'device_ports'}
25    || { map {($_->port => $_)} $device->ports->all };
26
27  my $i_vlan      = $snmp->i_vlan;
28  my $i_vlan_type = $snmp->i_vlan_type;
29  my $interfaces  = $snmp->interfaces;
30  my $i_vlan_membership          = $snmp->i_vlan_membership;
31  my $i_vlan_membership_untagged = $snmp->i_vlan_membership_untagged;
32
33  my %p_seen = ();
34  my @portvlans = ();
35  my @active_ports = uniq (keys %$i_vlan_membership_untagged, keys %$i_vlan_membership);
36
37  # build port vlans suitable for DBIC
38  foreach my $entry (@active_ports) {
39      my $port = $interfaces->{$entry} or next;
40
41      if (!defined $device_ports->{$port}) {
42          debug sprintf ' [%s] vlans - local port %s already skipped, ignoring',
43            $device->ip, $port;
44          next;
45      }
46
47      my %this_port_vlans = ();
48      my $type = $i_vlan_type->{$entry};
49
50      foreach my $vlan (@{ $i_vlan_membership_untagged->{$entry} || [] }) {
51          next unless $vlan;
52          next if $this_port_vlans{$vlan};
53          my $native = ((defined $i_vlan->{$entry})
54                          and ($vlan eq $i_vlan->{$entry})) ? 't' : 'f';
55
56          push @portvlans, {
57              port => $port,
58              vlan => $vlan,
59              native => $native,
60              egress_tag => 'f',
61              vlantype => $type,
62              last_discover => \'now()',
63          };
64
65          ++$this_port_vlans{$vlan};
66          ++$p_seen{$vlan};
67      }
68
69      foreach my $vlan (@{ $i_vlan_membership->{$entry} || [] }) {
70          next unless $vlan;
71          next if $this_port_vlans{$vlan};
72          my $native = ((defined $i_vlan->{$entry})
73                          and ($vlan eq $i_vlan->{$entry})) ? 't' : 'f';
74
75          push @portvlans, {
76              port => $port,
77              vlan => $vlan,
78              native => $native,
79              egress_tag => ($native eq 't' ? 'f' : 't'),
80              vlantype => $type,
81              last_discover => \'now()',
82          };
83
84          ++$this_port_vlans{$vlan};
85          ++$p_seen{$vlan};
86      }
87  }
88
89  schema('netdisco')->txn_do(sub {
90    my $gone = $device->port_vlans->delete;
91    debug sprintf ' [%s] vlans - removed %d port VLANs',
92      $device->ip, $gone;
93    $device->port_vlans->populate(\@portvlans);
94
95    debug sprintf ' [%s] vlans - added %d new port VLANs',
96      $device->ip, scalar @portvlans;
97  });
98
99  my %d_seen = ();
100  my @devicevlans = ();
101
102  # add named vlans to the device
103  foreach my $entry (keys %$v_name) {
104      my $vlan = $v_index->{$entry};
105      next unless $vlan;
106      next unless defined $vlan and $vlan;
107      ++$d_seen{$vlan};
108
109      push @devicevlans, {
110          vlan => $vlan,
111          description => $v_name->{$entry},
112          last_discover => \'now()',
113      };
114  }
115
116  # also add unnamed vlans to the device
117  foreach my $vlan (keys %p_seen) {
118      next unless $vlan;
119      next if $d_seen{$vlan};
120      push @devicevlans, {
121          vlan => $vlan,
122          description => (sprintf "VLAN %d", $vlan),
123          last_discover => \'now()',
124      };
125  }
126
127  # support for Hooks
128  vars->{'hook_data'}->{'vlans'} = \@devicevlans;
129
130  schema('netdisco')->txn_do(sub {
131    my $gone = $device->vlans->delete;
132    debug sprintf ' [%s] vlans - removed %d device VLANs',
133      $device->ip, $gone;
134    $device->vlans->populate(\@devicevlans);
135
136    debug sprintf ' [%s] vlans - added %d new device VLANs',
137      $device->ip, scalar @devicevlans;
138  });
139
140  return Status->info(sprintf ' [%s] vlans - discovered for ports and device',
141    $device->ip);
142});
143
144true;
145