check_junos.pl 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. #!/usr/bin/perl
  2. #############################################################################
  3. # (c) 2001, 2003 Juniper Networks, Inc. #
  4. # (c) 2011-2012 Sebastian "tokkee" Harl <sh@teamix.net> #
  5. # and team(ix) GmbH, Nuernberg, Germany #
  6. # #
  7. # This file is part of "team(ix) Monitoring Plugins" #
  8. # URL: http://oss.teamix.org/projects/monitoringplugins/ #
  9. # #
  10. # All rights reserved. #
  11. # Redistribution and use in source and binary forms, with or without #
  12. # modification, are permitted provided that the following conditions #
  13. # are met: #
  14. # 1. Redistributions of source code must retain the above copyright #
  15. # notice, this list of conditions and the following disclaimer. #
  16. # 2. Redistributions in binary form must reproduce the above copyright #
  17. # notice, this list of conditions and the following disclaimer in the #
  18. # documentation and/or other materials provided with the distribution. #
  19. # 3. The name of the copyright owner may not be used to endorse or #
  20. # promote products derived from this software without specific prior #
  21. # written permission. #
  22. # #
  23. # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR #
  24. # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED #
  25. # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE #
  26. # DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, #
  27. # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES #
  28. # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR #
  29. # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) #
  30. # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, #
  31. # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING #
  32. # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE #
  33. # POSSIBILITY OF SUCH DAMAGE. #
  34. #############################################################################
  35. use strict;
  36. use warnings;
  37. use utf8;
  38. use JUNOS::Device;
  39. use JUNOS::Trace;
  40. use FindBin qw( $Bin );
  41. use lib "$Bin/perl/lib";
  42. use Nagios::Plugin::JUNOS;
  43. binmode STDOUT, ":utf8";
  44. # TODO:
  45. # * chassis_routing_engine: show chassis routing-engine (-> number and status)
  46. my $plugin = Nagios::Plugin::JUNOS->new(
  47. plugin => 'check_junos',
  48. shortname => 'check_junos',
  49. version => '0.1',
  50. url => 'http://oss.teamix.org/projects/monitoringplugins',
  51. blurb => 'Monitor Juniper™ Switches.',
  52. usage =>
  53. "Usage: %s [-v|--verbose] [-H <host>] [-p <port>] [-t <timeout]
  54. [-C] [-U <user>] [-P <password] check-tuple [...]",
  55. license =>
  56. "This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY.
  57. It may be used, redistributed and/or modified under the terms of the 3-Clause
  58. BSD License (see http://opensource.org/licenses/BSD-3-Clause).",
  59. extra => "
  60. This plugin connects to a Juniper™ Switch device and checks various of its
  61. components.
  62. A check-tuple consists of the name of the check and, optionally, a \"target\"
  63. which more closely specifies which characteristics should be checked, and
  64. warning and critical thresholds:
  65. checkname[,target[,warning[,critical]]]
  66. The following checks are available:
  67. * interfaces: Status of interfaces. If a target is specified, only the
  68. specified interface(s) will be taken into account. The special target
  69. '\@with_description' selects all interfaces with a non-empty description.
  70. If an aggregated interface is encountered, the physical interfaces will
  71. be checked as well.
  72. * interface_forwarding: Check the forwarding state of interfaces as provided
  73. by 'show ethernet-switching interfaces'. Storm control, MAC limit and
  74. BPDUs will be considered CRITICAL states. If a target is specified, only
  75. the specified interface(s) will be taken into account. Targets may be
  76. specified as <interface_name>:<forwarding_state> in which case a CRITICAL
  77. state is assumed if the specified interface is not in the specified state.
  78. * chassis_environment: Check the status of verious system components
  79. (as provided by 'show chassis environment'). If a target is specified,
  80. only the specified component(s) will be taken into account. If specified,
  81. the thresholds will be checked against the temperature of the components.
  82. * system_storage: Check the amount of used space of system filesystems. If a
  83. target is specified, only the specified filesystem(s) will be taken into
  84. account (specified either by filesystem name or mount point). The
  85. threshold will be checked against the amount (percent) of used space.
  86. Warning and critical thresholds may be specified in the format documented at
  87. http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT.",
  88. );
  89. my %checks = (
  90. interfaces => \&check_interfaces,
  91. interface_forwarding => \&check_interface_forwarding,
  92. chassis_environment => \&check_chassis_environment,
  93. system_storage => \&check_system_storage,
  94. );
  95. my $junos = undef;
  96. my $cache = {};
  97. $plugin->add_common_args();
  98. $plugin->add_arg({
  99. spec => 'caching|C',
  100. usage => '-C, --caching',
  101. desc => 'Enabling caching of API results',
  102. default => 0,
  103. });
  104. foreach my $check (keys %checks) {
  105. $plugin->add_check_impl($check, $checks{$check});
  106. }
  107. $plugin->set_default_check('chassis_environment');
  108. # configure removes any options from @ARGV
  109. $plugin->configure();
  110. if ($plugin->{'conf'}->{'verbose'} > 3) {
  111. JUNOS::Trace::init(1);
  112. }
  113. $plugin->set_checks(@ARGV);
  114. $junos = $plugin->connect();
  115. $plugin->run_checks();
  116. my ($code, $msg) = $plugin->check_messages(join => ', ');
  117. $plugin->nagios_exit($code, $msg);
  118. sub check_interface
  119. {
  120. my $plugin = shift;
  121. my $iface = shift;
  122. my $opts = shift || {};
  123. my @targets = @_;
  124. my $name = $plugin->get_query_object_value($iface, 'name');
  125. my $admin_status = $plugin->get_query_object_value($iface, 'admin-status');
  126. if ($admin_status !~ m/^up$/) {
  127. if ((grep { $name =~ m/^$_$/; } @targets)
  128. || ($opts->{'with_description'} &&
  129. $plugin->get_query_object_value($iface, 'description'))) {
  130. $plugin->add_message(CRITICAL,
  131. "$name is not enabled");
  132. return -1;
  133. }
  134. return 1;
  135. }
  136. if ($plugin->get_query_object_value($iface, 'oper-status') !~ m/^up$/i) {
  137. return 0;
  138. }
  139. $plugin->add_perfdata(
  140. label => "'$name-input-bytes'",
  141. value => $plugin->get_query_object_value($iface,
  142. ['traffic-statistics', 'input-bytes']),
  143. min => 0,
  144. max => undef,
  145. uom => 'B',
  146. threshold => undef,
  147. );
  148. $plugin->add_perfdata(
  149. label => "'$name-output-bytes'",
  150. value => $plugin->get_query_object_value($iface,
  151. ['traffic-statistics', 'output-bytes']),
  152. min => 0,
  153. max => undef,
  154. uom => 'B',
  155. threshold => undef,
  156. );
  157. return 1;
  158. }
  159. sub get_interfaces
  160. {
  161. my $plugin = shift;
  162. my $opts = shift || {};
  163. my @targets = @_;
  164. my @ifaces = ();
  165. my @ret = ();
  166. if (defined($cache->{'interfaces'})) {
  167. @ifaces = @{$cache->{'interfaces'}};
  168. }
  169. else {
  170. my $cmd = 'get_interface_information';
  171. my $res;
  172. my $args = { detail => 1 };
  173. if ((scalar(@targets) == 1) && (! $opts->{'with_description'})
  174. && (! $plugin->{'conf'}->{'caching'})) {
  175. $args->{'interface_name'} = $targets[0];
  176. }
  177. $res = $plugin->send_query($cmd, $args);
  178. if (! ref $res) {
  179. $plugin->die($res);
  180. }
  181. @ifaces = $res->getElementsByTagName('physical-interface');
  182. }
  183. if ($plugin->{'conf'}->{'caching'}) {
  184. $cache->{'interfaces'} = \@ifaces;
  185. }
  186. @targets = map { s/\*/\.\*/g; s/\?/\./g; $_; } @targets;
  187. if (scalar(@targets)) {
  188. @ret = grep {
  189. my $i = $_;
  190. grep { $plugin->get_query_object_value($i, 'name') =~ m/^$_$/ } @targets;
  191. } @ifaces;
  192. }
  193. elsif (! $opts->{'with_description'}) {
  194. @ret = @ifaces;
  195. }
  196. if ($opts->{'with_description'}) {
  197. foreach my $iface (@ifaces) {
  198. my $name = $plugin->get_query_object_value($iface, 'name');
  199. if ($plugin->get_query_object_value($iface, 'description')
  200. && (! grep { m/^$name$/; } @targets)) {
  201. push @ret, $iface;
  202. }
  203. }
  204. }
  205. {
  206. my @i = map { $plugin->get_query_object_value($_, 'name'). " => "
  207. . $plugin->get_query_object_value($_, 'oper-status') } @ret;
  208. $plugin->verbose(3, "Interfaces: " . join(", ", @i));
  209. }
  210. return @ret;
  211. }
  212. sub check_interfaces
  213. {
  214. my $plugin = shift;
  215. my $junos = shift;
  216. my $targets = shift || [];
  217. my $exclude = shift || [];
  218. my $opts = {
  219. with_description => 0,
  220. };
  221. if (grep { m/^\@with_description$/; } @$targets) {
  222. $opts->{'with_description'} = 1;
  223. @$targets = grep { ! m/^\@with_description$/; } @$targets;
  224. }
  225. my @interfaces = get_interfaces($plugin, $opts, @$targets);;
  226. my $down_count = 0;
  227. my @down_ifaces = ();
  228. my $phys_down_count = 0;
  229. my @phys_down_ifaces = ();
  230. my $have_lag_ifaces = 0;
  231. foreach my $iface (@interfaces) {
  232. my $name = $plugin->get_query_object_value($iface, 'name');
  233. my $desc = $plugin->get_query_object_value($iface, 'description');
  234. my $status;
  235. if (grep { m/^$name$/ } @$exclude) {
  236. next;
  237. }
  238. $status = check_interface($plugin, $iface, $opts, @$targets);
  239. my $tmp;
  240. if ($status == 0) {
  241. ++$down_count;
  242. $tmp = $name . ($desc ? " ($desc)" : "");
  243. push @down_ifaces, $tmp;
  244. }
  245. if ($status <= 0) {
  246. # disabled or down
  247. next;
  248. }
  249. if ($name !~ m/^ae/) {
  250. next;
  251. }
  252. $have_lag_ifaces = 1;
  253. my @markers = $plugin->get_query_object($iface,
  254. ['logical-interface', 'lag-traffic-statistics', 'lag-marker']);
  255. if (! @markers) {
  256. print STDERR "Cannot get marker for non-LACP interfaces yet!\n";
  257. next;
  258. }
  259. foreach my $marker (@markers) {
  260. my $phy_name = $plugin->get_query_object_value($marker, 'name');
  261. $phy_name =~ s/\.\d+$//;
  262. $plugin->verbose(3, "Quering physical interface '$phy_name' "
  263. . "for $name.");
  264. my @phy_interfaces = get_interfaces($plugin, {}, $phy_name);
  265. foreach my $phy_iface (@phy_interfaces) {
  266. if (check_interface($plugin, $phy_iface, {}, $phy_name) == 0) {
  267. ++$phys_down_count;
  268. $tmp = $name . ($desc ? " ($desc)" : "");
  269. push @phys_down_ifaces, "$tmp -> $phy_name";
  270. }
  271. }
  272. }
  273. }
  274. if ($down_count > 0) {
  275. $plugin->add_message(CRITICAL, $down_count . " interface"
  276. . ($down_count == 1 ? "" : "s")
  277. . " down (" . join(", ", @down_ifaces) . ")");
  278. }
  279. if ($phys_down_count > 0) {
  280. $plugin->add_message(WARNING, $phys_down_count
  281. . " LAG member interface"
  282. . ($phys_down_count == 1 ? "" : "s")
  283. . " down ("
  284. . join(", ", @phys_down_ifaces) . ")");
  285. }
  286. if ((! $down_count) && (! $phys_down_count)) {
  287. if ((! scalar(@$targets)) || $opts->{'with_description'}) {
  288. $plugin->add_message(OK, "all interfaces up"
  289. . ($have_lag_ifaces
  290. ? " (including all LAG member interfaces)" : ""));
  291. }
  292. else {
  293. $plugin->add_message(OK, "interface"
  294. . (scalar(@$targets) == 1 ? " " : "s ")
  295. . join(", ", @$targets) . " up"
  296. . ($have_lag_ifaces
  297. ? " (including all LAG member interfaces)" : ""));
  298. }
  299. }
  300. }
  301. sub check_interface_forwarding
  302. {
  303. my $plugin = shift;
  304. my $junos = shift;
  305. my $targets = shift || [];
  306. my $exclude = shift || [];
  307. my $res = $plugin->send_query('show ethernet-switching interfaces brief');
  308. my %critical_map = (
  309. 'Disabled by bpdu-control' => 1,
  310. 'MAC limit exceeded' => 1,
  311. 'MAC move limit exceeded' => 1,
  312. 'Storm control in effect' => 1,
  313. );
  314. my %targets = map { my @t = split(':', $_); $t[0] => $t[1]; } @$targets;
  315. my @failed = ();
  316. {
  317. my @i = map {
  318. $plugin->get_query_object_value($_, 'interface-name')
  319. . " => { " .
  320. join(", ", map {
  321. $plugin->get_query_object_value($_, 'blocking-status')
  322. } $plugin->get_query_object($_,
  323. ['interface-vlan-member-list', 'interface-vlan-member']))
  324. . " }"
  325. } $plugin->get_query_object($res, 'interface');
  326. $plugin->verbose(3, "Interfaces: " . join(", ", @i));
  327. }
  328. foreach my $iface ($plugin->get_query_object($res, 'interface')) {
  329. my $name = $plugin->get_query_object_value($iface, 'interface-name');
  330. my $failed_status = undef;
  331. if (scalar(@$targets) && (! exists($targets{$name}))) {
  332. next;
  333. }
  334. if (grep { m/^$name$/ } @$exclude) {
  335. next;
  336. }
  337. foreach my $vlan_member ($plugin->get_query_object($iface,
  338. ['interface-vlan-member-list', 'interface-vlan-member'])) {
  339. my $status = $plugin->get_query_object_value($vlan_member,
  340. 'blocking-status');
  341. if (defined($targets{$name})) {
  342. if ($status ne $targets{$name}) {
  343. $failed_status = "$status (should: $targets{$name})";
  344. last;
  345. }
  346. }
  347. elsif (defined $critical_map{$status}) {
  348. $failed_status = $status;
  349. last;
  350. }
  351. }
  352. if ($failed_status) {
  353. push @failed, { name => $name, status => $failed_status };
  354. }
  355. }
  356. if (scalar(@failed)) {
  357. $plugin->add_message(CRITICAL, scalar(@failed) . " interface"
  358. . (scalar(@failed) == 1 ? "" : "s")
  359. . " blocked: "
  360. . join(", ", map { "$_->{'name'}: $_->{'status'}" } @failed));
  361. }
  362. else {
  363. $plugin->add_message(OK, "forwarding state of all interfaces OK");
  364. }
  365. }
  366. sub check_chassis_environment
  367. {
  368. my $plugin = shift;
  369. my $junos = shift;
  370. my $targets = shift || [];
  371. my $exclude = shift || [];
  372. my $res = $plugin->send_query('get_environment_information');
  373. my %status_map = (
  374. OK => OK,
  375. Testing => UNKNOWN,
  376. Check => UNKNOWN,
  377. Failed => CRITICAL,
  378. Absent => CRITICAL,
  379. );
  380. my $items_count = 0;
  381. my $items_ok = 0;
  382. my $class = "";
  383. foreach my $item ($plugin->get_query_object($res, 'environment-item')) {
  384. my $name = $plugin->get_query_object_value($item, 'name');
  385. if (scalar(@$targets) && (! grep { m/^$name$/ } @$targets)) {
  386. next;
  387. }
  388. if (grep { m/^$name$/ } @$exclude) {
  389. next;
  390. }
  391. if ($plugin->get_query_object_value($item, 'class')) {
  392. $class = $plugin->get_query_object_value($item, 'class');
  393. }
  394. my $status = $plugin->get_query_object_value($item, 'status');
  395. if ($status eq "Absent") {
  396. if (! scalar(@$targets)) {
  397. next;
  398. }
  399. # else: check this component
  400. }
  401. my $state = UNKNOWN;
  402. if (defined $status_map{$status}) {
  403. $state = $status_map{$status};
  404. }
  405. ++$items_count;
  406. if ($state == OK) {
  407. ++$items_ok;
  408. }
  409. else {
  410. $plugin->add_message($state, $class . " $name: status " .
  411. $status);
  412. }
  413. my $temp = $plugin->get_query_object_value($item, 'temperature');
  414. if (! $temp) {
  415. next;
  416. }
  417. ($temp) = $temp =~ m/(\d+) degrees C/;
  418. if (! defined($temp)) {
  419. next;
  420. }
  421. $state = $plugin->check_threshold($temp);
  422. if ($state != OK) {
  423. $plugin->add_message($state, $class
  424. . " $name: ${temp} degrees C");
  425. }
  426. my $label = "$name-temp";
  427. $label =~ s/ /_/g;
  428. $plugin->add_perfdata(
  429. label => "'$label'",
  430. value => $temp,
  431. min => undef,
  432. max => undef,
  433. uom => '',
  434. threshold => $plugin->threshold(),
  435. );
  436. }
  437. if (! $items_count) {
  438. $plugin->add_message(WARNING, "no components found");
  439. }
  440. elsif ($items_count == $items_ok) {
  441. $plugin->add_message(OK, "$items_ok component"
  442. . ($items_ok == 1 ? "" : "s")
  443. . " OK");
  444. }
  445. else {
  446. $plugin->add_message(WARNING,
  447. "$items_ok / $items_count components OK");
  448. }
  449. }
  450. sub check_system_storage
  451. {
  452. my $plugin = shift;
  453. my $junos = shift;
  454. my $targets = shift || [];
  455. my $exclude = shift || [];
  456. my $res = $plugin->send_query('get_system_storage');
  457. my $all_ok = 1;
  458. foreach my $re ($plugin->get_query_object($res,
  459. 'multi-routing-engine-item')) {
  460. my $re_name = $plugin->get_query_object_value($re, 're-name');
  461. foreach my $fs ($plugin->get_query_object($re,
  462. ['system-storage-information', 'filesystem'])) {
  463. my $name = $plugin->get_query_object_value($fs, 'filesystem-name');
  464. my $mnt_pt = $plugin->get_query_object_value($fs, 'mounted-on');
  465. if (scalar(@$targets) && (! grep { m/^$name$/ } @$targets)
  466. && (! grep { m/^$mnt_pt$/ } @$targets)) {
  467. next;
  468. }
  469. if (grep { m/^$mnt_pt$/ } @$exclude) {
  470. next;
  471. }
  472. my $used = $plugin->get_query_object_value($fs, 'used-percent') + 0;
  473. my $state = $plugin->check_threshold($used);
  474. if ($state != OK) {
  475. $all_ok = 0;
  476. $plugin->add_message($state, "$re_name $mnt_pt: "
  477. . "$used\% used");
  478. }
  479. $plugin->add_perfdata(
  480. label => "'$re_name-$mnt_pt'",
  481. value => $used,
  482. min => 0,
  483. max => 100,
  484. uom => '%',
  485. threshold => $plugin->threshold(),
  486. );
  487. }
  488. }
  489. if ($all_ok) {
  490. $plugin->add_message(OK, "all filesystems within thresholds");
  491. }
  492. }