Skip to content

Instantly share code, notes, and snippets.

@bldewolf
Created June 26, 2014 23:54
Show Gist options
  • Save bldewolf/0a767cabd71ce2ea0aee to your computer and use it in GitHub Desktop.
Save bldewolf/0a767cabd71ce2ea0aee to your computer and use it in GitHub Desktop.
Netdisco 1.x to Nagios config with dependencies generator
#!/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