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