check_junos.pl 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  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 FindBin qw( $Bin );
  40. use lib "$Bin/perl/lib";
  41. use Nagios::Plugin::JUNOS;
  42. binmode STDOUT, ":utf8";
  43. # TODO:
  44. # * chassis_routing_engine: show chassis routing-engine (-> number and status)
  45. my $plugin = Nagios::Plugin::JUNOS->new(
  46. plugin => 'check_junos',
  47. shortname => 'check_junos',
  48. version => '0.1',
  49. url => 'http://oss.teamix.org/projects/monitoringplugins',
  50. blurb => 'Monitor Juniper™ Switches.',
  51. usage =>
  52. "Usage: %s [-v|--verbose] [-H <host>] [-p <port>] [-t <timeout]
  53. [-U <user>] [-P <password] check-tuple [...]",
  54. license =>
  55. "This nagios plugin is free software, and comes with ABSOLUTELY NO WARRANTY.
  56. It may be used, redistributed and/or modified under the terms of the 3-Clause
  57. BSD License (see http://opensource.org/licenses/BSD-3-Clause).",
  58. extra => "
  59. This plugin connects to a Juniper™ Switch device and checks various of its
  60. components.
  61. A check-tuple consists of the name of the check and, optionally, a \"target\"
  62. which more closely specifies which characteristics should be checked, and
  63. warning and critical thresholds:
  64. checkname[,target[,warning[,critical]]]
  65. The following checks are available:
  66. * interfaces: Status of interfaces. If a target is specified, only the
  67. specified interface is taken into account.
  68. If an aggregated interface is encountered, the physical interfaces will
  69. be checked as well.
  70. * chassis_environment: Check the status of verious system components
  71. (as provided by 'show chassis environment'). If specified, the thresholds
  72. will be checked against the temperature of the components.
  73. * system_storage: Check the amount of used space of system filesystems. The
  74. threshold will be checked against the amount (percent) of used space.
  75. Warning and critical thresholds may be specified in the format documented at
  76. http://nagiosplug.sourceforge.net/developer-guidelines.html#THRESHOLDFORMAT.",
  77. );
  78. my @args = (
  79. {
  80. spec => 'host|H=s',
  81. usage => '-H, --host=HOSTNAME',
  82. desc => 'Hostname/IP of Juniper box to connect to',
  83. default => 'localhost',
  84. },
  85. {
  86. spec => 'port|p=i',
  87. usage => '-p, --port=PORT',
  88. desc => 'Port to connect to',
  89. default => 22,
  90. },
  91. {
  92. spec => 'user|U=s',
  93. usage => '-U, --user=USERNAME',
  94. desc => 'Username to log into box as',
  95. default => 'root',
  96. },
  97. {
  98. spec => 'password|P=s',
  99. usage => '-P, --password=PASSWORD',
  100. desc => 'Password for login username',
  101. default => '<prompt>',
  102. },
  103. );
  104. my %checks = (
  105. interfaces => \&check_interfaces,
  106. chassis_environment => \&check_chassis_environment,
  107. system_storage => \&check_system_storage,
  108. );
  109. my $junos = undef;
  110. foreach my $arg (@args) {
  111. $plugin->add_arg($arg);
  112. }
  113. foreach my $check (keys %checks) {
  114. $plugin->add_check_impl($check, $checks{$check});
  115. }
  116. $plugin->set_default_check('chassis_environment');
  117. # configure removes any options from @ARGV
  118. $plugin->configure();
  119. $plugin->set_checks(@ARGV);
  120. $junos = $plugin->connect();
  121. $plugin->run_checks();
  122. my ($code, $msg) = $plugin->check_messages(join => ', ');
  123. $junos->disconnect();
  124. $plugin->nagios_exit($code, $msg);
  125. sub send_query
  126. {
  127. my $device = shift;
  128. my $query = shift;
  129. my $queryargs = shift;
  130. my $res;
  131. my $err;
  132. $plugin->verbose(3, "Sending query '$query' "
  133. . join(", ", map { "$_ => $queryargs->{$_}" } keys %$queryargs)
  134. . " to router.");
  135. if (ref $queryargs) {
  136. $res = $device->$query(%$queryargs);
  137. } else {
  138. $res = $device->$query();
  139. }
  140. if (! ref $res) {
  141. return "ERROR: Failed to execute query '$query'";
  142. }
  143. $err = $res->getFirstError();
  144. if ($err) {
  145. return "ERROR: " . $err->{message};
  146. }
  147. return $res;
  148. }
  149. sub check_interface {
  150. my $iface = shift;
  151. my $opts = shift || {};
  152. my @targets = @_;
  153. my $name = get_iface_name($iface);
  154. my $admin_status = get_iface_admin_status($iface);
  155. if ($admin_status !~ m/^up$/) {
  156. if ((grep { $name =~ m/^$_$/; } @targets)
  157. || ($opts->{'with_description'} &&
  158. get_iface_description($iface))) {
  159. $plugin->add_message(CRITICAL,
  160. "$name is not enabled");
  161. return -1;
  162. }
  163. return 1;
  164. }
  165. if (get_iface_status($iface) !~ m/^up$/i) {
  166. return 0;
  167. }
  168. $plugin->add_perfdata(
  169. label => "'$name-input-bytes'",
  170. value => get_iface_traffic($iface, "input"),
  171. min => 0,
  172. max => undef,
  173. uom => 'B',
  174. threshold => undef,
  175. );
  176. $plugin->add_perfdata(
  177. label => "'$name-output-bytes'",
  178. value => get_iface_traffic($iface, "output"),
  179. min => 0,
  180. max => undef,
  181. uom => 'B',
  182. threshold => undef,
  183. );
  184. return 1;
  185. }
  186. sub get_interfaces
  187. {
  188. my $device = shift;
  189. my $opts = shift || {};
  190. my @targets = @_;
  191. my @ifaces = ();
  192. my @ret = ();
  193. my $cmd = 'get_interface_information';
  194. my $res;
  195. my $args = { detail => 1 };
  196. if ((scalar(@targets) == 1) && (! $opts->{'with_description'})) {
  197. $args->{'interface_name'} = $targets[0];
  198. }
  199. $res = send_query($device, $cmd, $args);
  200. if (! ref $res) {
  201. $plugin->die($res);
  202. }
  203. @ifaces = $res->getElementsByTagName('physical-interface');
  204. @targets = map { s/\*/\.\*/g; s/\?/\./g; $_; } @targets;
  205. if (scalar(@targets)) {
  206. @ret = grep {
  207. my $i = $_;
  208. grep { get_iface_name($i) =~ m/^$_$/ } @targets;
  209. } @ifaces;
  210. }
  211. elsif (! $opts->{'with_description'}) {
  212. @ret = @ifaces;
  213. }
  214. if ($opts->{'with_description'}) {
  215. foreach my $iface (@ifaces) {
  216. my $name = get_iface_name($iface);
  217. if (get_iface_description($iface)
  218. && (! grep { m/^$name$/; } @targets)) {
  219. push @ret, $iface;
  220. }
  221. }
  222. }
  223. {
  224. my @i = map { get_iface_name($_) . " => " . get_iface_status($_) }
  225. @ret;
  226. $plugin->verbose(3, "Interfaces: " . join(", ", @i));
  227. }
  228. return @ret;
  229. }
  230. sub get_obj_element
  231. {
  232. my $obj = shift;
  233. my $elem = shift;
  234. $elem = $obj->getElementsByTagName($elem);
  235. if ((! $elem) || (! $elem->item(0))) {
  236. return;
  237. }
  238. return $elem->item(0)->getFirstChild->getNodeValue;
  239. }
  240. sub get_object_value
  241. {
  242. my $res = shift;
  243. if (! $res) {
  244. return;
  245. }
  246. if (ref($res) eq "XML::DOM::NodeList") {
  247. $res = $res->item(0);
  248. }
  249. return $res->getFirstChild->getNodeValue;
  250. }
  251. sub get_object_by_spec
  252. {
  253. my $res = shift;
  254. my $spec = shift;
  255. if (! $res) {
  256. return;
  257. }
  258. if (! $spec) {
  259. return $res;
  260. }
  261. if (! ref($spec)) {
  262. $spec = [ $spec ];
  263. }
  264. my $iter = $res;
  265. for (my $i = 0; $i < scalar(@$spec) - 1; ++$i) {
  266. my $tmp = $iter->getElementsByTagName($spec->[$i]);
  267. if ((! $tmp) || (! $tmp->item(0))) {
  268. return;
  269. }
  270. $iter = $tmp->item(0);
  271. }
  272. if (wantarray) {
  273. my @ret = $iter->getElementsByTagName($spec->[scalar(@$spec) - 1]);
  274. return @ret;
  275. }
  276. else {
  277. my $ret = $iter->getElementsByTagName($spec->[scalar(@$spec) - 1]);
  278. if ((! $ret) || (! $ret->item(0))) {
  279. return;
  280. }
  281. return $ret->item(0);
  282. }
  283. }
  284. sub get_object_value_by_spec
  285. {
  286. my $res = get_object_by_spec(@_);
  287. return get_object_value($res);
  288. }
  289. sub get_iface_name
  290. {
  291. my $iface = shift;
  292. return get_obj_element($iface, 'name');
  293. }
  294. sub get_iface_description
  295. {
  296. my $iface = shift;
  297. return get_obj_element($iface, 'description');
  298. }
  299. sub get_iface_status
  300. {
  301. my $iface = shift;
  302. return get_obj_element($iface, 'oper-status');
  303. }
  304. sub get_iface_admin_status
  305. {
  306. my $iface = shift;
  307. return get_obj_element($iface, 'admin-status');
  308. }
  309. sub get_iface_traffic
  310. {
  311. my $iface = shift;
  312. my $type = shift;
  313. my $stats = get_obj_element($iface, 'traffic-statistics');
  314. return get_obj_element($iface, "$type-bytes");
  315. }
  316. sub get_iface_first_logical
  317. {
  318. my $iface = shift;
  319. return $iface->getElementsByTagName('logical-interface')->item(0);
  320. }
  321. sub get_liface_marker
  322. {
  323. my $liface = shift;
  324. my $lag_stats = $liface->getElementsByTagName('lag-traffic-statistics')->item(0);
  325. if (! $lag_stats) {
  326. print STDERR "Cannot get marker for non-LACP interfaces yet!\n";
  327. return;
  328. }
  329. my @markers = $lag_stats->getElementsByTagName('lag-marker');
  330. return @markers;
  331. }
  332. sub check_interfaces
  333. {
  334. my @targets = @_;
  335. my $opts = {
  336. with_description => 0,
  337. };
  338. if (grep { m/^\@with_description$/; } @targets) {
  339. $opts->{'with_description'} = 1;
  340. @targets = grep { ! m/^\@with_description$/; } @targets;
  341. }
  342. my @interfaces = get_interfaces($junos, $opts, @targets);;
  343. my $down_count = 0;
  344. my @down_ifaces = ();
  345. my $phys_down_count = 0;
  346. my @phys_down_ifaces = ();
  347. my $have_lag_ifaces = 0;
  348. foreach my $iface (@interfaces) {
  349. my $name = get_iface_name($iface);
  350. my $status = check_interface($iface, $opts, @targets);
  351. if ($status == 0) {
  352. ++$down_count;
  353. push @down_ifaces, $name;
  354. }
  355. if ($status <= 0) {
  356. # disabled or down
  357. next;
  358. }
  359. if ($name !~ m/^ae/) {
  360. next;
  361. }
  362. $have_lag_ifaces = 1;
  363. my @markers = get_liface_marker(get_iface_first_logical($iface));
  364. if (! @markers) {
  365. next;
  366. }
  367. foreach my $marker (@markers) {
  368. my $phy_name = get_iface_name($marker);
  369. $phy_name =~ s/\.\d+$//;
  370. $plugin->verbose(3, "Quering physical interface '$phy_name' "
  371. . "for $name.");
  372. my @phy_interfaces = get_interfaces($junos, {}, $phy_name);
  373. foreach my $phy_iface (@phy_interfaces) {
  374. if (check_interface($phy_iface, {}, $phy_name) == 0) {
  375. ++$phys_down_count;
  376. push @phys_down_ifaces, "$name -> $phy_name";
  377. }
  378. }
  379. }
  380. }
  381. if ($down_count > 0) {
  382. $plugin->add_message(CRITICAL, $down_count
  383. . " interfaces down (" . join(", ", @down_ifaces) . ")");
  384. }
  385. if ($phys_down_count > 0) {
  386. $plugin->add_message(WARNING, $phys_down_count
  387. . " LAG member interfaces down ("
  388. . join(", ", @phys_down_ifaces) . ")");
  389. }
  390. if ((! $down_count) && (! $phys_down_count)) {
  391. if (! scalar(@targets)) {
  392. $plugin->add_message(OK, "all interfaces up"
  393. . ($have_lag_ifaces
  394. ? " (including all LAG member interfaces)" : ""));
  395. }
  396. else {
  397. $plugin->add_message(OK, "interface"
  398. . (scalar(@targets) == 1 ? " " : "s ")
  399. . join(", ", @targets) . " up"
  400. . ($have_lag_ifaces
  401. ? " (including all LAG member interfaces)" : ""));
  402. }
  403. }
  404. }
  405. sub check_chassis_environment
  406. {
  407. my @targets = @_;
  408. my $res = send_query($junos, 'get_environment_information');
  409. my %status_map = (
  410. OK => OK,
  411. Testing => UNKNOWN,
  412. Check => UNKNOWN,
  413. Failed => CRITICAL,
  414. Absent => CRITICAL,
  415. );
  416. my $items_count = 0;
  417. my $items_ok = 0;
  418. my $class = "";
  419. foreach my $item (get_object_by_spec($res, 'environment-item')) {
  420. my $name = get_object_value_by_spec($item, 'name');
  421. if (scalar(@targets) && (! grep { m/^$name$/ } @targets)) {
  422. next;
  423. }
  424. if (get_object_value_by_spec($item, 'class')) {
  425. $class = get_object_value_by_spec($item, 'class');
  426. }
  427. my $status = get_object_value_by_spec($item, 'status');
  428. if ($status eq "Absent") {
  429. if (! scalar(@targets)) {
  430. next;
  431. }
  432. # else: check this component
  433. }
  434. my $state = UNKNOWN;
  435. if (defined $status_map{$status}) {
  436. $state = $status_map{$status};
  437. }
  438. ++$items_count;
  439. if ($state == OK) {
  440. ++$items_ok;
  441. }
  442. else {
  443. $plugin->add_message($state, $class . " $name: status " .
  444. $status);
  445. }
  446. my $temp = get_object_value_by_spec($item, 'temperature');
  447. if (! $temp) {
  448. next;
  449. }
  450. ($temp) = $temp =~ m/(\d+) degrees C/;
  451. if (! defined($temp)) {
  452. next;
  453. }
  454. $state = $plugin->check_threshold($temp);
  455. if ($state != OK) {
  456. $plugin->add_message($state, $class
  457. . " $name: ${temp} degrees C");
  458. }
  459. my $label = "$name-temp";
  460. $label =~ s/ /_/g;
  461. $plugin->add_perfdata(
  462. label => "'$label'",
  463. value => $temp,
  464. min => undef,
  465. max => undef,
  466. uom => '',
  467. threshold => $plugin->threshold(),
  468. );
  469. }
  470. if (! $items_count) {
  471. $plugin->add_message(UNKNOWN, "no components found");
  472. }
  473. elsif ($items_count == $items_ok) {
  474. $plugin->add_message(OK, "$items_ok components OK");
  475. }
  476. else {
  477. $plugin->add_message(WARNING,
  478. "$items_ok / $items_count components OK");
  479. }
  480. }
  481. sub check_system_storage
  482. {
  483. my @targets = @_;
  484. my $res = send_query($junos, 'get_system_storage');
  485. foreach my $re (get_object_by_spec($res,
  486. 'multi-routing-engine-item')) {
  487. my $re_name = get_object_value_by_spec($re, 're-name');
  488. foreach my $fs (get_object_by_spec($re,
  489. ['system-storage-information', 'filesystem'])) {
  490. my $name = get_object_value_by_spec($fs, 'filesystem-name');
  491. my $mnt_pt = get_object_value_by_spec($fs, 'mounted-on');
  492. if (scalar(@targets) && (! grep { m/^$name$/ } @targets)
  493. && (! grep { m/^$mnt_pt$/ } @targets)) {
  494. next;
  495. }
  496. my $used = get_object_value_by_spec($fs, 'used-percent') + 0;
  497. my $state = $plugin->check_threshold($used);
  498. if ($state != OK) {
  499. $plugin->add_message($state, "$re_name $mnt_pt: "
  500. . "$used\% used");
  501. }
  502. $plugin->add_perfdata(
  503. label => "'$re_name-$mnt_pt'",
  504. value => $used,
  505. min => 0,
  506. max => 100,
  507. uom => '%',
  508. threshold => $plugin->threshold(),
  509. );
  510. }
  511. }
  512. }