From e70769632066e81d6e3045e800a177d1b2ba42a4 Mon Sep 17 00:00:00 2001 From: Vojtech Horky Date: Wed, 26 Jun 2019 14:36:47 +0200 Subject: [PATCH] Initial import --- .gitignore | 5 + LICENSE | 177 ++++++++++++++++++++ README.md | 29 ++++ d3s-nagios-plugins.rpkg.spec | 38 +++++ d3s/__init__.py | 0 d3s/nagios.py | 121 +++++++++++++ d3s/nagios_plugins/__init__.py | 0 d3s/nagios_plugins/check_health.py | 95 +++++++++++ d3s/nagios_plugins/check_systemd_service.py | 93 ++++++++++ setup.py | 48 ++++++ 10 files changed, 606 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 d3s-nagios-plugins.rpkg.spec create mode 100644 d3s/__init__.py create mode 100644 d3s/nagios.py create mode 100644 d3s/nagios_plugins/__init__.py create mode 100755 d3s/nagios_plugins/check_health.py create mode 100755 d3s/nagios_plugins/check_systemd_service.py create mode 100755 setup.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5f1a01a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/build/ +/dist/ +/env/ +__pycache__ +*.egg-info diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md new file mode 100644 index 0000000..07cae71 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# D3S Nagios Plugins + +A collection of various mini-plugins for Nagios. + + + +## Installation (inside virtualenv) + +```shell +virtualenv env +. ./env/bin/activate +./setup.py install +# Now it is possible to run any of the ./env/bin/nagios_d3s_check_* scripts +``` + + + +## Available plugins + + +### `check_health` + +Only collects basic system status (uptime, free memory, ...), useful only +for performance data analysis. + + +### `check_systemd_service` + +Checks that systemd unit is enabled and running. diff --git a/d3s-nagios-plugins.rpkg.spec b/d3s-nagios-plugins.rpkg.spec new file mode 100644 index 0000000..a01d050 --- /dev/null +++ b/d3s-nagios-plugins.rpkg.spec @@ -0,0 +1,38 @@ +Name: {{{ git_name name="d3s-nagios-plugins" }}} +Version: {{{ git_version }}} +Release: 1%{?dist} +Summary: Custom Nagios plugins + +License: ASL 2.0 +URL: https://lab.d3s.mff.cuni.cz/nagios-plugins/ +VCS: {{{ git_vcs }}} +Source: {{{ git_pack }}} + +BuildRequires: python3 +BuildRequires: python3-devel +BuildRequires: python3-setuptools +Requires: python3 + +%description +Blab blah + + +%global debug_package %{nil} + +%prep +%setup + +%build +%{__python3} setup.py build + +%install +rm -rf $RPM_BUILD_ROOT +%{__python3} setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES + +%clean +rm -rf $RPM_BUILD_ROOT + +%files -f INSTALLED_FILES +%defattr(-,root,root) + +%changelog diff --git a/d3s/__init__.py b/d3s/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/d3s/nagios.py b/d3s/nagios.py new file mode 100644 index 0000000..263754a --- /dev/null +++ b/d3s/nagios.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 + +# +# Copyright 2019 Vojtech Horky +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +Nagios-releated utilities. +""" + +import subprocess + + +class NagiosPluginBase: + """ + Base class for Nagios plugins. + + Note that this class offers a lot of functionality that could be + better implemented in standalone modules. But packing it all into + one class make the implementation of individual plugins much faster. + """ + + STATUS_NAMES = ['OK', 'WARNING', 'CRITICAL', 'UNKNOWN'] + + def __init__(self, name): + self.name_ = name + self.status_ = 0 + self.message_ = "empty message" + self.perf_data_ = {} + + def collect(self): + """ Called from run() to actually ollect monitored information. """ + raise NotImplementedError("Re-implement in subclass!") + + def add_perf_data(self, name, value): + """ Add performance data. """ + self.perf_data_[name] = value + + def get_perf_data(self, name): + """ Get performance data. """ + return self.perf_data_[name] + + def set_message(self, msg): + """ Set the main plugin message. """ + self.message_ = msg + + def set_message_from_perf(self, msg): + """ Set the main plugin message with {placeholders} from perf data. """ + self.set_message(msg.format(**self.perf_data_)) + + def worsen_to_warning(self): + """ Final plugin status would be warning or worse. """ + if self.status_ == 0: + self.status_ = 1 + + def worsen_to_critical(self): + """ Final plugin status would be critical. """ + if (self.status_ == 0) or (self.status_ == 1): + self.status_ = 2 + + # pylint: disable=no-self-use + def read_file(self, filename): + """ Read given file and return list of its lines (rstripped). """ + with open(filename, "r") as inp: + for line in inp: + yield line.rstrip() + + # pylint: disable=no-self-use + def read_command_output(self, cmd): + """ Run given command and return list of lines on stdout (rstripped). """ + with subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) as proc: + for line in proc.stdout: + yield line.decode('utf-8').rstrip() + proc.wait() + + # pylint: disable=no-self-use + def grep_lines(self, regexp, lines): + """ Return lines matching regular expression. """ + for line in lines: + res = regexp.search(line) + if res is not None: + yield res + + def contains_line(self, regexp, lines): + """ Returns whether some line matches regular expression. """ + try: + next(self.grep_lines(regexp, lines)) + return True + except StopIteration: + return False + + def run(self): + """ Main of the plugin that does the work. """ + self.collect() + status_name = NagiosPluginBase.STATUS_NAMES[self.status_] + print("{name} {status} - {message}{perf}".format( + name=self.name_, + status=status_name, + message=self.message_, + perf=self.format_perf_data_())) + + def format_perf_data_(self): + """ Formats performance data as CSV. """ + if not self.perf_data_: + return "" + res = [] + for key, value in self.perf_data_.items(): + res.append("{}={}".format(key, value)) + return "|{}".format(",".join(res)) diff --git a/d3s/nagios_plugins/__init__.py b/d3s/nagios_plugins/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/d3s/nagios_plugins/check_health.py b/d3s/nagios_plugins/check_health.py new file mode 100755 index 0000000..504b784 --- /dev/null +++ b/d3s/nagios_plugins/check_health.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 + +# +# Copyright 2019 Vojtech Horky +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +Checks basic system health, useful for collecting basic stats. + +Usage: + + +Example output: + + HEALTH OK - up 1 day, 0.79 load, 1 ready tasks|\ + mem_total_kb=16300840,mem_avail_kb=9947436,\ + load_1min=0.39,load_5min=0.79,load_15min=0.86,\ + tasks_runnable=1,tasks_total=952,\ + uptime=1 day + +""" + +import re +from d3s.nagios import NagiosPluginBase + + +class CheckHealth(NagiosPluginBase): + """ + Collects basic information about running Linux system. + + There are not critical/warning limits as its main purpose is to collect + basic system information for later processing via perf data section. + """ + + MEM_INFO_RE = re.compile('^([^:]*):[ \t]*([0-9]*) kB$') + LOAD_AVG_RE = re.compile(''' + ^([0-9.]+) # 1 min + \\s+ + ([0-9.]+) # 5 min + \\s+ + ([0-9.]+) # 15 min + \\s+ + ([0-9]+)/([0-9]+) # runnable/total tasks in the system + \\s+.*$ # ignore rest of line + ''', re.VERBOSE) + UPTIME_RE = re.compile('^.*up[ \t]+([^,]*),.*$') + + def __init__(self): + NagiosPluginBase.__init__(self, 'HEALTH') + + def collect(self): + # Read memory information + for entry in self.grep_lines(CheckHealth.MEM_INFO_RE, self.read_file('/proc/meminfo')): + key = entry.group(1) + value = entry.group(2) + if key == 'MemTotal': + self.add_perf_data('mem_total_kb', value) + elif key == 'MemAvailable': + self.add_perf_data('mem_avail_kb', value) + + # Get load average + entry = next(self.grep_lines(CheckHealth.LOAD_AVG_RE, self.read_file('/proc/loadavg'))) + self.add_perf_data('load_1min', entry.group(1)) + self.add_perf_data('load_5min', entry.group(2)) + self.add_perf_data('load_15min', entry.group(3)) + self.add_perf_data('tasks_runnable', entry.group(4)) + self.add_perf_data('tasks_total', entry.group(5)) + + # Read uptime information + entry = next(self.grep_lines(CheckHealth.UPTIME_RE, self.read_command_output(['uptime']))) + self.add_perf_data('uptime', entry.group(1)) + + # Format final message + self.set_message_from_perf("up {uptime}, {load_5min} load, {tasks_runnable} ready tasks") + +def main(): + """ + Module main for execution from shell script. + """ + CheckHealth().run() + +if __name__ == '__main__': + main() diff --git a/d3s/nagios_plugins/check_systemd_service.py b/d3s/nagios_plugins/check_systemd_service.py new file mode 100755 index 0000000..5e68864 --- /dev/null +++ b/d3s/nagios_plugins/check_systemd_service.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 + +# +# Copyright 2019 Vojtech Horky +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +""" +Checks that systemd unit is enabled and running. + +Usage: + --service UNIT + +Example output: + + SRV OK - httpd running, 12.6M, 6 tasks|\ + service_name=httpd,service_tasks=6,service_memory=12.6M + +""" + +import re +import argparse +from d3s.nagios import NagiosPluginBase + + +class CheckSystemdService(NagiosPluginBase): + """ + Checks that given service is running and enabled. + """ + + DETAILS_BASE_RE = re.compile(''' + ^\\s+(Tasks|Memory): + \\s+ + ([^ \t]+) + .*$ + ''', re.VERBOSE) + + def __init__(self): + NagiosPluginBase.__init__(self, 'SRV') + parser = argparse.ArgumentParser() + parser.add_argument('--service', dest='service', metavar='UNIT', required=True) + args = parser.parse_args() + self.service = args.service + + def collect(self): + self.add_perf_data('service_name', self.service) + + is_enabled_output = self.read_command_output(['systemctl', 'is-enabled', self.service]) + is_enabled = self.contains_line(re.compile('^enabled$'), is_enabled_output) + if not is_enabled: + self.worsen_to_warning() + + is_active_output = self.read_command_output(['systemctl', 'is-active', self.service]) + is_active = self.contains_line(re.compile('^active$'), is_active_output) + if not is_active: + self.worsen_to_critical() + + status_output = self.read_command_output(['systemctl', 'status', self.service]) + for line in self.grep_lines(CheckSystemdService.DETAILS_BASE_RE, status_output): + key = line.group(1) + if key == 'Tasks': + self.add_perf_data('service_tasks', line.group(2)) + elif key == 'Memory': + self.add_perf_data('service_memory', line.group(2)) + + details = ', {service_memory}, {service_tasks} tasks' + if not is_active: + self.set_message_from_perf('{service_name} not running') + elif not is_enabled: + self.set_message_from_perf('{service_name} running but not enabled' + details) + else: + self.set_message_from_perf('{service_name} running' + details) + + +def main(): + """ + Module main for execution from shell script. + """ + CheckSystemdService().run() + +if __name__ == '__main__': + main() diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..499576c --- /dev/null +++ b/setup.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 + +# +# Copyright 2019 Vojtech Horky +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from setuptools import setup + +def get_readme(): + with open('README.md') as f: + return f.read() + +setup( + name='d3s-nagios-plugins', + version='0.1', + description='Collection of various Nagios plugins', + long_description=get_readme(), + classifiers=[ + 'Programming Language :: Python :: 3.6', + ], + keywords='nagios monitoring', + url='https://lab.d3s.mff.cuni.cz/nagios-plugins/', + install_requires=[], + include_package_data=True, + zip_safe=False, + packages=[ + 'd3s', + 'd3s.nagios_plugins', + ], + entry_points={ + 'console_scripts': [ + 'nagios_d3s_check_health=d3s.nagios_plugins.check_health:main', + 'nagios_d3s_check_systemd_service=d3s.nagios_plugins.check_systemd_service:main', + ], + }, +)