use
Dancer
qw/:syntax :script/
;
our
@EXPORT
= ();
our
@EXPORT_OK
=
qw/
load_cache_for_device
add_snmpinfo_aliases
make_snmpwalk_browsable
/
;
our
%EXPORT_TAGS
= (
all
=> \
@EXPORT_OK
);
sub
load_cache_for_device {
my
$device
=
shift
;
return
{}
unless
(
$device
->is_pseudo or not
$device
->in_storage);
my
$pseudo_cache
= catfile( catdir((
$ENV
{NETDISCO_HOME} ||
$ENV
{HOME}),
'logs'
,
'snapshots'
),
$device
->ip );
my
$loadmibs
= schema(
'netdisco'
)->resultset(
'SNMPObject'
)->count;
if
(-f
$pseudo_cache
and not
$loadmibs
) {
warning
"device snapshot exists ($pseudo_cache) but no MIB data available."
;
warning
'skipping offline cache load - run a "loadmibs" job if you want this!'
;
return
{};
}
my
%oids
= ();
if
(
$device
->is_pseudo
and not
$device
->oids->search({
-or
=> [
-bool
=> \
q{ array_length(oid_parts, 1) IS NULL }
,
-bool
=> \
q{ jsonb_typeof(value) != 'array' }
, ] })->count) {
my
@rows
=
$device
->oids->search({},{
join
=>
'oid_fields'
,
columns
=> [
qw/oid value/
],
select
=> [
qw/oid_fields.mib oid_fields.leaf/
],
as
=> [
qw/mib leaf/
],
})->hri->all;
$oids
{
$_
->{oid}} = {
%{
$_
},
value
=> (@{ from_json(
$_
->{value}) })[0],
}
for
@rows
;
}
elsif
(-f
$pseudo_cache
and not
$device
->in_storage) {
debug
sprintf
"importing snmpwalk from disk ($pseudo_cache)"
;
my
@lines
= read_lines(
$pseudo_cache
);
my
%store
= ();
if
(
$lines
[0] !~ m/^.\d/) {
warning
'snapshot file rejected - has translated names/values instead of numeric'
;
return
{};
}
foreach
my
$line
(
@lines
) {
my
(
$oid
,
$type
,
$value
) =
$line
=~ m/^(\S+)\s+=\s+(?:([^:]+):\s+)?(.+)$/;
next
unless
$oid
and
$value
;
$value
=
''
if
$value
=~ m/^[^:]+: ?$/;
$value
=~ s/^"//;
$value
=~ s/"$//;
$store
{
$oid
} = {
oid
=>
$oid
,
oid_parts
=> [],
value
=> to_json([ ((
defined
$type
and
$type
eq
'BASE64'
) ?
$value
: encode_base64(
$value
,
''
)) ]),
};
}
schema(
'netdisco'
)->txn_do(
sub
{
$device
->oids->
delete
;
$device
->oids->populate([
values
%store
]);
});
%oids
= make_snmpwalk_browsable(
$device
);
$oids
{
$_
}->{value} = (@{ from_json(
$oids
{
$_
}->{value} ) })[0]
for
keys
%oids
;
}
return
snmpwalk_to_snmpinfo_cache(
%oids
);
}
sub
make_snmpwalk_browsable {
my
$device
=
shift
;
my
%oids
= ();
my
%value_oids
=
map
{(
$_
=> 1)}
$device
->oids->get_column(
'oid'
)->all;
my
%table_oids
= ();
foreach
my
$orig_oid
(
keys
%value_oids
) {
(
my
$oid
=
$orig_oid
) =~ s/\.\d+$//;
my
$new_oid
=
''
;
while
(
length
(
$oid
)) {
$oid
=~ s/^(\.\d+)//;
$new_oid
.= $1;
$table_oids
{
$new_oid
} = {
oid
=>
$new_oid
,
oid_parts
=> []}
unless
exists
$value_oids
{
$new_oid
};
}
}
$device
->oids->populate([
values
%table_oids
]);
my
@rows
=
$device
->oids->search({},{
join
=>
'oid_fields'
,
columns
=> [
qw/oid value/
],
select
=> [
qw/oid_fields.mib oid_fields.leaf oid_fields.enum/
],
as
=> [
qw/mib leaf enum/
],
})->hri->all;
$oids
{
$_
->{oid}} = {
%{
$_
},
value
=> (
defined
$_
->{value} ? decode_base64( (@{ from_json(
$_
->{value}) })[0] ) :
q{}
),
}
for
grep
{
$_
->{leaf} or
length
( (@{ from_json(
$_
->{value}) })[0] )}
@rows
;
%oids
= collapse_snmp_tables(
%oids
);
%oids
= resolve_enums(
%oids
);
foreach
my
$k
(
keys
%oids
) {
my
$value
= (
defined
$oids
{
$k
}->{value} ?
$oids
{
$k
}->{value} :
q{}
);
if
(
ref
{} eq
ref
$value
) {
$oids
{
$k
}->{value} = to_json([{
map
{(
$_
=> encode_base64(
$value
->{
$_
},
''
))}
keys
%{
$value
} }]);
}
else
{
$oids
{
$k
}->{value} = to_json([encode_base64(
$value
,
''
)]);
}
$oids
{
$k
}->{oid_parts} = [
grep
{
length
} (
split
m/\./,
$oids
{
$k
}->{oid}) ];
}
schema(
'netdisco'
)->txn_do(
sub
{
$device
->oids->
delete
;
$device
->oids->populate([
map
{
{
oid
=>
$_
->{oid},
oid_parts
=>
$_
->{oid_parts},
value
=>
$_
->{value} }
}
values
%oids
]);
debug
sprintf
'replaced %d browsable oids in db'
,
scalar
keys
%oids
;
});
return
%oids
;
}
sub
collapse_snmp_tables {
my
%oids
=
@_
;
return
()
unless
scalar
keys
%oids
;
OID:
foreach
my
$orig_oid
(
sort
{sortable_oid(
$a
) cmp sortable_oid(
$b
)}
keys
%oids
) {
my
$oid
=
$orig_oid
;
my
$idx
=
''
;
while
(
length
(
$oid
) and !
defined
$oids
{
$oid
}->{leaf}) {
$oid
=~ s/\.(\d+)$//;
$idx
= (
length
$idx
?
"${1}.${idx}"
: $1);
}
if
(0 ==
length
(
$oid
)) {
delete
$oids
{
$orig_oid
};
next
OID;
}
$idx
||=
'.0'
;
$idx
=~ s/^\.//;
if
(
$idx
eq
'0'
) {
if
(
$oid
eq
$orig_oid
and
$oid
=~ m/\.0$/) {
}
else
{
$oids
{
$oid
}->{value} =
$oids
{
$orig_oid
}->{value};
}
}
else
{
$oids
{
$oid
}->{value} = {}
if
ref
{} ne
ref
$oids
{
$oid
}->{value};
$oids
{
$oid
}->{value}->{
$idx
} =
$oids
{
$orig_oid
}->{value};
}
delete
$oids
{
$orig_oid
}
if
$orig_oid
ne
$oid
;
}
delete
$oids
{
$_
}
for
grep
{!
defined
$oids
{
$_
}->{value}
or (
ref
q{}
eq
ref
$oids
{
$_
}->{value} and
$oids
{
$_
}->{value} eq
''
)}
keys
%oids
;
return
%oids
;
}
sub
resolve_enums {
my
%oids
=
@_
;
return
()
unless
scalar
keys
%oids
;
foreach
my
$oid
(
keys
%oids
) {
next
unless
$oids
{
$oid
}->{enum};
my
$value
=
$oids
{
$oid
}->{value};
my
%emap
=
map
{
reverse
split
m/\(/ }
map
{ s/\)//;
$_
}
@{
$oids
{
$oid
}->{enum} };
if
(
ref
q{}
eq
ref
$value
) {
$oids
{
$oid
}->{value} =
$emap
{
$value
}
if
exists
$emap
{
$value
};
}
elsif
(
ref
{} eq
ref
$value
) {
foreach
my
$k
(
keys
%$value
) {
$oids
{
$oid
}->{value}->{
$k
} =
$emap
{
$value
->{
$k
} }
if
exists
$emap
{
$value
->{
$k
} };
}
}
}
return
%oids
;
}
sub
snmpwalk_to_snmpinfo_cache {
my
%walk
=
@_
;
return
()
unless
scalar
keys
%walk
;
foreach
my
$oid
(
keys
%walk
) {
my
$value
=
$walk
{
$oid
}->{value};
if
(
ref
q{}
eq
ref
$value
) {
$walk
{
$oid
}->{value} = decode_base64(
$walk
{
$oid
}->{value});
}
elsif
(
ref
{} eq
ref
$value
) {
foreach
my
$k
(
keys
%$value
) {
$walk
{
$oid
}->{value}->{
$k
}
= decode_base64(
$walk
{
$oid
}->{value}->{
$k
});
}
}
}
my
$info
= SNMP::Info->new({
Offline
=> 1,
Cache
=> {},
Session
=> {},
MibDirs
=> [ get_mibdirs() ],
AutoSpecify
=> 0,
IgnoreNetSNMPConf
=> 1,
Debug
=> (
$ENV
{INFO_TRACE} || 0),
DebugSNMP
=> (
$ENV
{SNMP_TRACE} || 0),
});
foreach
my
$oid
(
keys
%walk
) {
my
$qleaf
=
$walk
{
$oid
}->{mib} .
'::'
.
$walk
{
$oid
}->{leaf};
(
my
$snmpqleaf
=
$qleaf
) =~ s/[-:]/_/g;
$info
->_cache(
$walk
{
$oid
}->{leaf},
$walk
{
$oid
}->{value});
$info
->_cache(
$snmpqleaf
,
$walk
{
$oid
}->{value});
}
return
add_snmpinfo_aliases(
$info
);
}
sub
add_snmpinfo_aliases {
my
$info
=
shift
or
return
{};
if
(not blessed
$info
) {
$info
= SNMP::Info->new({
Offline
=> 1,
Cache
=>
$info
,
Session
=> {},
MibDirs
=> [ get_mibdirs() ],
AutoSpecify
=> 0,
IgnoreNetSNMPConf
=> 1,
Debug
=> (
$ENV
{INFO_TRACE} || 0),
DebugSNMP
=> (
$ENV
{SNMP_TRACE} || 0),
});
}
my
%globals
= %{
$info
->globals };
my
%funcs
= %{
$info
->funcs };
while
(
my
(
$alias
,
$leaf
) =
each
%globals
) {
next
if
$leaf
=~ m/\.\d+$/;
$info
->_cache(
$alias
,
$info
->
$leaf
)
if
$info
->
$leaf
;
}
while
(
my
(
$alias
,
$leaf
) =
each
%funcs
) {
$info
->_cache(
$alias
, dclone
$info
->
$leaf
)
if
ref
q{}
ne
ref
$info
->
$leaf
;
}
my
%propfix
= (
chassisId
=>
'serial1'
,
ospfRouterId
=>
'router_ip'
,
bgpIdentifier
=>
'bgp_id'
,
bgpLocalAs
=>
'bgp_local_as'
,
ifPhysAddress
=>
'mac'
,
qw(
model model
serial serial
os_ver os_ver
os os
)
,
);
foreach
my
$prop
(
keys
%propfix
) {
my
$val
=
$info
->
$prop
;
$val
= [
values
%$val
]->[0]
if
ref
$val
eq
'HASH'
;
$info
->_cache(
$propfix
{
$prop
},
$val
);
}
if
(
defined
$info
->sysUpTimeInstance) {
my
$uptime
= (
ref
{} eq
ref
$info
->sysUpTimeInstance)
? (
$info
->sysUpTimeInstance->{0} ||
$info
->sysUpTimeInstance->{
''
})
:
$info
->sysUpTimeInstance;
if
(!
defined
$info
->uptime) {
$info
->_cache(
'uptime'
,
$uptime
);
}
if
(!
defined
$info
->sysUpTime) {
$info
->_cache(
'sysUpTime'
,
$uptime
);
}
}
while
(
my
$method
= <DATA>) {
$method
=~ s/\s//g;
next
unless
length
$method
and not
$info
->
$method
;
$info
->_cache(
$method
,
''
)
if
exists
$globals
{
$method
};
$info
->_cache(
$method
, {})
if
exists
$funcs
{
$method
};
}
return
$info
->cache;
}
true;