1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package ch.syabru.nagios;
17
18 import java.io.IOException;
19 import java.io.InputStreamReader;
20 import java.io.PrintStream;
21 import java.io.Reader;
22 import java.math.BigDecimal;
23 import java.math.BigInteger;
24 import java.net.MalformedURLException;
25 import java.net.URL;
26 import java.rmi.ConnectException;
27 import java.util.HashMap;
28 import java.util.Set;
29 import java.util.Properties;
30 import java.util.regex.Matcher;
31 import java.util.regex.Pattern;
32 import java.util.regex.PatternSyntaxException;
33
34 import javax.management.AttributeNotFoundException;
35 import javax.management.InstanceNotFoundException;
36 import javax.management.IntrospectionException;
37 import javax.management.MBeanException;
38 import javax.management.MBeanServerConnection;
39 import javax.management.ObjectInstance;
40 import javax.management.MalformedObjectNameException;
41 import javax.management.ObjectName;
42 import javax.management.ReflectionException;
43 import javax.management.openmbean.CompositeDataSupport;
44 import javax.management.openmbean.InvalidKeyException;
45 import javax.management.remote.JMXConnector;
46 import javax.management.remote.JMXConnectorFactory;
47 import javax.management.remote.JMXServiceURL;
48
49
50
51
52
53
54
55 public class NagiosJmxPlugin {
56
57
58
59
60 enum Status {
61
62
63
64 OK (0, "JMX OK - "),
65
66
67
68 WARNING (1, "JMX WARNING - "),
69
70
71
72 CRITICAL (2, "JMX CRITICAL - "),
73
74
75
76 UNKNOWN (3, "JMX UNKNOWN - ");
77
78 private int exitCode;
79 private String messagePrefix;
80
81
82
83
84
85
86 private Status(int exitCode, String messagePrefix) {
87 this.exitCode = exitCode;
88 this.messagePrefix = messagePrefix;
89 }
90
91 public int getExitCode() {
92 return exitCode;
93 }
94
95 public String getMessagePrefix() {
96 return messagePrefix;
97 }
98 }
99
100
101
102
103 enum Unit {
104
105
106
107 BYTES ("B"),
108
109
110
111 KILOBYTES ("KB"),
112
113
114
115 MEGABYTES ("MB"),
116
117
118
119 TERABYTES ("TB"),
120
121
122
123 SECONDS ("s"),
124
125
126
127 MICROSECONDS ("us"),
128
129
130
131 MILLISECONDS ("ms"),
132
133
134
135 COUNTER ("c");
136
137 private String abbreviation;
138
139
140
141
142
143 private Unit(String abbreviation) {
144 this.abbreviation = abbreviation;
145 }
146
147 public String getAbbreviation() {
148 return abbreviation;
149 }
150
151
152
153
154
155
156 public static Unit parse(String abbr) {
157 for (Unit unit : Unit.values()) {
158 if (unit.getAbbreviation().equals(abbr))
159 return unit;
160 }
161 return null;
162 }
163 }
164
165
166
167
168 public static final String PROP_USERNAME = "username";
169
170
171
172 public static final String PROP_PASSWORD = "password";
173
174
175
176 public static final String PROP_OBJECT_NAME = "objectName";
177
178
179
180 public static final String PROP_ATTRIBUTE_NAME = "attributeName";
181
182
183
184 public static final String PROP_ATTRIBUTE_KEY = "attributeKey";
185
186
187
188 public static final String PROP_SERVICE_URL = "serviceUrl";
189
190
191
192
193
194 public static final String PROP_THRESHOLD_WARNING = "thresholdWarning";
195
196
197
198
199
200 public static final String PROP_THRESHOLD_CRITICAL = "thresholdCritical";
201
202
203
204 public static final String PROP_UNITS = "units";
205
206
207
208 public static final String PROP_OPERATION = "operation";
209
210
211
212 public static final String PROP_VERBOSE = "verbose";
213
214
215
216 public static final String PROP_HELP = "help";
217
218 private HashMap<MBeanServerConnection, JMXConnector> connections =
219 new HashMap<MBeanServerConnection, JMXConnector>();
220
221
222
223
224
225
226
227
228
229
230 public MBeanServerConnection openConnection(
231 JMXServiceURL serviceUrl, String username, String password)
232 throws IOException
233 {
234 JMXConnector connector;
235 HashMap<String, Object> environment = new HashMap<String, Object>();
236
237 environment.put("jmx.remote.x.client.connection.check.period", 5000);
238 if (username != null && password != null) {
239 environment = new HashMap<String, Object>();
240 environment.put(JMXConnector.CREDENTIALS,
241 new String[] {username, password});
242 connector = JMXConnectorFactory.connect(serviceUrl, environment);
243 } else {
244 connector = JMXConnectorFactory.connect(serviceUrl, environment);
245 }
246 MBeanServerConnection connection = connector.getMBeanServerConnection();
247 connections.put(connection, connector);
248 return connection;
249 }
250
251
252
253
254
255
256 public void closeConnection(MBeanServerConnection connection)
257 throws IOException
258 {
259 JMXConnector connector = connections.remove(connection);
260 if (connector != null)
261 connector.close();
262 }
263
264
265
266
267
268
269
270
271
272
273
274 public ObjectName getObjectName(MBeanServerConnection connection,
275 String objectName)
276 throws InstanceNotFoundException, MalformedObjectNameException,
277 NagiosJmxPluginException, IOException
278 {
279 ObjectName objName = new ObjectName(objectName);
280 if (objName.isPropertyPattern() || objName.isDomainPattern()) {
281 Set<ObjectInstance> mBeans = connection.queryMBeans(objName, null);
282
283 if (mBeans.size() == 0) {
284 throw new InstanceNotFoundException();
285 } else if (mBeans.size() > 1) {
286 throw new NagiosJmxPluginException(
287 "Object name not unique: objectName pattern matches " +
288 mBeans.size() + " MBeans.");
289 } else {
290 objName = mBeans.iterator().next().getObjectName();
291 }
292 }
293 return objName;
294 }
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312 public Object query(MBeanServerConnection connection, String objectName,
313 String attributeName, String attributeKey)
314 throws InstanceNotFoundException, IntrospectionException,
315 ReflectionException, IOException, AttributeNotFoundException,
316 MBeanException, MalformedObjectNameException,
317 NagiosJmxPluginException
318 {
319 Object value = null;
320 ObjectName objName = getObjectName(connection, objectName);
321
322 Object attribute = connection.getAttribute(objName, attributeName);
323 if (attribute instanceof CompositeDataSupport) {
324 CompositeDataSupport compositeAttr =
325 (CompositeDataSupport) attribute;
326 value = compositeAttr.get(attributeKey);
327 } else {
328 value = attribute;
329 }
330 return value;
331 }
332
333
334
335
336
337
338
339
340
341
342
343
344
345 public void invoke(MBeanServerConnection connection, String objectName,
346 String operationName)
347 throws InstanceNotFoundException, IOException,
348 MalformedObjectNameException, MBeanException, ReflectionException,
349 NagiosJmxPluginException
350 {
351 ObjectName objName = getObjectName(connection, objectName);
352 connection.invoke(objName, operationName, null, null);
353 }
354
355
356
357
358
359
360
361 public int execute(Properties args) throws NagiosJmxPluginException {
362 String username = args.getProperty(PROP_USERNAME);
363 String password = args.getProperty(PROP_PASSWORD);
364 String objectName = args.getProperty(PROP_OBJECT_NAME);
365 String attributeName = args.getProperty(PROP_ATTRIBUTE_NAME);
366 String attributeKey = args.getProperty(PROP_ATTRIBUTE_KEY);
367 String serviceUrl = args.getProperty(PROP_SERVICE_URL);
368 String thresholdWarning = args.getProperty(PROP_THRESHOLD_WARNING);
369 String thresholdCritical = args.getProperty(PROP_THRESHOLD_CRITICAL);
370 String operation = args.getProperty(PROP_OPERATION);
371 String units = args.getProperty(PROP_UNITS);
372 String help = args.getProperty(PROP_HELP);
373
374 PrintStream out = System.out;
375
376 if (help != null) {
377 showHelp();
378 return Status.OK.getExitCode();
379 }
380
381 if (objectName == null || attributeName == null || serviceUrl == null)
382 {
383 showUsage();
384 return Status.CRITICAL.getExitCode();
385 }
386
387 Unit unit = null;
388 if (units != null && Unit.parse(units) == null) {
389 throw new NagiosJmxPluginException("Unknown unit [" + units + "]");
390 } else {
391 unit = Unit.parse(units);
392 }
393
394 JMXServiceURL url = null;
395 try {
396 url = new JMXServiceURL(serviceUrl);
397 } catch (MalformedURLException e) {
398 throw new NagiosJmxPluginException("Malformed service URL [" +
399 serviceUrl + "]", e);
400 }
401
402 MBeanServerConnection connection = null;
403 Object value = null;
404 try {
405 try {
406 connection = openConnection(url, username, password);
407 } catch (ConnectException ce) {
408 throw new NagiosJmxPluginException(
409 "Error opening RMI connection: " + ce.getMessage(), ce);
410 } catch (Exception e) {
411 throw new NagiosJmxPluginException(
412 "Error opening connection: " + e.getMessage(), e);
413 }
414
415 try {
416 value = query(connection, objectName, attributeName,
417 attributeKey);
418 } catch (MalformedObjectNameException e) {
419 throw new NagiosJmxPluginException(
420 "Malformed objectName [" + objectName + "]", e);
421 } catch (InstanceNotFoundException e) {
422 throw new NagiosJmxPluginException(
423 "objectName not found [" + objectName + "]", e);
424 } catch (AttributeNotFoundException e) {
425 throw new NagiosJmxPluginException(
426 "attributeName not found [" + attributeName + "]", e);
427 } catch (InvalidKeyException e) {
428 throw new NagiosJmxPluginException(
429 "attributeKey not found [" + attributeKey + "]", e);
430 } catch (Exception e) {
431 throw new NagiosJmxPluginException(
432 "Error querying server: " + e.getMessage(), e);
433 }
434
435 if (operation != null) {
436 try {
437 invoke(connection, objectName, operation);
438 } catch (Exception e) {
439 throw new NagiosJmxPluginException(
440 "Error invoking operation [" + operation + "]: " +
441 e.getMessage(), e);
442 }
443 }
444 } finally {
445 if (connection != null) {
446 try {
447 closeConnection(connection);
448 } catch (Exception e) {
449 throw new NagiosJmxPluginException(
450 "Error closing JMX connection", e);
451 }
452 }
453 }
454 int exitCode;
455 if (value != null) {
456 Status status;
457 if (value instanceof Number) {
458 Number numValue = (Number) value;
459 if (isOutsideThreshold(numValue, thresholdCritical)) {
460 status = Status.CRITICAL;
461 } else if (isOutsideThreshold(numValue, thresholdWarning)) {
462 status = Status.WARNING;
463 } else {
464 status = Status.OK;
465 }
466 } else if (value instanceof String) {
467 String strValue = (String) value;
468 if (matchesThreshold(strValue, thresholdCritical)) {
469 status = Status.CRITICAL;
470 } else if (matchesThreshold(strValue, thresholdWarning)) {
471 status = Status.WARNING;
472 } else {
473 status = Status.OK;
474 }
475 } else {
476 throw new NagiosJmxPluginException(
477 "Type of return value not supported [" +
478 value.getClass().getName() + "]. Must be either a " +
479 "Number or String object.");
480 }
481 outputStatus(out, status, objectName, attributeName, attributeKey,
482 value, unit);
483 if (value instanceof Number)
484 outputPerformanceData(out, objectName, attributeName,
485 attributeKey, (Number) value, thresholdWarning,
486 thresholdCritical, unit);
487 out.println();
488 exitCode = status.getExitCode();
489 } else {
490 out.print(Status.WARNING.getMessagePrefix());
491 out.println("Value not set. JMX query returned null value.");
492 exitCode = Status.WARNING.getExitCode();
493 }
494 return exitCode;
495 }
496
497
498
499
500
501
502
503
504
505
506
507
508 Number[] getThresholdLimits(
509 Class<? extends Number> clazz, String threshold)
510 throws NagiosJmxPluginException
511 {
512
513 Matcher matcher1 = Pattern.compile(
514 "^(\\d+\\.?\\d*)$").matcher(threshold);
515
516 Matcher matcher2 = Pattern.compile(
517 "^(\\d+\\.?\\d*):$").matcher(threshold);
518
519 Matcher matcher3 = Pattern.compile(
520 "^~:(\\d+\\.?\\d*)$").matcher(threshold);
521
522 Matcher matcher4 = Pattern.compile(
523 "^(\\d+\\.?\\d*):(\\d+\\.?\\d*)$").matcher(threshold);
524
525 Matcher matcher5 = Pattern.compile(
526 "^@(\\d+\\.?\\d*):(\\d+\\.?\\d*)$").matcher(threshold);
527
528 Number[] limits = new Number[2];
529 if (matcher1.matches()) {
530 limits[0] = parseAsNumber(clazz, "0");
531 limits[1] = parseAsNumber(clazz, matcher1.group(1));
532 } else if (matcher2.matches()) {
533 limits[0] = parseAsNumber(clazz, matcher2.group(1));
534 limits[1] = null;
535 } else if (matcher3.matches()) {
536 limits[0] = null;
537 limits[1] = parseAsNumber(clazz, matcher3.group(1));
538 } else if (matcher4.matches()) {
539 limits[0] = parseAsNumber(clazz, matcher4.group(1));
540 limits[1] = parseAsNumber(clazz, matcher4.group(2));
541 } else if (matcher5.matches()) {
542 limits[0] = parseAsNumber(clazz, matcher5.group(2));
543 limits[1] = parseAsNumber(clazz, matcher5.group(1));
544 } else {
545 throw new NagiosJmxPluginException("Error parsing threshold. " +
546 "Unknown threshold range format [" + threshold + "]");
547 }
548 return limits;
549 }
550
551
552
553
554
555
556
557
558
559 Number parseAsNumber(Class<? extends Number> clazz, String value)
560 throws NagiosJmxPluginException
561 {
562 Number result;
563 try {
564 if (Double.class.equals(clazz)) {
565 result = Double.valueOf(value);
566 } else if (Integer.class.equals(clazz)) {
567 result = Integer.valueOf(value);
568 } else if (Long.class.equals(clazz)) {
569 result = Long.valueOf(value);
570 } else if (Short.class.equals(clazz)) {
571 result = Short.valueOf(value);
572 } else if (Byte.class.equals(clazz)) {
573 result = Byte.valueOf(value);
574 } else if (Float.class.equals(clazz)) {
575 result = Float.valueOf(value);
576 } else if (BigInteger.class.equals(clazz)) {
577 result = new BigInteger(value);
578 } else if (BigDecimal.class.equals(clazz)) {
579 result = new BigDecimal(value);
580 } else {
581 throw new NumberFormatException("Can't handle object type [" +
582 value.getClass().getName() + "]");
583 }
584 } catch (NumberFormatException e) {
585 throw new NagiosJmxPluginException("Error parsing threshold " +
586 "value [" + value + "]. Expected [" + clazz.getName() +
587 "]", e);
588 }
589 return result;
590 }
591
592
593
594
595
596
597
598
599
600
601
602 private void outputStatus(PrintStream out, Status status, String objectName,
603 String attributeName, String attributeKey, Object value,
604 Unit unit)
605 {
606 StringBuilder output = new StringBuilder(status.getMessagePrefix());
607 output.append(attributeName);
608 if (attributeKey != null) {
609 output.append(".").append(attributeKey);
610 }
611 output.append(" = ").append(value);
612 if (unit != null)
613 output.append(unit.getAbbreviation());
614 out.print(output.toString());
615 }
616
617
618
619
620
621
622
623
624
625
626
627
628 private void outputPerformanceData(PrintStream out, String objectName,
629 String attributeName, String attributeKey, Number value,
630 String thresholdWarning, String thresholdCritical,
631 Unit unit)
632 {
633 StringBuilder output = new StringBuilder();
634 output.append(" | '");
635 output.append(attributeName);
636 if (attributeKey != null) {
637 output.append(" ").append(attributeKey);
638 }
639 output.append("'=").append(value);
640 if (unit != null)
641 output.append(unit.getAbbreviation());
642 output.append(";");
643 if (thresholdWarning != null)
644 output.append(thresholdWarning);
645 output.append(";");
646 if (thresholdCritical != null)
647 output.append(thresholdCritical);
648 output.append(";;");
649 out.print(output.toString());
650 }
651
652
653
654
655
656
657
658
659
660
661 @SuppressWarnings({ "rawtypes", "unchecked" })
662 private boolean isOutsideThreshold(Number value, String threshold)
663 throws NagiosJmxPluginException
664 {
665 boolean outsideThreshold = false;
666 if (threshold != null) {
667 Number[] limits = getThresholdLimits(value.getClass(), threshold);
668 Number min = limits[0];
669 Number max = limits[1];
670 if (value instanceof Double || value instanceof Float) {
671 outsideThreshold =
672 (min != null &&
673 value.doubleValue() < min.doubleValue()) ||
674 (max != null &&
675 value.doubleValue() > max.doubleValue());
676 } else if (value instanceof Long || value instanceof Integer ||
677 value instanceof Short || value instanceof Byte)
678 {
679 outsideThreshold =
680 (min != null &&
681 value.longValue() < min.longValue()) ||
682 (max != null &&
683 value.longValue() > max.longValue());
684 } else if (value instanceof BigInteger ||
685 value instanceof BigDecimal)
686 {
687 Comparable compVal = (Comparable) value;
688 outsideThreshold =
689 (min != null &&
690 compVal.compareTo(min) < 0) ||
691 (max != null &&
692 compVal.compareTo(max) > 0);
693 } else {
694 throw new NumberFormatException("Can't handle object type [" +
695 value.getClass().getName() + "]");
696 }
697 } else {
698 outsideThreshold = false;
699 }
700 return outsideThreshold;
701 }
702
703
704
705
706
707
708
709
710
711
712 private boolean matchesThreshold(String value, String threshold)
713 throws NagiosJmxPluginException
714 {
715 boolean matchesThreshold = false;
716 try {
717 if (threshold == null) {
718 matchesThreshold = false;
719 } else {
720 if (threshold.startsWith("@")) {
721 matchesThreshold = Pattern.matches(threshold.substring(1),
722 value);
723 } else {
724 matchesThreshold = !Pattern.matches(threshold, value);
725 }
726 }
727 return matchesThreshold;
728 } catch (PatternSyntaxException e) {
729 throw new NagiosJmxPluginException("Error parsing threshold " +
730 "regex [" + threshold + "]", e);
731 }
732 }
733
734
735
736
737
738 public static void main(String[] args) {
739 PrintStream out = System.out;
740 NagiosJmxPlugin plugin = new NagiosJmxPlugin();
741 int exitCode;
742 Properties props = parseArguments(args);
743 String verbose = props.getProperty(PROP_VERBOSE);
744 try {
745 exitCode = plugin.execute(props);
746 } catch (NagiosJmxPluginException e) {
747 out.println(Status.CRITICAL.getMessagePrefix() + e.getMessage());
748 if (verbose != null)
749 e.printStackTrace(System.out);
750 exitCode = Status.CRITICAL.getExitCode();
751 } catch (Exception e) {
752 out.println(Status.UNKNOWN.getMessagePrefix() + e.getMessage());
753 if (verbose != null)
754 e.printStackTrace(System.out);
755 exitCode = Status.UNKNOWN.getExitCode();
756 }
757 System.exit(exitCode);
758 }
759
760
761
762
763
764 private void showUsage() throws NagiosJmxPluginException {
765 outputResource(getClass().getResource("usage.txt"));
766 }
767
768
769
770
771
772 private void showHelp() throws NagiosJmxPluginException {
773 outputResource(getClass().getResource("help.txt"));
774 }
775
776
777
778
779
780
781 private void outputResource(URL url) throws NagiosJmxPluginException {
782 PrintStream out = System.out;
783 try {
784 Reader r = new InputStreamReader(url.openStream());
785 StringBuilder sbHelp = new StringBuilder();
786 char[] buffer = new char[1024];
787 for (int len = r.read(buffer); len != -1; len = r.read(buffer)) {
788 sbHelp.append(buffer, 0, len);
789 }
790 out.println(sbHelp.toString());
791 } catch (IOException e) {
792 throw new NagiosJmxPluginException(e);
793 }
794 }
795
796
797
798
799
800
801 private static Properties parseArguments(String[] args) {
802 Properties props = new Properties();
803 int i = 0;
804 while (i < args.length) {
805 if ("-h".equals(args[i]))
806 props.put(PROP_HELP, "");
807 else if ("-U".equals(args[i]))
808 props.put(PROP_SERVICE_URL, args[++i]);
809 else if ("-O".equals(args[i]))
810 props.put(PROP_OBJECT_NAME, args[++i]);
811 else if ("-A".equals(args[i]))
812 props.put(PROP_ATTRIBUTE_NAME, args[++i]);
813 else if ("-K".equals(args[i]))
814 props.put(PROP_ATTRIBUTE_KEY, args[++i]);
815 else if ("-v".equals(args[i]))
816 props.put(PROP_VERBOSE, "true");
817 else if ("-w".equals(args[i]))
818 props.put(PROP_THRESHOLD_WARNING, args[++i]);
819 else if ("-c".equals(args[i]))
820 props.put(PROP_THRESHOLD_CRITICAL, args[++i]);
821 else if ("--username".equals(args[i]))
822 props.put(PROP_USERNAME, args[++i]);
823 else if ("--password".equals(args[i]))
824 props.put(PROP_PASSWORD, args[++i]);
825 else if ("-u".equals(args[i]))
826 props.put(PROP_UNITS, args[++i]);
827 else if ("-o".equals(args[i]))
828 props.put(PROP_OPERATION, args[++i]);
829 i++;
830 }
831 return props;
832 }
833 }