Xav Paice has proposed merging ~xavpaice/hw-health-charm:add_ipmi into hw-health-charm:master.
Requested reviews: Nagios Charm developers (nagios-charmers) For more details, see: https://code.launchpad.net/~xavpaice/hw-health-charm/+git/hw-health-charm/+merge/363593 -- Your team Nagios Charm developers is requested to review the proposed merge of ~xavpaice/hw-health-charm:add_ipmi into hw-health-charm:master.
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..0fbc388 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "check_ipmi_sensor"] + path = check_ipmi_sensor_v3 + url = g...@github.com:thomas-krenn/check_ipmi_sensor_v3.git + branch = master diff --git a/check_ipmi_sensor_v3 b/check_ipmi_sensor_v3 new file mode 160000 index 0000000..268eb36 --- /dev/null +++ b/check_ipmi_sensor_v3 @@ -0,0 +1 @@ +Subproject commit 268eb36f0c20b060fd04f4c9d1ff1fcd17d3269f diff --git a/src/README b/src/README.md similarity index 88% rename from src/README rename to src/README.md index 2c5475b..cc21324 100644 --- a/src/README +++ b/src/README.md @@ -25,28 +25,29 @@ Furthermore, other hardware in the roadmap is: # Usage -Step by step instructions on using the charm: +``` juju deploy ubuntu juju deploy hw-health juju deploy nrpe juju add-relation ubuntu nrpe juju add-relation ubuntu hw-health juju add-relation hw-health nrpe +``` Charmstore version already ships a resource. However, a new resource can be attached: - * Option 1: juju deploy hw-health --resource tools=/tmp/zipfile.zip + * Option 1: `juju deploy hw-health --resource tools=/tmp/zipfile.zip` - * Option 2: juju attach-resource hw-health tools=/tmp/zipfile.zip + * Option 2: `juju attach-resource hw-health tools=/tmp/zipfile.zip` In both cases format of zipfile.zip must be one of the following: -``example +``` zip /tmp/zipfile.zip megacli sas2ircu sas3ircu zip /tmp/zipfile.zip megacli etc. -`` +``` ## Known Limitations and Issues diff --git a/src/actions.yaml b/src/actions.yaml new file mode 100644 index 0000000..421c35e --- /dev/null +++ b/src/actions.yaml @@ -0,0 +1,2 @@ +clear-sel: + description: Using ipmi-sel, clear the system event log diff --git a/src/actions/__init__.py b/src/actions/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/actions/__init__.py diff --git a/src/actions/actions.py b/src/actions/actions.py new file mode 100755 index 0000000..cb845dc --- /dev/null +++ b/src/actions/actions.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python3 +# +# Copyright 2016,2019 Canonical Ltd +# +# 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. + +import os +import sys +import subprocess +from charmhelpers.core.hookenv import action_set, action_fail, log + + +def clear_sel(): + """ + Action to clear the IPMI system event log, prints the content + of the log before clearing it. + """ + command = ['/usr/sbin/ipmi-sel', '--post-clear'] + try: + output = subprocess.check_output(command) + log("Action clear-sel completed, sel log cleared") + action_set({"cleared_log": output}) + except subprocess.CalledProcessError as e: + action_fail("Action failed with {}".format(e)) + + +# A dictionary of all the defined actions to callables (which take +# parsed arguments). +ACTIONS = {"clear-sel": clear_sel} + + +def main(args): + action_name = os.path.basename(args[0]) + try: + action = ACTIONS[action_name] + except KeyError: + return "Action {} undefined".format(action_name) + else: + try: + action(args) + except Exception as e: + action_fail(str(e)) + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/src/actions/clear-sel b/src/actions/clear-sel new file mode 120000 index 0000000..405a394 --- /dev/null +++ b/src/actions/clear-sel @@ -0,0 +1 @@ +actions.py \ No newline at end of file diff --git a/src/config.yaml b/src/config.yaml index af0a62a..1a5bcfd 100644 --- a/src/config.yaml +++ b/src/config.yaml @@ -13,3 +13,7 @@ options: description: | Choose the tools to get deployed (hp, dell, supermicro, huawei) or leave the charm to self discover the tools needed to run hardware health checks. + enable_ipmi: + type: boolean + default: True + description: Enable the use of freeipmi tools to monitor hardware status. \ No newline at end of file diff --git a/src/files/ipmi/check_ipmi_sensor b/src/files/ipmi/check_ipmi_sensor new file mode 120000 index 0000000..575389d --- /dev/null +++ b/src/files/ipmi/check_ipmi_sensor @@ -0,0 +1 @@ +../../../check_ipmi_sensor_v3/check_ipmi_sensor \ No newline at end of file diff --git a/src/files/ipmi/check_ipmi_sensor_sudoer b/src/files/ipmi/check_ipmi_sensor_sudoer new file mode 100644 index 0000000..6571238 --- /dev/null +++ b/src/files/ipmi/check_ipmi_sensor_sudoer @@ -0,0 +1 @@ +nagios ALL=(root) NOPASSWD: /usr/sbin/ipmi-sensors, /usr/sbin/ipmi-sel diff --git a/src/layer.yaml b/src/layer.yaml index 237b87e..054b43e 100644 --- a/src/layer.yaml +++ b/src/layer.yaml @@ -1,4 +1,5 @@ includes: +- "layer:apt" - "layer:basic" - "layer:status" - "layer:nagios" @@ -7,5 +8,9 @@ includes: options: status: patch-hookenv: false + apt: + packages: + - "freeipmi" + - "libipc-run-perl" repo: "lp:hw-health-charm" is: "hw-health" diff --git a/src/lib/utils/hwdiscovery.py b/src/lib/utils/hwdiscovery.py index 1827ac6..d68389e 100644 --- a/src/lib/utils/hwdiscovery.py +++ b/src/lib/utils/hwdiscovery.py @@ -5,7 +5,7 @@ import re import subprocess # import sys -from charmhelpers.core import host +from charmhelpers.core import host, hookenv def get_tools(): @@ -46,6 +46,9 @@ def get_tools(): if _supports_mdadm(): tools.add('mdadm') + if hookenv.config('enable_ipmi'): + tools.add('ipmi') + return tools @@ -78,14 +81,10 @@ def _supports_mdadm(): for line in devices_raw.stdout.readlines(): line = line.decode().strip() if devices_re.match(line): + hookenv.log("Software RAID devices found") return True - # FIXME: log - # if devices_raw.stderr: - # print('STDERR: {}'.format(devices_raw.stderr - # .readline().decode().strip())) - except Exception: - # log error - pass + except Exception as e: + hookenv.log("mdadm scan failed with {}".format(e)) return False diff --git a/src/lib/utils/tooling.py b/src/lib/utils/tooling.py index 3b9b927..d826ff2 100644 --- a/src/lib/utils/tooling.py +++ b/src/lib/utils/tooling.py @@ -29,26 +29,32 @@ TOOLING = { 'filename': 'check_mdadm.py', 'cronjob': 'cron_mdadm.py' }, + 'ipmi': { + 'filename': 'ipmi/check_ipmi_sensor', + 'sudoers': 'ipmi/check_ipmi_sensor_sudoer' + }, } PLUGINS_DIR = '/usr/local/lib/nagios/plugins/' TOOLS_DIR = '/usr/local/bin/' CRONJOB_PATH = '/etc/cron.d/hw_health_{}' +SUDOERS_DIR = '/etc/sudoers.d' def install_resource(tools, resource): ntools = len(tools) if ntools == 0: return False - elif 'mdadm' in tools: - tools = [tool for tool in tools if tool != 'mdadm'] - ntools -= 1 + elif 'mdadm' or 'ipmi' in tools: + tools = [tool for tool in tools if tool not in ['mdadm', 'ipmi']] + ntools = len(tools) if ntools == 0: return True if not isinstance(resource, str) \ or not resource.endswith('.zip') \ or not os.path.exists(resource): + hookenv.log("The resource 'tools' does not end with .zip or does not exist") return False with tempfile.TemporaryDirectory() as tmpdir: @@ -139,6 +145,12 @@ def configure_tools(tools): count += 1 hookenv.log( 'NRPE script for tool [{}] configured'.format(tool)) + if 'sudoers' in TOOLING[tool]: + sudoers_path = _get_filepath(TOOLING[tool]['sudoers']) + if sudoers_path: + shutil.copy2(sudoers_path, SUDOERS_DIR) + count += 1 + hookenv.log('sudoers entry for tool [{}] added'.format(tool)) return count > 0 @@ -159,7 +171,7 @@ def configure_nrpe_checks(tools): for tool in tools: if tool in TOOLING and 'filename' in TOOLING[tool]: scriptname = os.path.basename(TOOLING[tool]['filename']) - cmd = os.path.join(PLUGINS_DIR, scriptname) + cmd = os.path.join(PLUGINS_DIR, scriptname) # TODO args to the command? if os.path.exists(cmd): nrpe_setup.add_check(tool, '{} Hardware Health', cmd) nrpe_setup.write() diff --git a/src/reactive/hw_health.py b/src/reactive/hw_health.py index 1a1402f..cd8e770 100644 --- a/src/reactive/hw_health.py +++ b/src/reactive/hw_health.py @@ -62,7 +62,17 @@ def install(): set_flag('hw-health.installed') status.waiting('Preparing scripts installation') else: - status.blocked('Tools not found: {}'.format(', '.join(tools))) + missing_tools = [tool for tool in tools if tool not in ['mdadm', 'ipmi']] + status.blocked('Tools not found: {}'.format(', '.join(missing_tools))) + + +@when_not('ipmi-tools.installed') +def install_ipmi(): + # copy script from submodule location + # add sudoers + # install freeipmi + # add nrpe check + pass @when('hw-health.installed') diff --git a/src/tests/unit/test_actions.py b/src/tests/unit/test_actions.py new file mode 100644 index 0000000..45c4469 --- /dev/null +++ b/src/tests/unit/test_actions.py @@ -0,0 +1,30 @@ +# Copyright 2019 Canonical Ltd +# +# 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. + +import unittest +import unittest.mock as mock +from actions.actions import clear_sel + + +class ClearSelTestCase(unittest.TestCase): + + @mock.patch('subprocess.check_output') + @mock.patch('subprocess.check_call') + @mock.patch('charmhelpers.core.hookenv.log') + def test_clear_sel(self, mock_log, mock_check_call, mock_subprocess): + sel_output = "Unittest system event log output" + mock_subprocess.return_value = sel_output + mock_check_call.return_value = None + clear_sel() + mock_check_call.assert_called_once_with(['action-set', "cleared_log={}".format(sel_output)]) diff --git a/src/tests/unit/test_cron_mdadm.py b/src/tests/unit/test_cron_mdadm.py index dffd65b..016745e 100644 --- a/src/tests/unit/test_cron_mdadm.py +++ b/src/tests/unit/test_cron_mdadm.py @@ -29,10 +29,13 @@ class TestCronMdadm(unittest.TestCase): @mock.patch('os.path.exists') @mock.patch('subprocess.Popen') - def test_get_devices_mdadm_exception(self, mdadm_details, path_exists): + @mock.patch('sys.exit') + def test_get_devices_mdadm_exception(self, mock_exit, mdadm_details, + path_exists): path_exists.return_value = True mdadm_details.side_effect = subprocess.CalledProcessError( 1, 'unittest') + mock_exit.return_value = 0 self.assertEqual(cron_mdadm.get_devices(), set()) @mock.patch('cron_mdadm.generate_output') diff --git a/src/tests/unit/test_hwdiscovery.py b/src/tests/unit/test_hwdiscovery.py index 8a62247..ef36da7 100644 --- a/src/tests/unit/test_hwdiscovery.py +++ b/src/tests/unit/test_hwdiscovery.py @@ -5,8 +5,6 @@ import sys import unittest import unittest.mock as mock -import charmhelpers.core.host # noqa: F401 - sys.path.append('lib/utils') import hwdiscovery # noqa: E402 @@ -15,7 +13,8 @@ class TestHWDiscovery(unittest.TestCase): @mock.patch('hwdiscovery._supports_mdadm') @mock.patch('hwdiscovery.dmidecode') @mock.patch('glob.glob') - def test_get_tools_megacli(self, driver, dmidecode, supports_mdadm): + @mock.patch('charmhelpers.core.hookenv.config') + def test_get_tools_megacli(self, mock_hookenv, driver, dmidecode, supports_mdadm): def side_effect_glob(value): if value == '/sys/bus/pci/drivers/megaraid_sas/00*': return ['/...megaraid'] @@ -25,14 +24,16 @@ class TestHWDiscovery(unittest.TestCase): supports_mdadm.return_value = False dmidecode.return_value = 'unittest' driver.side_effect = side_effect_glob + mock_hookenv.return_value = True real = hwdiscovery.get_tools() - expected = set(['megacli']) + expected = set(['megacli', 'ipmi']) self.assertEqual(real, expected) @mock.patch('hwdiscovery._supports_mdadm') @mock.patch('hwdiscovery.dmidecode') @mock.patch('glob.glob') - def test_get_tools_sas2ircu(self, driver, dmidecode, supports_mdadm): + @mock.patch('charmhelpers.core.hookenv.config') + def test_get_tools_sas2ircu(self, mock_hookenv, driver, dmidecode, supports_mdadm): def side_effect_glob(value): if value == '/sys/bus/pci/drivers/mpt2sas/00*': return ['/...mp2sas'] @@ -41,15 +42,17 @@ class TestHWDiscovery(unittest.TestCase): supports_mdadm.return_value = False dmidecode.return_value = 'unittest' + mock_hookenv.return_value = True driver.side_effect = side_effect_glob real = hwdiscovery.get_tools() - expected = set(['sas2ircu']) + expected = set(['sas2ircu', 'ipmi']) self.assertEqual(real, expected) @mock.patch('hwdiscovery._supports_mdadm') @mock.patch('hwdiscovery.dmidecode') @mock.patch('glob.glob') - def test_get_tools_sas3ircu(self, driver, dmidecode, supports_mdadm): + @mock.patch('charmhelpers.core.hookenv.config') + def test_get_tools_sas3ircu(self, mock_hookenv, driver, dmidecode, supports_mdadm): def side_effect_glob(value): if value not in ('/sys/bus/pci/drivers/megaraid_sas/00*', '/sys/bus/pci/drivers/mpt2sas/00*'): @@ -60,6 +63,7 @@ class TestHWDiscovery(unittest.TestCase): supports_mdadm.return_value = False dmidecode.return_value = 'unittest' driver.side_effect = side_effect_glob + mock_hookenv.return_value = False real = hwdiscovery.get_tools() expected = set(['sas3ircu']) self.assertEqual(real, expected) @@ -67,10 +71,12 @@ class TestHWDiscovery(unittest.TestCase): @mock.patch('hwdiscovery._supports_mdadm') @mock.patch('hwdiscovery.dmidecode') @mock.patch('glob.glob') - def test_get_tools_hpe(self, driver, dmidecode, supports_mdadm): + @mock.patch('charmhelpers.core.hookenv.config') + def test_get_tools_hpe(self, mock_hookenv, driver, dmidecode, supports_mdadm): supports_mdadm.return_value = False dmidecode.return_value = 'HPE' driver.side_effect = lambda x: [] + mock_hookenv.return_value = False real = hwdiscovery.get_tools() expected = set(['hpe']) self.assertEqual(real, expected) @@ -78,10 +84,12 @@ class TestHWDiscovery(unittest.TestCase): @mock.patch('hwdiscovery._supports_mdadm') @mock.patch('hwdiscovery.dmidecode') @mock.patch('glob.glob') - def test_get_tools_hp(self, driver, dmidecode, supports_mdadm): + @mock.patch('charmhelpers.core.hookenv.config') + def test_get_tools_hp(self, mock_hookenv, driver, dmidecode, supports_mdadm): supports_mdadm.return_value = False dmidecode.return_value = 'HP' driver.side_effect = lambda x: [] + mock_hookenv.return_value = False real = hwdiscovery.get_tools() expected = set(['hp']) self.assertEqual(real, expected) @@ -89,9 +97,11 @@ class TestHWDiscovery(unittest.TestCase): @mock.patch('hwdiscovery._supports_mdadm') @mock.patch('hwdiscovery.dmidecode') @mock.patch('glob.glob') - def test_get_tools_mdadm(self, driver, dmidecode, supports_mdadm): + @mock.patch('charmhelpers.core.hookenv.config') + def test_get_tools_mdadm(self, mock_hookenv, driver, dmidecode, supports_mdadm): supports_mdadm.return_value = True dmidecode.return_value = 'unittest' + mock_hookenv.return_value = False real = hwdiscovery.get_tools() expected = set(['mdadm']) self.assertEqual(real, expected) diff --git a/src/tests/unit/test_tooling.py b/src/tests/unit/test_tooling.py index f19dbde..5236f6e 100644 --- a/src/tests/unit/test_tooling.py +++ b/src/tests/unit/test_tooling.py @@ -4,8 +4,6 @@ import unittest import unittest.mock as mock import zipfile -import shutil # noqa: F401 -import tempfile # noqa: F401 sys.path.append('lib/utils') import tooling # noqa: E402 @@ -66,7 +64,7 @@ class TestTooling(unittest.TestCase): @mock.patch('zipfile.ZipFile') def test_install_resource_zipfile_error(self, zfile, path_exists): path_exists.return_value = True - zfile.side_effect = zipfile.BadZipFile('ugly error') + zfile.side_effect = zipfile.BadZipFile('Intended error for unit test') actual = tooling.install_resource(['megacli'], '/tmp/test.zip') expected = False self.assertEqual(actual, expected)
-- Mailing list: https://launchpad.net/~nagios-charmers Post to : nagios-charmers@lists.launchpad.net Unsubscribe : https://launchpad.net/~nagios-charmers More help : https://help.launchpad.net/ListHelp