Created
June 26, 2014 23:54
-
-
Save bldewolf/0a767cabd71ce2ea0aee to your computer and use it in GitHub Desktop.
Netdisco 1.x to Nagios config with dependencies generator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
# | |
use warnings; | |
use strict; | |
use lib "/var/lib/netdisco"; | |
use netdisco qw/:all/; | |
config("/var/lib/netdisco/netdisco.conf"); | |
exit 1 if !defined $ARGV[0]; | |
my $starter = getip($ARGV[0]); | |
my $baseurl = "https://example.com/netdisco"; | |
$baseurl = "/netdisco"; | |
my $debug = 0; | |
my $mode = 'graph'; | |
$mode = 'nagios'; | |
my $mgmt_vlans = "1,2,3,4,5"; | |
if($debug) { | |
use Data::Dumper; | |
} | |
my %skip_models = ( | |
AIRAP1210 => 1, | |
AIRAP1240 => 1, | |
AirAp350IOS => 1, | |
# AIRBR1300 => 1, | |
); | |
$debug and print STDERR "Starting vertice is $starter\n"; | |
my %devs; | |
my %groups; | |
$groups{all_devices} = "All devices added by auto-generated config"; | |
foreach my $dev (@{sql_rows('device',['ip','dns','model','vtp_domain','vendor','os','os_ver','snmp_comm','snmp_ver'])}){ | |
# Remove models from %devs | |
next if exists $skip_models{$dev->{model}}; | |
my $ip = $dev->{ip}; | |
$devs{$ip}{dns} = $dev->{dns}; | |
# Chop domain off of name, might make less portable :-/ | |
$devs{$ip}{name} = $dev->{dns}; | |
$devs{$ip}{name} =~ s/\..*//; | |
$devs{$ip}{comm} = $dev->{snmp_comm}; | |
$devs{$ip}{vers} = $dev->{snmp_ver} == 2 ? "2c" : "1"; | |
# Build list of applicable groups | |
my @dg; | |
push @dg, "all_devices"; | |
foreach my $col qw(model vtp_domain vendor os os_ver) { | |
my $g; | |
# Changing this to exists/defined will cause "" and "0" fields | |
# to be given groups, which is undesirable | |
if($dev->{$col}) { | |
$g = $col . "-" . clean_name($dev->{$col}); | |
# Add to global group tracking | |
$groups{$g} = "Devices with $col of $dev->{$col}"; | |
} else { | |
$g = "no-$col"; | |
# Add to global group tracking | |
$groups{$g} = "Devices with no value for $col"; | |
} | |
# Add to local dev groups | |
push @dg, $g; | |
} | |
# associate groups with device | |
$devs{$ip}{groups} = join(",", sort @dg); | |
} | |
my $links = make_links($mgmt_vlans); | |
my %distance = ( $starter => 1 ); | |
my @vertices = ( $starter ); | |
# We put $starter in edges since we use edges to judge existence in the graph | |
my %edges = ( $starter => {} ); | |
# Build %edges | |
while(my $parent = shift @vertices) { | |
my $pn = $devs{$parent}{dns}; | |
next if(!defined $pn); | |
my $pdist = $distance{$parent}; | |
foreach my $child (keys %{$links->{$parent}}) { | |
# Skip devs that we don't want to track | |
next if !exists $devs{$child}; | |
my $cn = $devs{$child}{dns}; | |
next if(!defined $cn); | |
my $cdist = $pdist + 1; | |
if(!exists $distance{$child}) { | |
$debug and print STDERR "first: $cn -> $pn dist $cdist\n"; | |
$distance{$child} = $cdist; | |
push @vertices, $child; | |
$edges{$child}{$parent} = $cdist; | |
} elsif($distance{$child} > $pdist) { | |
$debug and print STDERR "extra: $cn -> $pn dist $cdist\n"; | |
$edges{$child}{$parent} = $cdist; | |
} else { | |
$debug and print STDERR "skip : $cn -> $pn dist $cdist\n"; | |
} | |
} | |
} | |
my %deps; | |
# Seed %deps with direct dependencies | |
foreach my $cn (keys %edges) { | |
foreach my $pn (keys %{$edges{$cn}}) { | |
$deps{$pn}{$cn} = 1; | |
} | |
} | |
# add one degree of indirect dependencies, furthest distance first (if we do | |
# this, we can do them in one pass, rather than recursing on each node). | |
foreach my $pn (reverse sort { $distance{$a} cmp $distance{$b} } (keys %distance)) { | |
foreach my $cn (keys %{$deps{$pn}}) { | |
foreach my $ccn (keys %{$deps{$cn}}) { | |
$deps{$pn}{$ccn} = 1; | |
} | |
} | |
} | |
# Old graph generation used during script testing | |
if($mode eq "graph") { | |
print "digraph test{\ngraph [size=\"30,30\", ratio=fill, epsilon=\"0.0000001\", fontsize=\"46.0\", nodesep=2, overlap=scale, ranksep=\".3\", splines=false, ratio=compress];\n"; | |
foreach my $child (keys %edges) { | |
my $cn = $devs{$child}{dns}; | |
foreach my $parent (keys %{$edges{$child}}) { | |
my $pn = $devs{$parent}{dns}; | |
print "\"". $cn ."\" -> \"". $pn ."\";\n"; | |
} | |
} | |
print "}\n"; | |
} | |
# Generate Nagios config | |
if($mode eq "nagios") { | |
print "\n\n### Hostgroup definitions\n\n"; | |
foreach my $group (sort keys %groups) { | |
print <<EOF | |
define hostgroup { | |
hostgroup_name $group | |
alias $groups{$group} | |
} | |
EOF | |
} | |
print "\n\n### Host definitions\n\n"; | |
foreach my $ip (sort keys %edges) { | |
# Note that we sort at various points to always produce the | |
# same ordering so we can detect meaningful config changes | |
my $parents = join(",", sort(map { $devs{$_}{name} } keys %{$edges{$ip}})); | |
my $deps = (join(",", sort(map { $devs{$_}{name} } keys %{$deps{$ip}})) or "none"); | |
print <<EOF | |
define host { | |
use device-template | |
host_name $devs{$ip}{name} | |
alias $devs{$ip}{dns} | |
address $ip | |
parents $parents | |
hostgroups $devs{$ip}{groups} | |
notes_url $baseurl/device.html?ip=$ip | |
_DEPENDENTS $deps | |
_COMMUNITY $devs{$ip}{comm} | |
_VERSION $devs{$ip}{vers} | |
} | |
EOF | |
} | |
} | |
# Lost devices checking | |
$debug and printf STDERR "devices: %d vs %d\n", scalar(keys %devs), scalar(keys %edges); | |
for my $dev (keys %devs) { | |
if(!exists $edges{$dev}) { | |
print STDERR "Missed $dev\n"; | |
} | |
} | |
exit 0; | |
sub clean_name { | |
my $name = shift; | |
return undef if !defined $name; | |
$name =~ s/\W/_/g; | |
return lc($name); | |
} | |
sub make_links { | |
my $vlans = shift; | |
my $aliases = sql_column('device_ip',['alias','ip']); | |
my $links; | |
if(defined $vlans) { | |
my @v = split(/,/, $vlans); | |
$links = sql_rows('device_port d join device_port_vlan v on v.ip = d.ip AND d.port = v.port',['d.ip','d.remote_ip','d.remote_type'],{'remote_ip' => 'IS NOT NULL', 'v.vlan' => [\@v] }); | |
# Grab routed links (links without native vlans) | |
my $extra = sql_rows('device_port',['ip','remote_ip','remote_type'],{'remote_ip' => 'IS NOT NULL', 'pvid' => 'IS NULL'}); | |
push @$links, @$extra; | |
# Grab port channels from broken 3750s running 12.2(58)SE2 | |
my $extra2 = sql_rows('device_port',['ip','remote_ip','remote_type'],{'remote_ip' => 'IS NOT NULL', 'vlan' => 'IS NULL'}); | |
push @$links, @$extra2; | |
} else { | |
$links = sql_rows('device_port',['ip','remote_ip','remote_type'],{'remote_ip' => 'IS NOT NULL'}); | |
} | |
my %link_seen; | |
foreach my $link (@$links){ | |
my $source = $link->{ip}; | |
my $dest = $link->{remote_ip}; | |
my $type = $link->{remote_type}; | |
# Check for Aliases | |
if (defined $aliases->{$dest} ){ | |
# Set to root device | |
$dest = $aliases->{$dest}; | |
} | |
# Remove loopback - After alias check (bbaetz) | |
if ($source eq $dest) { | |
$debug and print STDERR "Loopback on $source\n"; | |
next; | |
} | |
# Skip IP Phones | |
if (defined $type and $type =~ /ip.phone/i){ | |
$debug and print STDERR "Skipping IP Phone. $source -> $dest ($type)\n"; | |
next; | |
} | |
next if exists $link_seen{$source}->{$dest}; | |
# assume all links are bidirectional | |
$link_seen{$source}->{$dest}++; | |
$link_seen{$dest}->{$source}++; | |
} | |
return \%link_seen; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment