Merge ~afreiberger/charm-hw-health:blacken-20.08 into charm-hw-health:master

Proposed by Drew Freiberger
Status: Merged
Merged at revision: 410c7d4c6a87b86a2e2b8209d2c3f739877bda48
Proposed branch: ~afreiberger/charm-hw-health:blacken-20.08
Merge into: charm-hw-health:master
Prerequisite: ~afreiberger/charm-hw-health:makefile-20.08
Diff against target: 5526 lines (+1489/-1426)
37 files modified
src/actions/actions.py (+8/-8)
src/files/common/check_hw_health_cron_output.py (+12/-10)
src/files/common/hw_health_lib.py (+74/-73)
src/files/hplog/cron_hplog.py (+81/-74)
src/files/ilorest/check_ilorest.py (+6/-9)
src/files/ilorest/cron_ilorest.py (+43/-41)
src/files/ipmi/check_ipmi.py (+17/-11)
src/files/ipmi/cron_ipmi_sensors.py (+16/-16)
src/files/mdadm/check_mdadm.py (+8/-13)
src/files/mdadm/cron_mdadm.py (+55/-64)
src/files/megacli/check_megacli.py (+36/-40)
src/files/nvme/check_nvme.py (+31/-22)
src/files/sas2ircu/check_sas2ircu.py (+16/-19)
src/files/sas3ircu/check_sas3ircu.py (+79/-90)
src/files/ssacli/cron_ssacli.py (+39/-33)
src/lib/hwhealth/discovery/lshw.py (+87/-87)
src/lib/hwhealth/discovery/supported_vendors.py (+14/-19)
src/lib/hwhealth/hwdiscovery.py (+26/-27)
src/lib/hwhealth/tools.py (+174/-180)
src/reactive/hw_health.py (+82/-73)
src/tests/download_nagios_plugin3.py (+7/-6)
src/tests/functional/conftest.py (+43/-34)
src/tests/functional/test_hwhealth.py (+222/-192)
src/tests/unit/lib/samples.py (+1/-6)
src/tests/unit/test_actions.py (+113/-72)
src/tests/unit/test_check_mdadm.py (+19/-30)
src/tests/unit/test_check_megacli.py (+15/-17)
src/tests/unit/test_check_nvme.py (+8/-8)
src/tests/unit/test_check_sas2ircu.py (+5/-5)
src/tests/unit/test_check_sas3ircu.py (+5/-5)
src/tests/unit/test_cron_hplog.py (+10/-7)
src/tests/unit/test_cron_ilorest.py (+12/-5)
src/tests/unit/test_cron_mdadm.py (+65/-70)
src/tests/unit/test_cron_ssacli.py (+20/-9)
src/tests/unit/test_hwdiscovery.py (+27/-37)
src/tests/unit/test_lshw.py (+11/-11)
src/tox.ini (+2/-3)
Reviewer Review Type Date Requested Status
Xav Paice (community) Approve
Review via email: mp+388951@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Xav Paice (xavpaice) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/actions/actions.py b/src/actions/actions.py
index e3f04cd..734841b 100755
--- a/src/actions/actions.py
+++ b/src/actions/actions.py
@@ -21,7 +21,7 @@ import sys
21from charmhelpers.core.hookenv import action_set, action_get, action_fail, log21from charmhelpers.core.hookenv import action_set, action_get, action_fail, log
2222
2323
24IPMI_SEL = '/usr/sbin/ipmi-sel'24IPMI_SEL = "/usr/sbin/ipmi-sel"
2525
2626
27def clear_sel():27def clear_sel():
@@ -30,11 +30,11 @@ def clear_sel():
30 Uses ipmi-sel --post-clear, clears the SEL log and stores the cleared entries30 Uses ipmi-sel --post-clear, clears the SEL log and stores the cleared entries
31 in action output.31 in action output.
32 """32 """
33 command = [IPMI_SEL, '--post-clear']33 command = [IPMI_SEL, "--post-clear"]
34 try:34 try:
35 output = subprocess.check_output(command)35 output = subprocess.check_output(command)
36 log("Action clear-sel completed, sel log cleared: {}".format(output))36 log("Action clear-sel completed, sel log cleared: {}".format(output))
37 action_set({'message': output.decode('UTF-8')})37 action_set({"message": output.decode("UTF-8")})
38 except subprocess.CalledProcessError as e:38 except subprocess.CalledProcessError as e:
39 action_fail("Action failed with {}".format(e))39 action_fail("Action failed with {}".format(e))
4040
@@ -45,23 +45,23 @@ def show_sel():
45 By default, this will show all non-nominal events. If you specify show-all,45 By default, this will show all non-nominal events. If you specify show-all,
46 it will show all events.46 it will show all events.
47 """47 """
48 show_all = action_get('show-all')48 show_all = action_get("show-all")
49 command = [IPMI_SEL, '--output-event-state']49 command = [IPMI_SEL, "--output-event-state"]
50 try:50 try:
51 header, body = None, None51 header, body = None, None
52 output = subprocess.check_output(command).decode('UTF-8')52 output = subprocess.check_output(command).decode("UTF-8")
53 lines = output.splitlines()53 lines = output.splitlines()
54 if lines:54 if lines:
55 header, body = lines[0], lines[1:]55 header, body = lines[0], lines[1:]
56 if not show_all:56 if not show_all:
57 # This is fairly naive, but it may be good enough for now57 # This is fairly naive, but it may be good enough for now
58 body = [line for line in body if 'Nominal' not in line]58 body = [line for line in body if "Nominal" not in line]
59 if body:59 if body:
60 final_output = "\n".join([header] + body)60 final_output = "\n".join([header] + body)
61 else:61 else:
62 final_output = "No matching entries found"62 final_output = "No matching entries found"
63 log("Action show-sel completed:\n{}".format(final_output))63 log("Action show-sel completed:\n{}".format(final_output))
64 action_set({'message': final_output})64 action_set({"message": final_output})
65 except subprocess.CalledProcessError as e:65 except subprocess.CalledProcessError as e:
66 action_fail("Action failed with {}".format(e))66 action_fail("Action failed with {}".format(e))
6767
diff --git a/src/files/common/check_hw_health_cron_output.py b/src/files/common/check_hw_health_cron_output.py
index ebb63e1..92ff372 100755
--- a/src/files/common/check_hw_health_cron_output.py
+++ b/src/files/common/check_hw_health_cron_output.py
@@ -10,11 +10,13 @@
1010
11from optparse import OptionParser11from optparse import OptionParser
1212
13from nagios_plugin3 import (check_file_freshness,13from nagios_plugin3 import (
14 try_check,14 check_file_freshness,
15 WarnError,15 try_check,
16 UnknownError,16 WarnError,
17 CriticalError)17 UnknownError,
18 CriticalError,
19)
1820
1921
20###############################################################################22###############################################################################
@@ -55,17 +57,17 @@ def main():
55 help="freshness time limit [default=%default]",57 help="freshness time limit [default=%default]",
56 metavar="SECONDS",58 metavar="SECONDS",
57 default=1200,59 default=1200,
58 type=int60 type=int,
59 )61 )
60 parser.add_option(62 parser.add_option(
61 "-f", "--filename",63 "-f",
64 "--filename",
62 dest="input_file",65 dest="input_file",
63 help=('file containing the output of '66 help=("file containing the output of cron_ilorest.py [default=%default]"),
64 'cron_ilorest.py [default=%default]'),
65 metavar="FILE",67 metavar="FILE",
66 nargs=1,68 nargs=1,
67 # default="/var/lib/nagios/UNSETOUTPUTFILE.out",69 # default="/var/lib/nagios/UNSETOUTPUTFILE.out",
68 type=str70 type=str,
69 )71 )
7072
71 (opts, args) = parser.parse_args()73 (opts, args) = parser.parse_args()
diff --git a/src/files/common/hw_health_lib.py b/src/files/common/hw_health_lib.py
index c75a4e5..945de0d 100644
--- a/src/files/common/hw_health_lib.py
+++ b/src/files/common/hw_health_lib.py
@@ -60,25 +60,30 @@ def read_ignore_file(ignore_file): # noqa C901
60 ignores = []60 ignores = []
61 if os.path.isfile(ignore_file):61 if os.path.isfile(ignore_file):
62 for line in open(ignore_file).readlines():62 for line in open(ignore_file).readlines():
63 d = {'matched': False, 'expired': False, 'line': line.rstrip(), 'ignore': None}63 d = {
64 "matched": False,
65 "expired": False,
66 "line": line.rstrip(),
67 "ignore": None,
68 }
64 line = line.strip()69 line = line.strip()
65 # special case lines starting with '*', do not disable if unmatched70 # special case lines starting with '*', do not disable if unmatched
66 persist = False71 persist = False
67 if line.startswith('*'):72 if line.startswith("*"):
68 persist = True73 persist = True
69 line = line.lstrip('*').strip()74 line = line.lstrip("*").strip()
70 # parse date lines75 # parse date lines
71 if line.startswith('['):76 if line.startswith("["):
72 parts = re.split('\\[|\\]', line, maxsplit=2)77 parts = re.split("\\[|\\]", line, maxsplit=2)
73 date = parts[1].strip()78 date = parts[1].strip()
74 ignore = parts[2].strip()79 ignore = parts[2].strip()
75 try:80 try:
76 date = datetime.strptime(date, '%Y-%m-%d %H:%M')81 date = datetime.strptime(date, "%Y-%m-%d %H:%M")
77 except ValueError:82 except ValueError:
78 try:83 try:
79 date = datetime.strptime(date, '%Y-%m-%d')84 date = datetime.strptime(date, "%Y-%m-%d")
80 except ValueError:85 except ValueError:
81 print("Failed to parse ignore date: {}".format(d['line']))86 print("Failed to parse ignore date: {}".format(d["line"]))
82 date = None87 date = None
83 if date:88 if date:
84 # Do not alert directly at midnight UTC89 # Do not alert directly at midnight UTC
@@ -88,18 +93,18 @@ def read_ignore_file(ignore_file): # noqa C901
88 if date:93 if date:
89 if datetime.now().weekday() in (5, 6):94 if datetime.now().weekday() in (5, 6):
90 # Ignore Saturday/Sunday to not annoy on-call95 # Ignore Saturday/Sunday to not annoy on-call
91 d['ignore'] = ignore96 d["ignore"] = ignore
92 elif date > datetime.now():97 elif date > datetime.now():
93 d['ignore'] = ignore98 d["ignore"] = ignore
94 else:99 else:
95 d['expired'] = True100 d["expired"] = True
96 if persist and not d['expired']:101 if persist and not d["expired"]:
97 # set matched True so will not get disabled on non-match102 # set matched True so will not get disabled on non-match
98 d['matched'] = True103 d["matched"] = True
99 # comment lines and empty lines are just added104 # comment lines and empty lines are just added
100 elif line.startswith('#') or not line:105 elif line.startswith("#") or not line:
101 # add with matched True so does not trigger a file rewrite106 # add with matched True so does not trigger a file rewrite
102 d['matched'] = True107 d["matched"] = True
103 # unrecognized lines left matched False so rewritten108 # unrecognized lines left matched False so rewritten
104 ignores.append(d)109 ignores.append(d)
105 return ignores110 return ignores
@@ -107,22 +112,24 @@ def read_ignore_file(ignore_file): # noqa C901
107112
108def write_ignore_file(ignores, ignore_file):113def write_ignore_file(ignores, ignore_file):
109 # if any ignores are not matched then write out file lines again114 # if any ignores are not matched then write out file lines again
110 if any([not i['matched'] for i in ignores]):115 if any([not i["matched"] for i in ignores]):
111 dirname, basename = os.path.split(ignore_file)116 dirname, basename = os.path.split(ignore_file)
112 date = datetime.now()117 date = datetime.now()
113 try:118 try:
114 f = tempfile.NamedTemporaryFile(dir=dirname, prefix=basename, delete=False)119 f = tempfile.NamedTemporaryFile(dir=dirname, prefix=basename, delete=False)
115 for ignore in ignores:120 for ignore in ignores:
116 if not ignore['matched'] and ignore['ignore']:121 if not ignore["matched"] and ignore["ignore"]:
117 ignore['line'] = "# not matched at {} #{}".format(122 ignore["line"] = "# not matched at {} #{}".format(
118 date.strftime("%Y-%m-%dT%H:%M:%S"), ignore['line']123 date.strftime("%Y-%m-%dT%H:%M:%S"), ignore["line"]
119 )124 )
120 elif ignore['expired']:125 elif ignore["expired"]:
121 # this won't get updated unless the alert has cleared126 # this won't get updated unless the alert has cleared
122 ignore['line'] = "# expired #{}".format(ignore['line'])127 ignore["line"] = "# expired #{}".format(ignore["line"])
123 elif not ignore['matched']:128 elif not ignore["matched"]:
124 ignore['line'] = "# unknown or bad format #{}".format(ignore['line'])129 ignore["line"] = "# unknown or bad format #{}".format(
125 f.write(ignore['line'] + '\n')130 ignore["line"]
131 )
132 f.write(ignore["line"] + "\n")
126 f.flush()133 f.flush()
127 os.fsync(f.fileno())134 os.fsync(f.fileno())
128 f.close()135 f.close()
@@ -137,11 +144,11 @@ def write_ignore_file(ignores, ignore_file):
137def ignore(line, ignores):144def ignore(line, ignores):
138 # check if each ignore is in line145 # check if each ignore is in line
139 for ignore in ignores:146 for ignore in ignores:
140 if ignore['ignore'] and ignore['ignore'] in line:147 if ignore["ignore"] and ignore["ignore"] in line:
141 ignoring_output.append("Ignoring: {}".format(line))148 ignoring_output.append("Ignoring: {}".format(line))
142 # note: ignores can be updated since it is passed by reference149 # note: ignores can be updated since it is passed by reference
143 # matched=True to keep using this ignore (see write_ignore_file)150 # matched=True to keep using this ignore (see write_ignore_file)
144 ignore['matched'] = True151 ignore["matched"] = True
145 return True152 return True
146 return False153 return False
147154
@@ -153,102 +160,96 @@ def get_hp_controller_slots():
153 Use the utility to determine the current controller slot(s) available for probing160 Use the utility to determine the current controller slot(s) available for probing
154 """161 """
155 slots = []162 slots = []
156 cmd = ['/usr/sbin/hpssacli', 'ctrl', 'all', 'show']163 cmd = ["/usr/sbin/hpssacli", "ctrl", "all", "show"]
157 try:164 try:
158 results = subprocess.check_output(cmd).decode('UTF-8')165 results = subprocess.check_output(cmd).decode("UTF-8")
159 except subprocess.CalledProcessError:166 except subprocess.CalledProcessError:
160 return slots167 return slots
161 for line in results.splitlines():168 for line in results.splitlines():
162 if 'in Slot' in line:169 if "in Slot" in line:
163 slots.append(line.split()[5])170 slots.append(line.split()[5])
164 return slots171 return slots
165172
166173
167class HWCronArgumentParser(argparse.ArgumentParser):174class HWCronArgumentParser(argparse.ArgumentParser):
168 def __init__(175 def __init__(self, def_write_file=None, *args, **kwargs):
169 self,
170 def_write_file=None,
171 *args,
172 **kwargs
173 ):
174 super().__init__(176 super().__init__(
175 formatter_class=argparse.ArgumentDefaultsHelpFormatter,177 formatter_class=argparse.ArgumentDefaultsHelpFormatter, *args, **kwargs
176 *args,
177 **kwargs
178 )178 )
179 # self.prog is populated by ArgumentParser179 # self.prog is populated by ArgumentParser
180 self._def_write_file = \180 self._def_write_file = def_write_file or "/var/lib/nagios/{}.out".format(
181 def_write_file or '/var/lib/nagios/{}.out'.format(self.prog)181 self.prog
182 )
182183
183 def parse_args(self, *args, **kwargs):184 def parse_args(self, *args, **kwargs):
184 self.add_argument(185 self.add_argument(
185 '-w', '--write', dest='write', type=str,186 "-w",
187 "--write",
188 dest="write",
189 type=str,
186 default=self._def_write_file,190 default=self._def_write_file,
187 help='cache tool output in this file',191 help="cache tool output in this file",
188 )192 )
189 super().parse_args(*args, **kwargs)193 super().parse_args(*args, **kwargs)
190194
191195
192class HWCheckArgumentParser(argparse.ArgumentParser):196class HWCheckArgumentParser(argparse.ArgumentParser):
193 def __init__(197 def __init__(self, def_input_file=None, *args, **kwargs):
194 self,
195 def_input_file=None,
196 *args,
197 **kwargs
198 ):
199 super().__init__(198 super().__init__(
200 formatter_class=argparse.ArgumentDefaultsHelpFormatter,199 formatter_class=argparse.ArgumentDefaultsHelpFormatter, *args, **kwargs
201 *args,
202 **kwargs
203 )200 )
204 # self.prog is populated by ArgumentParser201 # self.prog is populated by ArgumentParser
205 self._def_input_file = \202 self._def_input_file = def_input_file or "/var/lib/nagios/{}.out".format(
206 def_input_file or '/var/lib/nagios/{}.out'.format(self.prog)203 self.prog
204 )
207205
208 def parse_args(self, *args, **kwargs):206 def parse_args(self, *args, **kwargs):
209 self.add_argument(207 self.add_argument(
210 '-i', '--input', dest='input_file', type=str,208 "-i",
209 "--input",
210 dest="input_file",
211 type=str,
211 default=self._def_input_file,212 default=self._def_input_file,
212 help='read cached tool output from this file',213 help="read cached tool output from this file",
213 )214 )
214 super().parse_args(*args, **kwargs)215 super().parse_args(*args, **kwargs)
215216
216217
217class HPArgumentParser(HWCronArgumentParser):218class HPArgumentParser(HWCronArgumentParser):
218 def __init__(219 def __init__(self, def_exclude_file=None, *args, **kwargs):
219 self,
220 def_exclude_file=None,
221 *args,
222 **kwargs
223 ):
224 super().__init__(*args, **kwargs)220 super().__init__(*args, **kwargs)
225 self._def_exclude_file = \221 self._def_exclude_file = (
226 def_exclude_file or '/etc/nagios/{}.exclude.yaml'.format(self.prog)222 def_exclude_file or "/etc/nagios/{}.exclude.yaml".format(self.prog)
223 )
227224
228 def _expired(self, exclusion):225 def _expired(self, exclusion):
229 return 'expires' in exclusion and exclusion['expires'] < datetime.now()226 return "expires" in exclusion and exclusion["expires"] < datetime.now()
230227
231 def parse_args(self, *args, **kwargs):228 def parse_args(self, *args, **kwargs):
232 self.add_argument(229 self.add_argument(
233 '--debug', dest='debug', action='store_true',230 "--debug", dest="debug", action="store_true", help="Extra debugging",
234 help='Extra debugging',
235 )231 )
236232
237 self.add_argument(233 self.add_argument(
238 '--exclude', dest='exclude', type=str, action='append',234 "--exclude",
239 help='Errors to ignore (multiple)',235 dest="exclude",
236 type=str,
237 action="append",
238 help="Errors to ignore (multiple)",
240 )239 )
241240
242 self.add_argument(241 self.add_argument(
243 '--exclude-file', dest='exclude_file', type=str,242 "--exclude-file",
243 dest="exclude_file",
244 type=str,
244 default=self._def_exclude_file,245 default=self._def_exclude_file,
245 help='YAML file with errors to ignore',246 help="YAML file with errors to ignore",
246 )247 )
247248
248 # Ensure we initialize a namespace if needed,249 # Ensure we initialize a namespace if needed,
249 # and have a reference to it250 # and have a reference to it
250 namespace = kwargs.get('namespace') or argparse.Namespace()251 namespace = kwargs.get("namespace") or argparse.Namespace()
251 kwargs['namespace'] = namespace252 kwargs["namespace"] = namespace
252 # now parse args and put them in the namespace253 # now parse args and put them in the namespace
253 super().parse_args(*args, **kwargs)254 super().parse_args(*args, **kwargs)
254255
@@ -258,6 +259,6 @@ class HPArgumentParser(HWCronArgumentParser):
258 with open(namespace.exclude_file) as f:259 with open(namespace.exclude_file) as f:
259 for i in yaml.safe_load(f):260 for i in yaml.safe_load(f):
260 if not self._expired(i):261 if not self._expired(i):
261 namespace.exclude.append(i['error'])262 namespace.exclude.append(i["error"])
262263
263 return namespace264 return namespace
diff --git a/src/files/hplog/cron_hplog.py b/src/files/hplog/cron_hplog.py
index d2b21cf..57b52e2 100755
--- a/src/files/hplog/cron_hplog.py
+++ b/src/files/hplog/cron_hplog.py
@@ -25,7 +25,7 @@ try:
25except ImportError:25except ImportError:
26 # shared lib will be under $CHARM_SOURCE_DIR/files/common during unit tests26 # shared lib will be under $CHARM_SOURCE_DIR/files/common during unit tests
27 common_libs_dir = os.path.abspath(27 common_libs_dir = os.path.abspath(
28 os.path.join(os.path.dirname(__file__), '..', 'common')28 os.path.join(os.path.dirname(__file__), "..", "common")
29 )29 )
30 if common_libs_dir not in sys.path:30 if common_libs_dir not in sys.path:
31 sys.path.append(common_libs_dir)31 sys.path.append(common_libs_dir)
@@ -37,35 +37,43 @@ except ImportError:
37 )37 )
3838
39FLAG_PROCESSOR = {39FLAG_PROCESSOR = {
40 't': 'parse_temperature',40 "t": "parse_temperature",
41 'f': 'parse_fans',41 "f": "parse_fans",
42 'p': 'parse_power',42 "p": "parse_power",
43 'v': 'save_log',43 "v": "save_log",
44}44}
45OUTPUT_FILE = '/var/lib/nagios/hplog.out'45OUTPUT_FILE = "/var/lib/nagios/hplog.out"
46EXCLUDE_FILE = '/etc/nagios/hplog.exclude.yaml'46EXCLUDE_FILE = "/etc/nagios/hplog.exclude.yaml"
47DEBUG_FILE = '/var/lib/nagios/hplog.debug'47DEBUG_FILE = "/var/lib/nagios/hplog.debug"
4848
4949
50def parse_args(argv=None):50def parse_args(argv=None):
51 parser = HPArgumentParser(51 parser = HPArgumentParser(
52 prog='cron_hplog',52 prog="cron_hplog", def_write_file=OUTPUT_FILE, def_exclude_file=EXCLUDE_FILE
53 def_write_file=OUTPUT_FILE,
54 def_exclude_file=EXCLUDE_FILE
55 )53 )
56 parser.add_argument(54 parser.add_argument(
57 '-f', '--hplog_flags', dest='hplog_flags', type=str,55 "-f",
58 default=','.join(FLAG_PROCESSOR.keys()),56 "--hplog_flags",
59 help='Flags to call hplog with',57 dest="hplog_flags",
58 type=str,
59 default=",".join(FLAG_PROCESSOR.keys()),
60 help="Flags to call hplog with",
60 )61 )
61 parser.add_argument(62 parser.add_argument(
62 '-l', '--log_path', dest='log_path', type=str,63 "-l",
64 "--log_path",
65 dest="log_path",
66 type=str,
63 default=DEBUG_FILE,67 default=DEBUG_FILE,
64 help='Where to write hplog -v output for troubleshooting',68 help="Where to write hplog -v output for troubleshooting",
65 )69 )
66 parser.add_argument(70 parser.add_argument(
67 '-s', '--single_psu', dest='single_psu', action='store_true',71 "-s",
68 help='Do not alert on lack of PSU redundancy', default=False,72 "--single_psu",
73 dest="single_psu",
74 action="store_true",
75 help="Do not alert on lack of PSU redundancy",
76 default=False,
69 )77 )
70 return parser.parse_args(args=argv)78 return parser.parse_args(args=argv)
7179
@@ -77,15 +85,16 @@ def call_hplog(flag):
77 The output of this cron script will be checked by85 The output of this cron script will be checked by
78 nagios via check_hplog.py. The86 nagios via check_hplog.py. The
79 """87 """
80 env = 'export PATH=$PATH:/usr/sbin:/sbin'88 env = "export PATH=$PATH:/usr/sbin:/sbin"
8189
82 cmd = 'hplog -{}'.format(flag)90 cmd = "hplog -{}".format(flag)
83 try:91 try:
84 cmdline = '{}; {}'.format(env, cmd)92 cmdline = "{}; {}".format(env, cmd)
85 output = subprocess.check_output(cmdline, shell=True).decode('UTF-8')93 output = subprocess.check_output(cmdline, shell=True).decode("UTF-8")
86 except subprocess.CalledProcessError as e:94 except subprocess.CalledProcessError as e:
87 return ('Failed running command "{}" Return Code {}: {}'95 return 'Failed running command "{}" Return Code {}: {}' "".format(
88 ''.format(cmd, e.returncode, e.output))96 cmd, e.returncode, e.output
97 )
8998
90 funcname = FLAG_PROCESSOR[flag]99 funcname = FLAG_PROCESSOR[flag]
91 return globals()[funcname](output)100 return globals()[funcname](output)
@@ -106,10 +115,12 @@ def parse_temperature(result):
106 7 Basic Sensor CPU (2) Normal ---F/---C 260F/127C115 7 Basic Sensor CPU (2) Normal ---F/---C 260F/127C
107 """116 """
108 input_file = result.splitlines()117 input_file = result.splitlines()
109 if os.path.isfile('/etc/nagios/skip-cat-hp-temperature.txt'):118 if os.path.isfile("/etc/nagios/skip-cat-hp-temperature.txt"):
110 return119 return
111 header_line = input_file.pop(0).strip()120 header_line = input_file.pop(0).strip()
112 if header_line != "ID TYPE LOCATION STATUS CURRENT THRESHOLD": # noqa E501121 if (
122 header_line != "ID TYPE LOCATION STATUS CURRENT THRESHOLD"
123 ): # noqa E501
113 return "UNKNOWN Unrecognised header line in 'hplog -t' output"124 return "UNKNOWN Unrecognised header line in 'hplog -t' output"
114 for line in input_file:125 for line in input_file:
115 line = line.rstrip()126 line = line.rstrip()
@@ -123,11 +134,11 @@ def parse_temperature(result):
123 temp_current = line[42:51].split("/")[1].strip()134 temp_current = line[42:51].split("/")[1].strip()
124 temp_threshold = line[52:].split("/")[1].strip()135 temp_threshold = line[52:].split("/")[1].strip()
125 if temp_status not in ["Normal", "Nominal", "Absent"]:136 if temp_status not in ["Normal", "Nominal", "Absent"]:
126 return (137 return "%s: temperature is '%s' (%s / %s)" % (
127 "%s: temperature is '%s' (%s / %s)" % (temp_location,138 temp_location,
128 temp_status,139 temp_status,
129 temp_current,140 temp_current,
130 temp_threshold)141 temp_threshold,
131 )142 )
132143
133 return144 return
@@ -147,13 +158,15 @@ def parse_fans(result):
147 6 Var. Speed Processor Zone Normal Yes Low ( 36)158 6 Var. Speed Processor Zone Normal Yes Low ( 36)
148 """159 """
149 input_file = result.splitlines()160 input_file = result.splitlines()
150 if os.path.isfile('/etc/nagios/skip-cat-hp-fans.txt'):161 if os.path.isfile("/etc/nagios/skip-cat-hp-fans.txt"):
151 return162 return
152 header_line = input_file.pop(0).strip()163 header_line = input_file.pop(0).strip()
153 if header_line != "ID TYPE LOCATION STATUS REDUNDANT FAN SPEED": # noqa E501164 if (
165 header_line != "ID TYPE LOCATION STATUS REDUNDANT FAN SPEED"
166 ): # noqa E501
154 return "UNKNOWN Unrecognised header line in 'hplog -f' output"167 return "UNKNOWN Unrecognised header line in 'hplog -f' output"
155168
156 ignore_file = '/etc/nagios/ignores/ignores-cat-hp-fans.txt'169 ignore_file = "/etc/nagios/ignores/ignores-cat-hp-fans.txt"
157 ignores = read_ignore_file(ignore_file)170 ignores = read_ignore_file(ignore_file)
158 for line in input_file:171 for line in input_file:
159 line = line.rstrip()172 line = line.rstrip()
@@ -168,12 +181,7 @@ def parse_fans(result):
168 fan_speed = line[51:].strip()181 fan_speed = line[51:].strip()
169182
170 (return_now, msg) = process_fan_line(183 (return_now, msg) = process_fan_line(
171 fan_type,184 fan_type, fan_location, fan_status, fan_speed, fan_redundant, ignores
172 fan_location,
173 fan_status,
174 fan_speed,
175 fan_redundant,
176 ignores
177 )185 )
178 if return_now:186 if return_now:
179 return msg187 return msg
@@ -182,36 +190,37 @@ def parse_fans(result):
182190
183191
184def process_fan_line(192def process_fan_line(
185 fan_type,193 fan_type, fan_location, fan_status, fan_speed, fan_redundant, ignores
186 fan_location,
187 fan_status,
188 fan_speed,
189 fan_redundant,
190 ignores
191):194):
192 if fan_type == "Basic Fan":195 if fan_type == "Basic Fan":
193 return(False, None)196 return (False, None)
194197
195 if fan_type not in ["Var. Speed", "Pwr. Supply", "Auto. Speed"]:198 if fan_type not in ["Var. Speed", "Pwr. Supply", "Auto. Speed"]:
196 return(True, "UNKNOWN %s: Unrecognised fan type '%s'" % (fan_location,199 return (
197 fan_type))200 True,
201 "UNKNOWN %s: Unrecognised fan type '%s'" % (fan_location, fan_type),
202 )
198203
199 if fan_status not in ["Normal", "Nominal"]:204 if fan_status not in ["Normal", "Nominal"]:
200 err = "%s: fans are '%s' (%s / Redundant: %s)" % (fan_location,205 err = "%s: fans are '%s' (%s / Redundant: %s)" % (
201 fan_status,206 fan_location,
202 fan_speed,207 fan_status,
203 fan_redundant)208 fan_speed,
209 fan_redundant,
210 )
204 if not ignore(err, ignores):211 if not ignore(err, ignores):
205 return(True, err)212 return (True, err)
206213
207 if fan_redundant not in ["Yes", "N/A"] and fan_type == "Var. Speed":214 if fan_redundant not in ["Yes", "N/A"] and fan_type == "Var. Speed":
208 err = "%s: fans are not redundant (%s / Status: %s)" % (fan_location,215 err = "%s: fans are not redundant (%s / Status: %s)" % (
209 fan_speed,216 fan_location,
210 fan_redundant)217 fan_speed,
218 fan_redundant,
219 )
211 if not ignore(err, ignores):220 if not ignore(err, ignores):
212 return(True, err)221 return (True, err)
213222
214 return(False, None)223 return (False, None)
215224
216225
217def parse_power(result):226def parse_power(result):
@@ -224,13 +233,13 @@ def parse_power(result):
224 2 Standard Pwr. Supply Bay Normal Yes233 2 Standard Pwr. Supply Bay Normal Yes
225 """234 """
226 input_file = result.splitlines()235 input_file = result.splitlines()
227 if os.path.isfile('/etc/nagios/skip-cat-hp-power.txt'):236 if os.path.isfile("/etc/nagios/skip-cat-hp-power.txt"):
228 return237 return
229 header_line = input_file.pop(0).strip()238 header_line = input_file.pop(0).strip()
230 if header_line != "ID TYPE LOCATION STATUS REDUNDANT":239 if header_line != "ID TYPE LOCATION STATUS REDUNDANT":
231 return "UNKNOWN Unrecognised header line in 'hplog -p' output"240 return "UNKNOWN Unrecognised header line in 'hplog -p' output"
232241
233 ignore_file = '/etc/nagios/ignores/ignores-cat-hp-power.txt'242 ignore_file = "/etc/nagios/ignores/ignores-cat-hp-power.txt"
234 ignores = read_ignore_file(ignore_file)243 ignores = read_ignore_file(ignore_file)
235244
236 for line in input_file:245 for line in input_file:
@@ -244,10 +253,9 @@ def parse_power(result):
244 power_status = line[33:40].strip()253 power_status = line[33:40].strip()
245 # power_redundant = line[41:50].strip()254 # power_redundant = line[41:50].strip()
246 if power_type != "Standard":255 if power_type != "Standard":
247 err = "%s: Unrecognised power type '%s'" % (power_location,256 err = "%s: Unrecognised power type '%s'" % (power_location, power_type)
248 power_type)
249 if not ignore(err, ignores):257 if not ignore(err, ignores):
250 return 'UNKNOWN {}'.format(err)258 return "UNKNOWN {}".format(err)
251 if not ARGS.single_psu and power_status not in ["Normal", "Nominal"]:259 if not ARGS.single_psu and power_status not in ["Normal", "Nominal"]:
252 err = "%s: power supply is '%s'" % (power_location, power_status)260 err = "%s: power supply is '%s'" % (power_location, power_status)
253 if not ignore(err, ignores):261 if not ignore(err, ignores):
@@ -259,7 +267,7 @@ def save_log(result):
259 """267 """
260 Save full hplog -v output for troubleshooting after alert268 Save full hplog -v output for troubleshooting after alert
261 """269 """
262 with open(ARGS.log_path, 'w') as f:270 with open(ARGS.log_path, "w") as f:
263 f.write(result)271 f.write(result)
264 return272 return
265273
@@ -270,35 +278,34 @@ def main():
270278
271 try:279 try:
272 # This matches hpasmlited on latest packages for bionic on <= gen9280 # This matches hpasmlited on latest packages for bionic on <= gen9
273 subprocess.check_call('ps -ef | grep -q hp[a]sm', shell=True)281 subprocess.check_call("ps -ef | grep -q hp[a]sm", shell=True)
274 except subprocess.CalledProcessError as e:282 except subprocess.CalledProcessError as e:
275 msg = (283 msg = (
276 'UNKNOWN hp[a]sm daemon not found running, cannot run hplog: '284 "UNKNOWN hp[a]sm daemon not found running, cannot run hplog: "
277 '{}'.format(e.output)285 "{}".format(e.output)
278 )286 )
279 exit = 3287 exit = 3
280 else:288 else:
281 errors = []289 errors = []
282 for flag in ARGS.hplog_flags.split(','):290 for flag in ARGS.hplog_flags.split(","):
283 log_output = call_hplog(flag)291 log_output = call_hplog(flag)
284 if log_output:292 if log_output:
285 errors.append(log_output)293 errors.append(log_output)
286294
287 if len(errors) > 0:295 if len(errors) > 0:
288 msg = ('CRIT {} error(s): {}'296 msg = "CRIT {} error(s): {}".format(len(errors), " - ".join(errors))
289 ''.format(len(errors), ' - '.join(errors)))
290 exit = 2297 exit = 2
291 else:298 else:
292 msg = 'OK No errors found'299 msg = "OK No errors found"
293 exit = 0300 exit = 0
294301
295 if ARGS.write:302 if ARGS.write:
296 with open(ARGS.write, 'w') as f:303 with open(ARGS.write, "w") as f:
297 f.write(msg)304 f.write(msg)
298 else:305 else:
299 print(msg)306 print(msg)
300 sys.exit(exit)307 sys.exit(exit)
301308
302309
303if __name__ == '__main__':310if __name__ == "__main__":
304 main()311 main()
diff --git a/src/files/ilorest/check_ilorest.py b/src/files/ilorest/check_ilorest.py
index 430c632..8b477c8 100755
--- a/src/files/ilorest/check_ilorest.py
+++ b/src/files/ilorest/check_ilorest.py
@@ -10,10 +10,7 @@
1010
11from optparse import OptionParser11from optparse import OptionParser
1212
13from nagios_plugin3 import (check_file_freshness,13from nagios_plugin3 import check_file_freshness, try_check, WarnError, CriticalError
14 try_check,
15 WarnError,
16 CriticalError)
1714
1815
19###############################################################################16###############################################################################
@@ -52,16 +49,16 @@ def main():
52 help="freshness time limit [default=%default]",49 help="freshness time limit [default=%default]",
53 metavar="SECONDS",50 metavar="SECONDS",
54 default=1200,51 default=1200,
55 type=int52 type=int,
56 )53 )
57 parser.add_option(54 parser.add_option(
58 "-f", "--filename",55 "-f",
56 "--filename",
59 dest="input_file",57 dest="input_file",
60 help=('file containing the output of '58 help=("file containing the output of cron_ilorest.py [default=%default]"),
61 'cron_ilorest.py [default=%default]'),
62 metavar="FILE",59 metavar="FILE",
63 default="/var/lib/nagios/ilorest.nagios",60 default="/var/lib/nagios/ilorest.nagios",
64 type=str61 type=str,
65 )62 )
6663
67 (opts, args) = parser.parse_args()64 (opts, args) = parser.parse_args()
diff --git a/src/files/ilorest/cron_ilorest.py b/src/files/ilorest/cron_ilorest.py
index 739438d..561d0aa 100755
--- a/src/files/ilorest/cron_ilorest.py
+++ b/src/files/ilorest/cron_ilorest.py
@@ -18,7 +18,7 @@ try:
18except ImportError:18except ImportError:
19 # shared lib will be under $CHARM_SOURCE_DIR/files/common during unit tests19 # shared lib will be under $CHARM_SOURCE_DIR/files/common during unit tests
20 common_libs_dir = os.path.abspath(20 common_libs_dir = os.path.abspath(
21 os.path.join(os.path.dirname(__file__), '..', 'common')21 os.path.join(os.path.dirname(__file__), "..", "common")
22 )22 )
23 if common_libs_dir not in sys.path:23 if common_libs_dir not in sys.path:
24 sys.path.append(common_libs_dir)24 sys.path.append(common_libs_dir)
@@ -26,16 +26,16 @@ except ImportError:
2626
2727
28DEFAULT_SELECTORS = [28DEFAULT_SELECTORS = [
29 'Chassis',29 "Chassis",
30 'HpeSmartStorage',30 "HpeSmartStorage",
31 'Memory',31 "Memory",
32 'Power',32 "Power",
33 'Processor',33 "Processor",
34 'Thermal',34 "Thermal",
35]35]
3636
37EXCLUDE_FILE = '/etc/nagios/cron_ilorest.exclude.yaml'37EXCLUDE_FILE = "/etc/nagios/cron_ilorest.exclude.yaml"
38OUTPUT_FILE = '/var/lib/nagios/ilorest.nagios'38OUTPUT_FILE = "/var/lib/nagios/ilorest.nagios"
3939
4040
41class CronILOrest:41class CronILOrest:
@@ -44,62 +44,64 @@ class CronILOrest:
4444
45 def parse_args(self, argv=None):45 def parse_args(self, argv=None):
46 parser = HPArgumentParser(46 parser = HPArgumentParser(
47 prog='cron_ilorest',47 prog="cron_ilorest",
48 description=('Convert the output of ilorest into an appropriate '48 description=(
49 'Nagios status line'),49 "Convert the output of ilorest into an appropriate "
50 "Nagios status line"
51 ),
50 def_write_file=OUTPUT_FILE,52 def_write_file=OUTPUT_FILE,
51 def_exclude_file=EXCLUDE_FILE53 def_exclude_file=EXCLUDE_FILE,
52 )54 )
5355
54 parser.add_argument(56 parser.add_argument(
55 '--selectors', dest='selectors', type=str, nargs='+',57 "--selectors",
58 dest="selectors",
59 type=str,
60 nargs="+",
56 default=DEFAULT_SELECTORS,61 default=DEFAULT_SELECTORS,
57 help='iLO selectors to run',62 help="iLO selectors to run",
58 )63 )
5964
60 return parser.parse_args(args=argv)65 return parser.parse_args(args=argv)
6166
62 def check_selector(self, selector):67 def check_selector(self, selector):
63 if self.args.debug:68 if self.args.debug:
64 print('Checking selector {}'.format(selector), file=sys.stderr)69 print("Checking selector {}".format(selector), file=sys.stderr)
65 ilorest_output = self._get_json_ilorest_output(selector)70 ilorest_output = self._get_json_ilorest_output(selector)
6671
67 errors = []72 errors = []
68 jsonidx = -173 jsonidx = -1
69 # Disregard the first chunk of data, it's banner/debug/etc74 # Disregard the first chunk of data, it's banner/debug/etc
70 for jsondata in ilorest_output.split('\n{\n')[1:]:75 for jsondata in ilorest_output.split("\n{\n")[1:]:
71 # The output will be one or more JSON defs76 # The output will be one or more JSON defs
72 jsonidx += 177 jsonidx += 1
73 j = json.loads('{' + jsondata)78 j = json.loads("{" + jsondata)
74 errors += self._walk_selector(j, [selector, str(jsonidx)])79 errors += self._walk_selector(j, [selector, str(jsonidx)])
75 return errors80 return errors
7681
77 def _get_json_ilorest_output(self, selector):82 def _get_json_ilorest_output(self, selector):
78 cmd = ['ilorest', 'list', '-j', '--selector={}'.format(selector)]83 cmd = ["ilorest", "list", "-j", "--selector={}".format(selector)]
79 return check_output(cmd).decode('UTF-8')84 return check_output(cmd).decode("UTF-8")
8085
81 def _get_health_status_message(self, j, crumb_trail=[]):86 def _get_health_status_message(self, j, crumb_trail=[]):
82 desc = j['Name']87 desc = j["Name"]
83 if 'SerialNumber' in j:88 if "SerialNumber" in j:
84 desc += ' ({})'.format(j['SerialNumber'])89 desc += " ({})".format(j["SerialNumber"])
85 state = j.get('Status', 'null').get('State', 'unknown')90 state = j.get("Status", "null").get("State", "unknown")
86 health = j.get('Status', 'null').get('Health', 'unknown')91 health = j.get("Status", "null").get("Health", "unknown")
87 msg = '{} ({}): {} health {}'.format(' '.join(crumb_trail),92 msg = "{} ({}): {} health {}".format(" ".join(crumb_trail), desc, state, health)
88 desc,
89 state,
90 health)
91 if self.args.debug:93 if self.args.debug:
92 print(msg, file=sys.stderr)94 print(msg, file=sys.stderr)
9395
94 if msg in self.args.exclude and self.args.debug:96 if msg in self.args.exclude and self.args.debug:
95 print('Ignoring excluded error: {}'.format(msg), file=sys.stderr)97 print("Ignoring excluded error: {}".format(msg), file=sys.stderr)
96 return []98 return []
97 else:99 else:
98 return [msg]100 return [msg]
99101
100 def _walk_selector(self, j, crumb_trail=[]):102 def _walk_selector(self, j, crumb_trail=[]):
101 errors = []103 errors = []
102 if j.get('Status') and j.get('Status').get('Health') != 'OK':104 if j.get("Status") and j.get("Status").get("Health") != "OK":
103 errors.extend(self._get_health_status_message(j, crumb_trail))105 errors.extend(self._get_health_status_message(j, crumb_trail))
104106
105 for keyname in j.keys():107 for keyname in j.keys():
@@ -110,31 +112,31 @@ class CronILOrest:
110 for i in range(len(j[keyname])):112 for i in range(len(j[keyname])):
111 if type(j[keyname][i]) != dict:113 if type(j[keyname][i]) != dict:
112 continue114 continue
113 if 'Status' not in j[keyname][i]:115 if "Status" not in j[keyname][i]:
114 continue116 continue
115 self._walk_selector(j[keyname][i],117 self._walk_selector(j[keyname][i], (crumb_trail + [keyname, str(i)]))
116 (crumb_trail + [keyname, str(i)]))
117 return errors118 return errors
118119
119120
120def main(argv=None):121def main(argv=None):
121 cronilorest = CronILOrest(argv)122 cronilorest = CronILOrest(argv)
122123
123 errors = [cronilorest.check_selector(selector)124 errors = [
124 for selector in cronilorest.args.selectors]125 cronilorest.check_selector(selector) for selector in cronilorest.args.selectors
126 ]
125127
126 if len(errors) > 0:128 if len(errors) > 0:
127 msg = 'CRIT {} error(s): {}'.format(len(errors), ' - '.join(errors))129 msg = "CRIT {} error(s): {}".format(len(errors), " - ".join(errors))
128 exit = 2130 exit = 2
129 else:131 else:
130 msg = 'OK No errors found'132 msg = "OK No errors found"
131 exit = 0133 exit = 0
132134
133 if cronilorest.args.write:135 if cronilorest.args.write:
134 if cronilorest.args.write == '-':136 if cronilorest.args.write == "-":
135 print(msg)137 print(msg)
136 else:138 else:
137 with open(cronilorest.args.write, 'w') as f:139 with open(cronilorest.args.write, "w") as f:
138 f.write(msg)140 f.write(msg)
139 else:141 else:
140 # This should never happen since 'write' has a default value142 # This should never happen since 'write' has a default value
@@ -142,5 +144,5 @@ def main(argv=None):
142 sys.exit(exit)144 sys.exit(exit)
143145
144146
145if __name__ == '__main__':147if __name__ == "__main__":
146 main(sys.argv[1:])148 main(sys.argv[1:])
diff --git a/src/files/ipmi/check_ipmi.py b/src/files/ipmi/check_ipmi.py
index fafd774..1fbe34c 100644
--- a/src/files/ipmi/check_ipmi.py
+++ b/src/files/ipmi/check_ipmi.py
@@ -3,36 +3,42 @@
33
4import os4import os
55
6from nagios_plugin3 import CriticalError, UnknownError, WarnError, check_file_freshness, try_check6from nagios_plugin3 import (
77 CriticalError,
8OUTPUT_FILE = '/var/lib/nagios/ipmi_sensors.out'8 UnknownError,
9 WarnError,
10 check_file_freshness,
11 try_check,
12)
13
14OUTPUT_FILE = "/var/lib/nagios/ipmi_sensors.out"
9NAGIOS_ERRORS = {15NAGIOS_ERRORS = {
10 'CRITICAL': CriticalError,16 "CRITICAL": CriticalError,
11 'UNKNOWN': UnknownError,17 "UNKNOWN": UnknownError,
12 'WARNING': WarnError,18 "WARNING": WarnError,
13}19}
1420
1521
16def parse_output():22def parse_output():
17 if not os.path.exists(OUTPUT_FILE):23 if not os.path.exists(OUTPUT_FILE):
18 raise UnknownError('UNKNOWN: {} does not exist (yet?)'.format(OUTPUT_FILE))24 raise UnknownError("UNKNOWN: {} does not exist (yet?)".format(OUTPUT_FILE))
1925
20 # Check if file is newer than 10min26 # Check if file is newer than 10min
21 try_check(check_file_freshness, OUTPUT_FILE)27 try_check(check_file_freshness, OUTPUT_FILE)
2228
23 try:29 try:
24 with open(OUTPUT_FILE, 'r') as fd:30 with open(OUTPUT_FILE, "r") as fd:
25 output = fd.read()31 output = fd.read()
26 except PermissionError as error:32 except PermissionError as error:
27 raise UnknownError(error)33 raise UnknownError(error)
2834
29 for startline in NAGIOS_ERRORS:35 for startline in NAGIOS_ERRORS:
30 if output.startswith('{}: '.format(startline)):36 if output.startswith("{}: ".format(startline)):
31 func = NAGIOS_ERRORS[startline]37 func = NAGIOS_ERRORS[startline]
32 raise func(output)38 raise func(output)
3339
34 print('OK: {}'.format(output))40 print("OK: {}".format(output))
3541
3642
37if __name__ == '__main__':43if __name__ == "__main__":
38 try_check(parse_output)44 try_check(parse_output)
diff --git a/src/files/ipmi/cron_ipmi_sensors.py b/src/files/ipmi/cron_ipmi_sensors.py
index 2b6cdd5..aa3430d 100644
--- a/src/files/ipmi/cron_ipmi_sensors.py
+++ b/src/files/ipmi/cron_ipmi_sensors.py
@@ -4,20 +4,20 @@ import os
4import subprocess4import subprocess
5import sys5import sys
66
7CHECK_IPMI_PID = '/var/run/nagios/check_ipmi_sensors.pid'7CHECK_IPMI_PID = "/var/run/nagios/check_ipmi_sensors.pid"
8OUTPUT_FILE = '/var/lib/nagios/ipmi_sensors.out'8OUTPUT_FILE = "/var/lib/nagios/ipmi_sensors.out"
9TMP_OUTPUT_FILE = OUTPUT_FILE + '.tmp'9TMP_OUTPUT_FILE = OUTPUT_FILE + ".tmp"
10CMD = '/usr/local/lib/nagios/plugins/check_ipmi_sensor'10CMD = "/usr/local/lib/nagios/plugins/check_ipmi_sensor"
11NAGIOS_ERRORS = {11NAGIOS_ERRORS = {
12 1: 'WARNING',12 1: "WARNING",
13 2: 'CRITICAL',13 2: "CRITICAL",
14 3: 'UNKNOWN',14 3: "UNKNOWN",
15}15}
1616
1717
18def write_output_file(output):18def write_output_file(output):
19 try:19 try:
20 with open(TMP_OUTPUT_FILE, 'w') as fd:20 with open(TMP_OUTPUT_FILE, "w") as fd:
21 fd.write(output)21 fd.write(output)
22 except IOError as e:22 except IOError as e:
23 print("Cannot write output file {}, error {}".format(TMP_OUTPUT_FILE, e))23 print("Cannot write output file {}, error {}".format(TMP_OUTPUT_FILE, e))
@@ -29,16 +29,16 @@ def gather_metrics():
29 # Check if a PID file exists29 # Check if a PID file exists
30 if os.path.exists(CHECK_IPMI_PID):30 if os.path.exists(CHECK_IPMI_PID):
31 # is the PID valid?31 # is the PID valid?
32 with open(CHECK_IPMI_PID, 'r') as fd:32 with open(CHECK_IPMI_PID, "r") as fd:
33 PID = fd.read()33 PID = fd.read()
34 if PID not in os.listdir('/proc'):34 if PID not in os.listdir("/proc"):
35 # PID file is invalid, remove it35 # PID file is invalid, remove it
36 os.remove(CHECK_IPMI_PID)36 os.remove(CHECK_IPMI_PID)
37 else:37 else:
38 return38 return
3939
40 try:40 try:
41 with open(CHECK_IPMI_PID, 'w') as fd:41 with open(CHECK_IPMI_PID, "w") as fd:
42 fd.write(str(os.getpid()))42 fd.write(str(os.getpid()))
43 except IOError as e:43 except IOError as e:
44 # unable to write PID file, can't lock44 # unable to write PID file, can't lock
@@ -49,17 +49,17 @@ def gather_metrics():
49 if len(sys.argv) > 1:49 if len(sys.argv) > 1:
50 cmdline.extend(sys.argv[1:])50 cmdline.extend(sys.argv[1:])
51 try:51 try:
52 output = subprocess.check_output(cmdline).decode('utf8')52 output = subprocess.check_output(cmdline).decode("utf8")
53 write_output_file(output)53 write_output_file(output)
54 except subprocess.CalledProcessError as error:54 except subprocess.CalledProcessError as error:
55 output = error.stdout.decode(errors='ignore')55 output = error.stdout.decode(errors="ignore")
56 write_output_file('{}: {}'.format(NAGIOS_ERRORS[error.returncode], output))56 write_output_file("{}: {}".format(NAGIOS_ERRORS[error.returncode], output))
57 except PermissionError as error:57 except PermissionError as error:
58 write_output_file('UNKNOWN: {}'.format(error))58 write_output_file("UNKNOWN: {}".format(error))
5959
60 # remove pid reference60 # remove pid reference
61 os.remove(CHECK_IPMI_PID)61 os.remove(CHECK_IPMI_PID)
6262
6363
64if __name__ == '__main__':64if __name__ == "__main__":
65 gather_metrics()65 gather_metrics()
diff --git a/src/files/mdadm/check_mdadm.py b/src/files/mdadm/check_mdadm.py
index 29a82c6..c602877 100755
--- a/src/files/mdadm/check_mdadm.py
+++ b/src/files/mdadm/check_mdadm.py
@@ -12,38 +12,33 @@ try:
12except ImportError:12except ImportError:
13 # shared lib will be under $CHARM_SOURCE_DIR/files/common during unit tests13 # shared lib will be under $CHARM_SOURCE_DIR/files/common during unit tests
14 common_libs_dir = os.path.abspath(14 common_libs_dir = os.path.abspath(
15 os.path.join(os.path.dirname(__file__), '..', 'common')15 os.path.join(os.path.dirname(__file__), "..", "common")
16 )16 )
17 if common_libs_dir not in sys.path:17 if common_libs_dir not in sys.path:
18 sys.path.append(common_libs_dir)18 sys.path.append(common_libs_dir)
19 from hw_health_lib import HWCheckArgumentParser19 from hw_health_lib import HWCheckArgumentParser
2020
21INPUT_FILE = '/var/lib/nagios/mdadm.out'21INPUT_FILE = "/var/lib/nagios/mdadm.out"
22ARGS = argparse.Namespace()22ARGS = argparse.Namespace()
2323
2424
25def parse_output():25def parse_output():
26 if not os.path.exists(ARGS.input_file):26 if not os.path.exists(ARGS.input_file):
27 raise UnknownError(27 raise UnknownError("UNKNOWN: file not found ({})".format(ARGS.input_file))
28 'UNKNOWN: file not found ({})'.format(ARGS.input_file)
29 )
3028
31 with open(ARGS.input_file, 'r') as fd:29 with open(ARGS.input_file, "r") as fd:
32 for line in fd.readlines():30 for line in fd.readlines():
33 line = line.strip()31 line = line.strip()
34 if line.startswith('CRITICAL: '):32 if line.startswith("CRITICAL: "):
35 raise CriticalError(line)33 raise CriticalError(line)
36 elif line.startswith('WARNING: '):34 elif line.startswith("WARNING: "):
37 raise WarnError(line)35 raise WarnError(line)
38 else:36 else:
39 print(line)37 print(line)
4038
4139
42def parse_args(argv=None):40def parse_args(argv=None):
43 parser = HWCheckArgumentParser(41 parser = HWCheckArgumentParser(prog="check_mdadm", def_input_file=INPUT_FILE)
44 prog='check_mdadm',
45 def_input_file=INPUT_FILE,
46 )
47 return parser.parse_args(args=argv, namespace=ARGS)42 return parser.parse_args(args=argv, namespace=ARGS)
4843
4944
@@ -52,5 +47,5 @@ def main(argv):
52 try_check(parse_output)47 try_check(parse_output)
5348
5449
55if __name__ == '__main__':50if __name__ == "__main__":
56 main(sys.argv[1:])51 main(sys.argv[1:])
diff --git a/src/files/mdadm/cron_mdadm.py b/src/files/mdadm/cron_mdadm.py
index 3e7e8e9..fed9ea8 100755
--- a/src/files/mdadm/cron_mdadm.py
+++ b/src/files/mdadm/cron_mdadm.py
@@ -13,7 +13,7 @@ try:
13except ImportError:13except ImportError:
14 # shared lib will be under $CHARM_SOURCE_DIR/files/common during unit tests14 # shared lib will be under $CHARM_SOURCE_DIR/files/common during unit tests
15 common_libs_dir = os.path.abspath(15 common_libs_dir = os.path.abspath(
16 os.path.join(os.path.dirname(__file__), '..', 'common')16 os.path.join(os.path.dirname(__file__), "..", "common")
17 )17 )
18 if common_libs_dir not in sys.path:18 if common_libs_dir not in sys.path:
19 sys.path.append(common_libs_dir)19 sys.path.append(common_libs_dir)
@@ -25,22 +25,20 @@ ARGS = argparse.Namespace()
2525
2626
27def get_devices():27def get_devices():
28 if os.path.exists('/sbin/mdadm'):28 if os.path.exists("/sbin/mdadm"):
29 try:29 try:
30 cmd = ["/sbin/mdadm", "--detail", "--scan"]30 cmd = ["/sbin/mdadm", "--detail", "--scan"]
31 devices_raw = subprocess.check_output(cmd)31 devices_raw = subprocess.check_output(cmd)
32 devices_re = re.compile(r"^ARRAY\s+([^ ]+) ")32 devices_re = re.compile(r"^ARRAY\s+([^ ]+) ")
33 devices = set()33 devices = set()
34 for line in devices_raw.decode().split('\n'):34 for line in devices_raw.decode().split("\n"):
35 line = line.strip()35 line = line.strip()
36 device_re = devices_re.match(line)36 device_re = devices_re.match(line)
37 if device_re is not None:37 if device_re is not None:
38 devices.add(device_re.group(1))38 devices.add(device_re.group(1))
39 return devices39 return devices
40 except subprocess.CalledProcessError as error:40 except subprocess.CalledProcessError as error:
41 rc = generate_output(41 rc = generate_output("CRITICAL: get_devices error - {}".format(error))
42 "CRITICAL: get_devices error - {}".format(error)
43 )
44 if rc:42 if rc:
45 sys.exit(0)43 sys.exit(0)
46 return set()44 return set()
@@ -48,28 +46,28 @@ def get_devices():
4846
49def generate_output(msg):47def generate_output(msg):
50 try:48 try:
51 with open(TEMP_FILE, 'w') as fd:49 with open(TEMP_FILE, "w") as fd:
52 fd.write(msg)50 fd.write(msg)
53 shutil.move(TEMP_FILE, ARGS.write)51 shutil.move(TEMP_FILE, ARGS.write)
54 return True52 return True
55 except Exception as error:53 except Exception as error:
56 print('Unable to generate output file:', error)54 print("Unable to generate output file:", error)
57 return False55 return False
5856
5957
60def get_devices_stats(devices):58def get_devices_stats(devices):
61 mdadm_detail = ['/sbin/mdadm', '--detail']59 mdadm_detail = ["/sbin/mdadm", "--detail"]
62 mdadm_detail.extend(sorted(devices))60 mdadm_detail.extend(sorted(devices))
6361
64 devices_details_raw = subprocess.check_output(mdadm_detail)62 devices_details_raw = subprocess.check_output(mdadm_detail)
6563
66 devices_re = r'^(/\S+):$'64 devices_re = r"^(/\S+):$"
67 state_re = r'^\s*State\s+:\s+(.+)\s*$'65 state_re = r"^\s*State\s+:\s+(.+)\s*$"
68 status_re = r'^\s*(Active|Working|Failed|Spare) Devices\s+:\s+(\d+)$'66 status_re = r"^\s*(Active|Working|Failed|Spare) Devices\s+:\s+(\d+)$"
69 rebuild_status_re = r'^\s*Rebuild Status\s+:\s+(\d+%\s+\S+)$'67 rebuild_status_re = r"^\s*Rebuild Status\s+:\s+(\d+%\s+\S+)$"
70 removed_re = r'^\s*-\s+0\s+0\s+(\d+)\s+removed$'68 removed_re = r"^\s*-\s+0\s+0\s+(\d+)\s+removed$"
71 # 4 8 162 3 spare rebuilding /dev/sdk269 # 4 8 162 3 spare rebuilding /dev/sdk2
72 rebuilding_re = r'^\s*\d+\s+\d+\s+\d+\s+\d+\s+\S+\s+rebuilding\s+(\S+)$'70 rebuilding_re = r"^\s*\d+\s+\d+\s+\d+\s+\d+\s+\S+\s+rebuilding\s+(\S+)$"
7371
74 devices_cre = re.compile(devices_re)72 devices_cre = re.compile(devices_re)
75 state_cre = re.compile(state_re)73 state_cre = re.compile(state_re)
@@ -80,54 +78,50 @@ def get_devices_stats(devices):
8078
81 device = None79 device = None
82 devices_stats = {}80 devices_stats = {}
83 for line in devices_details_raw.decode().split('\n'):81 for line in devices_details_raw.decode().split("\n"):
84 line = line.rstrip()82 line = line.rstrip()
85 m = devices_cre.match(line)83 m = devices_cre.match(line)
86 if m:84 if m:
87 device = m.group(1)85 device = m.group(1)
88 devices_stats[device] = {86 devices_stats[device] = {
89 'stats': {87 "stats": {"Active": 0, "Working": 0, "Failed": 0, "Spare": 0},
90 'Active': 0,88 "rebuild_status": "",
91 'Working': 0,89 "degraded": False,
92 'Failed': 0,90 "recovering": False,
93 'Spare': 0,91 "removed": [],
94 },92 "rebuilding": [],
95 'rebuild_status': '',
96 'degraded': False,
97 'recovering': False,
98 'removed': [],
99 'rebuilding': [],
100 }93 }
101 continue94 continue
10295
103 m = state_cre.match(line)96 m = state_cre.match(line)
104 if m:97 if m:
105 # format for State line can be "clean" or "clean, degraded" or "active, degraded, rebuilding", etc.98 # format for State line can be "clean" or "clean, degraded",
99 # or "active, degraded, rebuilding", etc.
106 states = m.group(1).split(", ")100 states = m.group(1).split(", ")
107 if 'degraded' in states and device:101 if "degraded" in states and device:
108 devices_stats[device]['degraded'] = True102 devices_stats[device]["degraded"] = True
109 if 'recovering' in states and device:103 if "recovering" in states and device:
110 devices_stats[device]['recovering'] = True104 devices_stats[device]["recovering"] = True
111 continue105 continue
112106
113 m = status_cre.match(line)107 m = status_cre.match(line)
114 if m and device:108 if m and device:
115 devices_stats[device]['stats'][m.group(1)] = int(m.group(2))109 devices_stats[device]["stats"][m.group(1)] = int(m.group(2))
116 continue110 continue
117111
118 m = removed_cre.match(line)112 m = removed_cre.match(line)
119 if m and device:113 if m and device:
120 devices_stats[device]['removed'].append(m.group(1))114 devices_stats[device]["removed"].append(m.group(1))
121 continue115 continue
122116
123 m = rebuild_status_cre.match(line)117 m = rebuild_status_cre.match(line)
124 if m and device:118 if m and device:
125 devices_stats[device]['rebuild_status'] = m.group(1)119 devices_stats[device]["rebuild_status"] = m.group(1)
126 continue120 continue
127121
128 m = rebuilding_cre.match(line)122 m = rebuilding_cre.match(line)
129 if m and device:123 if m and device:
130 devices_stats[device]['rebuilding'].append(m.group(1))124 devices_stats[device]["rebuilding"].append(m.group(1))
131 continue125 continue
132126
133 return devices_stats127 return devices_stats
@@ -136,14 +130,12 @@ def get_devices_stats(devices):
136def parse_output(): # noqa:C901130def parse_output(): # noqa:C901
137 devices = get_devices()131 devices = get_devices()
138 if len(devices) == 0:132 if len(devices) == 0:
139 return generate_output('WARNING: unexpectedly checked no devices')133 return generate_output("WARNING: unexpectedly checked no devices")
140134
141 try:135 try:
142 devices_stats = get_devices_stats(devices)136 devices_stats = get_devices_stats(devices)
143 except subprocess.CalledProcessError as error:137 except subprocess.CalledProcessError as error:
144 return generate_output(138 return generate_output("WARNING: error executing mdadm: {}".format(error))
145 "WARNING: error executing mdadm: {}".format(error)
146 )
147139
148 msg = []140 msg = []
149 critical = False141 critical = False
@@ -151,51 +143,50 @@ def parse_output(): # noqa:C901
151 for device in devices_stats:143 for device in devices_stats:
152 parts = []144 parts = []
153 # Is device degraded?145 # Is device degraded?
154 if devices_stats[device]['degraded'] and devices_stats[device]['recovering']:146 if devices_stats[device]["degraded"] and devices_stats[device]["recovering"]:
155 warning = True147 warning = True
156 parts = ['{} recovering'.format(device)]148 parts = ["{} recovering".format(device)]
157 elif devices_stats[device]['degraded']:149 elif devices_stats[device]["degraded"]:
158 critical = True150 critical = True
159 parts = ['{} degraded'.format(device)]151 parts = ["{} degraded".format(device)]
160 else:152 else:
161 parts = ['{} ok'.format(device)]153 parts = ["{} ok".format(device)]
162154
163 # If Failed drives are found, list counters (how many?)155 # If Failed drives are found, list counters (how many?)
164 failed_cnt = devices_stats[device]['stats'].get('Failed', 0)156 failed_cnt = devices_stats[device]["stats"].get("Failed", 0)
165 if failed_cnt > 0:157 if failed_cnt > 0:
166 critical = True158 critical = True
167 dev_stats = [159 dev_stats = [
168 '{}[{}]'.format(status, devices_stats[device]['stats'][status])160 "{}[{}]".format(status, devices_stats[device]["stats"][status])
169 for status in sorted(devices_stats[device]['stats'])161 for status in sorted(devices_stats[device]["stats"])
170 ]162 ]
171 parts.extend(dev_stats)163 parts.extend(dev_stats)
172164
173 if len(devices_stats[device]['removed']) != 0:165 if len(devices_stats[device]["removed"]) != 0:
174 critical = True166 critical = True
175 members = " and ".join(devices_stats[device]['removed'])167 members = " and ".join(devices_stats[device]["removed"])
176 parts.append('RaidDevice(s) {} marked removed'.format(members))168 parts.append("RaidDevice(s) {} marked removed".format(members))
177169
178 if len(devices_stats[device]['rebuilding']) != 0:170 if len(devices_stats[device]["rebuilding"]) != 0:
179 rebuilding_members = " ".join(devices_stats[device]['rebuilding'])171 rebuilding_members = " ".join(devices_stats[device]["rebuilding"])
180 rebuild_status = devices_stats[device]['rebuild_status']172 rebuild_status = devices_stats[device]["rebuild_status"]
181 parts.append('{} rebuilding ({})'.format(rebuilding_members, rebuild_status))173 parts.append(
174 "{} rebuilding ({})".format(rebuilding_members, rebuild_status)
175 )
182176
183 msg.append(', '.join(parts))177 msg.append(", ".join(parts))
184178
185 if critical:179 if critical:
186 msg = 'CRITICAL: {}'.format('; '.join(msg))180 msg = "CRITICAL: {}".format("; ".join(msg))
187 elif warning:181 elif warning:
188 msg = 'WARNING: {}'.format('; '.join(msg))182 msg = "WARNING: {}".format("; ".join(msg))
189 else:183 else:
190 msg = 'OK: {}'.format('; '.join(msg))184 msg = "OK: {}".format("; ".join(msg))
191 return generate_output(msg)185 return generate_output(msg)
192186
193187
194def parse_args(argv=None):188def parse_args(argv=None):
195 parser = HWCronArgumentParser(189 parser = HWCronArgumentParser(prog="cron_mdadm", def_write_file=OUTPUT_FILE)
196 prog='cron_mdadm',
197 def_write_file=OUTPUT_FILE,
198 )
199 return parser.parse_args(args=argv, namespace=ARGS)190 return parser.parse_args(args=argv, namespace=ARGS)
200191
201192
diff --git a/src/files/megacli/check_megacli.py b/src/files/megacli/check_megacli.py
index b587d10..03d3a5d 100755
--- a/src/files/megacli/check_megacli.py
+++ b/src/files/megacli/check_megacli.py
@@ -13,55 +13,54 @@ try:
13except ImportError:13except ImportError:
14 # shared lib will be under $CHARM_SOURCE_DIR/files/common during unit tests14 # shared lib will be under $CHARM_SOURCE_DIR/files/common during unit tests
15 common_libs_dir = os.path.abspath(15 common_libs_dir = os.path.abspath(
16 os.path.join(os.path.dirname(__file__), '..', 'common')16 os.path.join(os.path.dirname(__file__), "..", "common")
17 )17 )
18 if common_libs_dir not in sys.path:18 if common_libs_dir not in sys.path:
19 sys.path.append(common_libs_dir)19 sys.path.append(common_libs_dir)
20 from hw_health_lib import HWCheckArgumentParser20 from hw_health_lib import HWCheckArgumentParser
2121
22INPUT_FILE = '/var/lib/nagios/megacli.out'22INPUT_FILE = "/var/lib/nagios/megacli.out"
23ARGS = argparse.Namespace()23ARGS = argparse.Namespace()
2424
2525
26def handle_results(26def handle_results(nlines, match, critical, errors, num_ldrive, num_pdrive, policy):
27 nlines, match, critical, errors, num_ldrive, num_pdrive, policy
28):
29 if nlines == 0:27 if nlines == 0:
30 raise WarnError('WARNING: controller not found')28 raise WarnError("WARNING: controller not found")
31 elif not match:29 elif not match:
32 raise WarnError('WARNING: error parsing megacli output')30 raise WarnError("WARNING: error parsing megacli output")
33 elif critical:31 elif critical:
34 if len(errors) > 0:32 if len(errors) > 0:
35 msg = ', '.join([33 msg = ", ".join(
36 '{}({})'.format(cnt, vars()[cnt])34 [
37 for cnt in ('failed_ld', 'wrg_policy_ld')35 "{}({})".format(cnt, vars()[cnt])
38 if vars().get(cnt, 0) > 036 for cnt in ("failed_ld", "wrg_policy_ld")
39 ])37 if vars().get(cnt, 0) > 0
40 msg += '; '.join(errors)38 ]
39 )
40 msg += "; ".join(errors)
41 else:41 else:
42 msg = 'failure caught but no output available'42 msg = "failure caught but no output available"
43 raise CriticalError('CRITICAL: {}'.format(msg))43 raise CriticalError("CRITICAL: {}".format(msg))
44 elif len(errors) > 0:44 elif len(errors) > 0:
45 raise WarnError('WARNING: {}'.format('; '.join(errors)))45 raise WarnError("WARNING: {}".format("; ".join(errors)))
4646
47 else:47 else:
48 if num_ldrive == 0:48 if num_ldrive == 0:
49 msg = 'OK: no disks configured for RAID'49 msg = "OK: no disks configured for RAID"
50 else:50 else:
51 msg = ('OK: Optimal, ldrives[{}], pdrives[{}]'51 msg = "OK: Optimal, ldrives[{}], pdrives[{}]".format(num_ldrive, num_pdrive)
52 ''.format(num_ldrive, num_pdrive))
53 if policy:52 if policy:
54 msg += ', policy[{}]'.format(policy)53 msg += ", policy[{}]".format(policy)
55 print(msg)54 print(msg)
5655
5756
58def parse_output(policy=False): # noqa:C90157def parse_output(policy=False): # noqa:C901
59 noadapter_re = r'^Adapter \d+: No Virtual Drive Configured'58 noadapter_re = r"^Adapter \d+: No Virtual Drive Configured"
60 adapter_re = r'^Adapter (\d+) -- Virtual Drive Information:'59 adapter_re = r"^Adapter (\d+) -- Virtual Drive Information:"
61 ldrive_re = r'^Virtual Drive\s*:\s+(\d+)'60 ldrive_re = r"^Virtual Drive\s*:\s+(\d+)"
62 state_re = r'^State\s*:\s+([^\n]+)'61 state_re = r"^State\s*:\s+([^\n]+)"
63 npdrives_re = r'^Number Of Drives(?: per span)?\s*:\s+(\d+)'62 npdrives_re = r"^Number Of Drives(?: per span)?\s*:\s+(\d+)"
64 w_policy_re = r'^Current Cache Policy\s*:\s+([^,]+)'63 w_policy_re = r"^Current Cache Policy\s*:\s+([^,]+)"
6564
66 noadapter_cre = re.compile(noadapter_re)65 noadapter_cre = re.compile(noadapter_re)
67 adapter_cre = re.compile(adapter_re)66 adapter_cre = re.compile(adapter_re)
@@ -78,7 +77,7 @@ def parse_output(policy=False): # noqa:C901
7877
79 with open(ARGS.input_file) as devices_raw:78 with open(ARGS.input_file) as devices_raw:
80 for line in devices_raw.readlines():79 for line in devices_raw.readlines():
81 if len(line.strip()) and not line.startswith('Exit Code'):80 if len(line.strip()) and not line.startswith("Exit Code"):
82 nlines += 181 nlines += 1
8382
84 if noadapter_cre.match(line):83 if noadapter_cre.match(line):
@@ -101,10 +100,11 @@ def parse_output(policy=False): # noqa:C901
101 if m:100 if m:
102 num_ldrive += 1101 num_ldrive += 1
103 state = m.group(1)102 state = m.group(1)
104 if state != 'Optimal':103 if state != "Optimal":
105 failed_ld += 1104 failed_ld += 1
106 msg = 'adapter({}):ld({}):state({})'.format(105 msg = "adapter({}):ld({}):state({})".format(
107 adapter_id, ldrive_id, state)106 adapter_id, ldrive_id, state
107 )
108 errors.append(msg)108 errors.append(msg)
109 critical = True109 critical = True
110 continue110 continue
@@ -120,22 +120,18 @@ def parse_output(policy=False): # noqa:C901
120 w_policy = m.group(1)120 w_policy = m.group(1)
121 if w_policy != policy:121 if w_policy != policy:
122 wrg_policy_ld += 1122 wrg_policy_ld += 1
123 msg = 'adp({}):ld({}):policy({})'.format(123 msg = "adp({}):ld({}):policy({})".format(
124 adapter_id, ldrive_id, w_policy)124 adapter_id, ldrive_id, w_policy
125 )
125 errors.append(msg)126 errors.append(msg)
126 critical = True127 critical = True
127 continue128 continue
128129
129 handle_results(130 handle_results(nlines, match, critical, errors, num_ldrive, num_pdrive, policy)
130 nlines, match, critical, errors, num_ldrive, num_pdrive, policy
131 )
132131
133132
134def parse_args(argv=None):133def parse_args(argv=None):
135 parser = HWCheckArgumentParser(134 parser = HWCheckArgumentParser(prog="check_megacli", def_input_file=INPUT_FILE)
136 prog='check_megacli',
137 def_input_file=INPUT_FILE,
138 )
139 return parser.parse_args(args=argv, namespace=ARGS)135 return parser.parse_args(args=argv, namespace=ARGS)
140136
141137
@@ -144,5 +140,5 @@ def main(argv, policy=False):
144 try_check(parse_output, policy)140 try_check(parse_output, policy)
145141
146142
147if __name__ == '__main__':143if __name__ == "__main__":
148 main(sys.argv[1:])144 main(sys.argv[1:])
diff --git a/src/files/nvme/check_nvme.py b/src/files/nvme/check_nvme.py
index d4117c9..701bc9c 100755
--- a/src/files/nvme/check_nvme.py
+++ b/src/files/nvme/check_nvme.py
@@ -8,55 +8,64 @@ import re
8import subprocess8import subprocess
9from nagios_plugin3 import CriticalError, try_check, UnknownError9from nagios_plugin3 import CriticalError, try_check, UnknownError
1010
11NVME_RE = re.compile(r'^/dev/nvme\d+$')11NVME_RE = re.compile(r"^/dev/nvme\d+$")
1212
1313
14def parse_output():14def parse_output():
15 keymap = {}15 keymap = {}
16 critical = False16 critical = False
17 alloutputs = []17 alloutputs = []
18 for device in glob.glob('/dev/nvme*'):18 for device in glob.glob("/dev/nvme*"):
19 if not NVME_RE.match(device):19 if not NVME_RE.match(device):
20 continue20 continue
21 try:21 try:
22 output = subprocess.check_output(['sudo', '/usr/sbin/nvme',22 output = subprocess.check_output(
23 'smart-log', device])23 ["sudo", "/usr/sbin/nvme", "smart-log", device]
24 )
24 except subprocess.CalledProcessError as error:25 except subprocess.CalledProcessError as error:
25 print('nvme check error: {}'.format(error))26 print("nvme check error: {}".format(error))
26 return27 return
2728
28 for line in output.decode(errors='ignore').splitlines():29 for line in output.decode(errors="ignore").splitlines():
29 datavalues_re = re.match(r'^(\w+)\s+:\s+([\d.]+)', line.strip())30 datavalues_re = re.match(r"^(\w+)\s+:\s+([\d.]+)", line.strip())
30 if not datavalues_re:31 if not datavalues_re:
31 continue32 continue
32 key, value = datavalues_re.groups()33 key, value = datavalues_re.groups()
33 keymap[key] = value.replace('.', '')34 keymap[key] = value.replace(".", "")
3435
35 if int(keymap['critical_warning']) != 0:36 if int(keymap["critical_warning"]) != 0:
36 status = ('CRITICAL: {} critical_warning is {}'37 status = ("CRITICAL: {} critical_warning is {}").format(
37 '').format(device, keymap['critical_warning'])38 device, keymap["critical_warning"]
39 )
38 critical = True40 critical = True
39 else:41 else:
40 status = 'OK: no errors on {}'.format(device)42 status = "OK: no errors on {}".format(device)
4143
42 alloutputs.append('{} | {}'.format(44 alloutputs.append(
43 status, ' '.join(['{}={}'.format(repr(key), value)45 "{} | {}".format(
44 for key, value in keymap.items()])))46 status,
47 " ".join(
48 ["{}={}".format(repr(key), value) for key, value in keymap.items()]
49 ),
50 )
51 )
4552
46 if critical:53 if critical:
47 raise CriticalError('\n'.join(alloutputs))54 raise CriticalError("\n".join(alloutputs))
4855
49 if not alloutputs:56 if not alloutputs:
50 raise UnknownError('no nvme devices found')57 raise UnknownError("no nvme devices found")
5158
52 print('\n'.join(alloutputs))59 print("\n".join(alloutputs))
5360
5461
55def parse_args(argv=None):62def parse_args(argv=None):
56 parser = argparse.ArgumentParser(63 parser = argparse.ArgumentParser(
57 prog='check_nvme',64 prog="check_nvme",
58 description=('this program reads the nvme smart-log and outputs an '65 description=(
59 'appropriate Nagios status line'),66 "this program reads the nvme smart-log and outputs an "
67 "appropriate Nagios status line"
68 ),
60 formatter_class=argparse.ArgumentDefaultsHelpFormatter,69 formatter_class=argparse.ArgumentDefaultsHelpFormatter,
61 )70 )
62 return parser.parse_args(argv)71 return parser.parse_args(argv)
@@ -67,5 +76,5 @@ def main(argv):
67 try_check(parse_output)76 try_check(parse_output)
6877
6978
70if __name__ == '__main__':79if __name__ == "__main__":
71 main(sys.argv[1:])80 main(sys.argv[1:])
diff --git a/src/files/sas2ircu/check_sas2ircu.py b/src/files/sas2ircu/check_sas2ircu.py
index b38e131..9628893 100755
--- a/src/files/sas2ircu/check_sas2ircu.py
+++ b/src/files/sas2ircu/check_sas2ircu.py
@@ -13,20 +13,20 @@ try:
13except ImportError:13except ImportError:
14 # shared lib will be under $CHARM_SOURCE_DIR/files/common during unit tests14 # shared lib will be under $CHARM_SOURCE_DIR/files/common during unit tests
15 common_libs_dir = os.path.abspath(15 common_libs_dir = os.path.abspath(
16 os.path.join(os.path.dirname(__file__), '..', 'common')16 os.path.join(os.path.dirname(__file__), "..", "common")
17 )17 )
18 if common_libs_dir not in sys.path:18 if common_libs_dir not in sys.path:
19 sys.path.append(common_libs_dir)19 sys.path.append(common_libs_dir)
20 from hw_health_lib import HWCheckArgumentParser20 from hw_health_lib import HWCheckArgumentParser
2121
22INPUT_FILE = '/var/lib/nagios/sas2ircu.out'22INPUT_FILE = "/var/lib/nagios/sas2ircu.out"
23ARGS = argparse.Namespace()23ARGS = argparse.Namespace()
2424
2525
26def parse_output():26def parse_output():
27 enclosure_re = r'^\s+Enclosure #\s+:\s+(\d+)'27 enclosure_re = r"^\s+Enclosure #\s+:\s+(\d+)"
28 slot_re = r'^\s+Slot #\s+:\s+(\d+)'28 slot_re = r"^\s+Slot #\s+:\s+(\d+)"
29 state_re = r'^\s+State\s+:\s+(\S+)'29 state_re = r"^\s+State\s+:\s+(\S+)"
3030
31 encl_slot_state_cre = [31 encl_slot_state_cre = [
32 re.compile(enclosure_re),32 re.compile(enclosure_re),
@@ -47,28 +47,25 @@ def parse_output():
4747
48 if len(device) == 3:48 if len(device) == 3:
49 tmpdev = devices.get(device[2], [])49 tmpdev = devices.get(device[2], [])
50 tmpdev.append('{}:{}'.format(device[0], device[1]))50 tmpdev.append("{}:{}".format(device[0], device[1]))
51 devices[device[2]] = tmpdev51 devices[device[2]] = tmpdev
52 if not ('Ready' in device or 'Optimal' in device):52 if not ("Ready" in device or "Optimal" in device):
53 critical = True53 critical = True
54 device = []54 device = []
5555
56 msg = '; '.join([56 msg = "; ".join(
57 '{}[{}]'.format(state, ','.join(devices[state])) for state in devices57 ["{}[{}]".format(state, ",".join(devices[state])) for state in devices]
58 ])58 )
59 if msg == '':59 if msg == "":
60 raise WarnError('WARNING: no output')60 raise WarnError("WARNING: no output")
61 elif critical:61 elif critical:
62 raise CriticalError('CRITICAL: {}'.format(msg))62 raise CriticalError("CRITICAL: {}".format(msg))
63 else:63 else:
64 print('OK: {}'.format(msg))64 print("OK: {}".format(msg))
6565
6666
67def parse_args(argv=None):67def parse_args(argv=None):
68 parser = HWCheckArgumentParser(68 parser = HWCheckArgumentParser(prog="check_sas2ircu", def_input_file=INPUT_FILE)
69 prog='check_sas2ircu',
70 def_input_file=INPUT_FILE,
71 )
72 return parser.parse_args(args=argv, namespace=ARGS)69 return parser.parse_args(args=argv, namespace=ARGS)
7370
7471
@@ -77,5 +74,5 @@ def main(argv):
77 try_check(parse_output)74 try_check(parse_output)
7875
7976
80if __name__ == '__main__':77if __name__ == "__main__":
81 main(sys.argv[1:])78 main(sys.argv[1:])
diff --git a/src/files/sas3ircu/check_sas3ircu.py b/src/files/sas3ircu/check_sas3ircu.py
index d62a90f..8b62679 100755
--- a/src/files/sas3ircu/check_sas3ircu.py
+++ b/src/files/sas3ircu/check_sas3ircu.py
@@ -14,182 +14,174 @@ try:
14except ImportError:14except ImportError:
15 # shared lib will be under $CHARM_SOURCE_DIR/files/common during unit tests15 # shared lib will be under $CHARM_SOURCE_DIR/files/common during unit tests
16 common_libs_dir = os.path.abspath(16 common_libs_dir = os.path.abspath(
17 os.path.join(os.path.dirname(__file__), '..', 'common')17 os.path.join(os.path.dirname(__file__), "..", "common")
18 )18 )
19 if common_libs_dir not in sys.path:19 if common_libs_dir not in sys.path:
20 sys.path.append(common_libs_dir)20 sys.path.append(common_libs_dir)
21 from hw_health_lib import HWCheckArgumentParser21 from hw_health_lib import HWCheckArgumentParser
2222
23INPUT_FILE = '/var/lib/nagios/sas3ircu.out'23INPUT_FILE = "/var/lib/nagios/sas3ircu.out"
24ARGS = argparse.Namespace()24ARGS = argparse.Namespace()
2525
2626
27def parse_output(input_file):27def parse_output(input_file):
28 '''28 """
29 Turn the whole sas3ircu output into a dictionary29 Turn the whole sas3ircu output into a dictionary
30 '''30 """
31 sections_re = re.compile(31 sections_re = re.compile(
32 r'(?<=^Controller information\n)'32 r"(?<=^Controller information\n)"
33 r'-+\n'33 r"-+\n"
34 r'(?P<ctrl>(?:.|\n)*)'34 r"(?P<ctrl>(?:.|\n)*)"
35 r'^-+\n'35 r"^-+\n"
36 r'^IR Volume information\n'36 r"^IR Volume information\n"
37 r'-+\n'37 r"-+\n"
38 r'(?P<vols>(?:.|\n)*)'38 r"(?P<vols>(?:.|\n)*)"
39 r'^-+\n'39 r"^-+\n"
40 r'^Physical device information\n'40 r"^Physical device information\n"
41 r'-+\n'41 r"-+\n"
42 r'(?P<disks>(?:.|\n)*)'42 r"(?P<disks>(?:.|\n)*)"
43 r'^-+\n'43 r"^-+\n"
44 r'^Enclosure information\n'44 r"^Enclosure information\n"
45 r'-+\n'45 r"-+\n"
46 r'(?P<encl>(?:.|\n)*)'46 r"(?P<encl>(?:.|\n)*)"
47 r'^-+\n',47 r"^-+\n",
48 re.MULTILINE48 re.MULTILINE,
49 )49 )
50 disks_re = re.compile(50 disks_re = re.compile(
51 r'(?<=^Device is a Hard disk\n)(?P<kv_data>(?:.|\n)*?)(?=^$)',51 r"(?<=^Device is a Hard disk\n)(?P<kv_data>(?:.|\n)*?)(?=^$)", re.MULTILINE
52 re.MULTILINE
53 )52 )
5453
55 with open(input_file) as devices_raw:54 with open(input_file) as devices_raw:
56 sections = sections_re.search(devices_raw.read()).groupdict()55 sections = sections_re.search(devices_raw.read()).groupdict()
57 controller = _kv_parse(sections['ctrl'])56 controller = _kv_parse(sections["ctrl"])
58 volumes = _vols_parse(sections['vols'])57 volumes = _vols_parse(sections["vols"])
59 # This collects disk level information in a structure simulating the58 # This collects disk level information in a structure simulating the
60 # physical encl/slot arrangement59 # physical encl/slot arrangement
61 topology = defaultdict(dict)60 topology = defaultdict(dict)
62 for match in disks_re.findall(sections['disks']):61 for match in disks_re.findall(sections["disks"]):
63 disk = _kv_parse(match)62 disk = _kv_parse(match)
64 encl = disk['Enclosure #']63 encl = disk["Enclosure #"]
65 slot = disk['Slot #']64 slot = disk["Slot #"]
66 topology[encl][slot] = disk65 topology[encl][slot] = disk
67 enclosure = _kv_parse(sections['encl'])66 enclosure = _kv_parse(sections["encl"])
6867
69 return {68 return {
70 'controller': controller,69 "controller": controller,
71 'volumes': volumes,70 "volumes": volumes,
72 'disks': topology,71 "disks": topology,
73 'enclosure': enclosure,72 "enclosure": enclosure,
74 }73 }
7574
7675
77def _vols_parse(text):76def _vols_parse(text):
78 vols_re = re.compile(77 vols_re = re.compile(
79 r'^IR volume (?P<n>\d+)\n'78 r"^IR volume (?P<n>\d+)\n"
80 r'(?P<kv_data>(?:.|\n)*?)'79 r"(?P<kv_data>(?:.|\n)*?)"
81 r'\s+Physical hard disks\s+:.*\n'80 r"\s+Physical hard disks\s+:.*\n"
82 r'(?P<topology>(?:^\s+PHY.*\n)+)',81 r"(?P<topology>(?:^\s+PHY.*\n)+)",
83 re.MULTILINE82 re.MULTILINE,
84 )83 )
85 vol_topology_re = re.compile(84 vol_topology_re = re.compile(
86 r'\s+PHY\[(?P<n>\d+)\]\s+Enclosure#\/Slot#\s+'85 r"\s+PHY\[(?P<n>\d+)\]\s+Enclosure#\/Slot#\s+" r":\s+(?P<enc>\d+):(?P<slot>\d+)"
87 r':\s+(?P<enc>\d+):(?P<slot>\d+)'
88 )86 )
89 volumes = {}87 volumes = {}
90 for (vol_n, kv_data, vol_topology) in vols_re.findall(text):88 for (vol_n, kv_data, vol_topology) in vols_re.findall(text):
91 topology = {}89 topology = {}
92 for (member_n, enc, slot) in vol_topology_re.findall(vol_topology):90 for (member_n, enc, slot) in vol_topology_re.findall(vol_topology):
93 topology[member_n] = {'enc': enc, 'slot': slot}91 topology[member_n] = {"enc": enc, "slot": slot}
94 volumes[vol_n] = {**_kv_parse(kv_data), 'topology': topology}92 volumes[vol_n] = {**_kv_parse(kv_data), "topology": topology}
9593
96 return volumes94 return volumes
9795
9896
99def _kv_parse(text):97def _kv_parse(text):
100 '''98 """
101 Build a dict by parsing text like:99 Build a dict by parsing text like:
102100
103 key1 : value1101 key1 : value1
104 key2 : value2102 key2 : value2
105 '''103 """
106 key_value_re = re.compile(104 key_value_re = re.compile(r"^\s*(?P<key>.*?)\s+:\s+(?P<value>.*)")
107 r'^\s*(?P<key>.*?)\s+:\s+(?P<value>.*)'
108 )
109 text = text.strip()105 text = text.strip()
110 return {106 return {
111 m.group('key'): m.group('value')107 m.group("key"): m.group("value")
112 for m in map(key_value_re.search, text.split('\n'))108 for m in map(key_value_re.search, text.split("\n"))
113 }109 }
114110
115111
116def eval_status(data):112def eval_status(data):
117 '''113 """
118 Given a dictionary and a set of rules, determine the state of the storage114 Given a dictionary and a set of rules, determine the state of the storage
119 subsystem115 subsystem
120 '''116 """
121 OK = 'Okay (OKY)'117 OK = "Okay (OKY)"
122 READY = 'Ready (RDY)'118 READY = "Ready (RDY)"
123 OPTIMAL = 'Optimal (OPT)'119 OPTIMAL = "Optimal (OPT)"
124 status = Status()120 status = Status()
125121
126 # 1. Volumes must be in Okay state122 # 1. Volumes must be in Okay state
127 for volume in data['volumes'].values():123 for volume in data["volumes"].values():
128 vol_id = volume['Volume ID']124 vol_id = volume["Volume ID"]
129 vol_status = volume['Status of volume']125 vol_status = volume["Status of volume"]
130 if vol_status != OK:126 if vol_status != OK:
131 status.crit("Volume {}: {}".format(vol_id, vol_status))127 status.crit("Volume {}: {}".format(vol_id, vol_status))
132 else:128 else:
133 # 2. Volume members must be in Optimal state129 # 2. Volume members must be in Optimal state
134 for member in volume['topology'].values():130 for member in volume["topology"].values():
135 disk = data['disks'][member['enc']][member['slot']]131 disk = data["disks"][member["enc"]][member["slot"]]
136 if disk['State'] != OPTIMAL:132 if disk["State"] != OPTIMAL:
137 msg = "Disk {}:{} {}".format(133 msg = "Disk {}:{} {}".format(
138 member['enc'],134 member["enc"], member["slot"], disk["State"]
139 member['slot'],
140 disk['State']
141 )135 )
142 if disk['State'] == READY:136 if disk["State"] == READY:
143 status.warn(msg)137 status.warn(msg)
144 else:138 else:
145 status.crit(msg)139 status.crit(msg)
146 # 3. Disks can be in Optimal or Ready state ("ready" is ok for non-RAID140 # 3. Disks can be in Optimal or Ready state ("ready" is ok for non-RAID
147 # members)141 # members)
148 for enclosure_id, enclosure in data['disks'].items():142 for enclosure_id, enclosure in data["disks"].items():
149 for slot_id, slot in enclosure.items():143 for slot_id, slot in enclosure.items():
150 if slot['State'] not in [OPTIMAL, READY]:144 if slot["State"] not in [OPTIMAL, READY]:
151 status.crit("Disk {}:{} {}".format(145 status.crit(
152 enclosure_id,146 "Disk {}:{} {}".format(enclosure_id, slot_id, slot["State"])
153 slot_id,147 )
154 slot['State']
155 ))
156 status.get_status()148 status.get_status()
157149
158150
159class Status:151class Status:
160 '''152 """
161 Class hiding the whole "CRIT >> WARN >> OK" priority scheme153 Class hiding the whole "CRIT >> WARN >> OK" priority scheme
162 '''154 """
163 def __init__(self, status='OK'):155
156 def __init__(self, status="OK"):
164 self._status = status157 self._status = status
165 self._msgs = set()158 self._msgs = set()
166159
167 def crit(self, msg):160 def crit(self, msg):
168 self._status = 'CRITICAL'161 self._status = "CRITICAL"
169 self._msgs.add(msg)162 self._msgs.add(msg)
170163
171 def warn(self, msg):164 def warn(self, msg):
172 if self._status != 'CRITICAL':165 if self._status != "CRITICAL":
173 self._status = 'WARNING'166 self._status = "WARNING"
174 self._msgs.add(msg)167 self._msgs.add(msg)
175168
176 def ok(self, msg):169 def ok(self, msg):
177 self._msgs.add(msg)170 self._msgs.add(msg)
178171
179 def get_status(self):172 def get_status(self):
180 '''173 """
181 Render the current status, rasing nagios_plugin3 exceptions if things174 Render the current status, rasing nagios_plugin3 exceptions if things
182 are not OK175 are not OK
183 '''176 """
184 if self._status == 'OK':177 if self._status == "OK":
185 msg = '{}: no errors'.format(self._status)178 msg = "{}: no errors".format(self._status)
186 print(msg)179 print(msg)
187 else:180 else:
188 msg = '{}: {}'.format(self._status,181 msg = "{}: {}".format(self._status, " | ".join(self._msgs))
189 ' | '.join(self._msgs))182 if self._status == "CRITICAL":
190 if self._status == 'CRITICAL':
191 raise CriticalError(msg)183 raise CriticalError(msg)
192 elif self._status == 'WARNING':184 elif self._status == "WARNING":
193 raise WarnError(msg)185 raise WarnError(msg)
194 else:186 else:
195 # this really shouldn't be happening187 # this really shouldn't be happening
@@ -200,10 +192,7 @@ class Status:
200192
201193
202def parse_args(argv=None):194def parse_args(argv=None):
203 parser = HWCheckArgumentParser(195 parser = HWCheckArgumentParser(prog="check_sas3ircu", def_input_file=INPUT_FILE)
204 prog='check_sas3ircu',
205 def_input_file=INPUT_FILE,
206 )
207 return parser.parse_args(args=argv, namespace=ARGS)196 return parser.parse_args(args=argv, namespace=ARGS)
208197
209198
@@ -213,5 +202,5 @@ def main(argv=None):
213 try_check(eval_status, data)202 try_check(eval_status, data)
214203
215204
216if __name__ == '__main__':205if __name__ == "__main__":
217 main(sys.argv[1:])206 main(sys.argv[1:])
diff --git a/src/files/ssacli/cron_ssacli.py b/src/files/ssacli/cron_ssacli.py
index 94c7ef7..eadd711 100755
--- a/src/files/ssacli/cron_ssacli.py
+++ b/src/files/ssacli/cron_ssacli.py
@@ -25,7 +25,7 @@ try:
25except ImportError:25except ImportError:
26 # shared lib will be under $CHARM_SOURCE_DIR/files/common during unit tests26 # shared lib will be under $CHARM_SOURCE_DIR/files/common during unit tests
27 common_libs_dir = os.path.abspath(27 common_libs_dir = os.path.abspath(
28 os.path.join(os.path.dirname(__file__), '..', 'common')28 os.path.join(os.path.dirname(__file__), "..", "common")
29 )29 )
30 if common_libs_dir not in sys.path:30 if common_libs_dir not in sys.path:
31 sys.path.append(common_libs_dir)31 sys.path.append(common_libs_dir)
@@ -37,16 +37,14 @@ except ImportError:
37 HPArgumentParser,37 HPArgumentParser,
38 )38 )
3939
40SSACLI_BIN = '/opt/smartstorageadmin/ssacli/bin/ssacli'40SSACLI_BIN = "/opt/smartstorageadmin/ssacli/bin/ssacli"
41OUTPUT_FILE = '/var/lib/nagios/ssacli.out'41OUTPUT_FILE = "/var/lib/nagios/ssacli.out"
42EXCLUDE_FILE = '/etc/nagios/ssacli.exclude.yaml'42EXCLUDE_FILE = "/etc/nagios/ssacli.exclude.yaml"
4343
4444
45def parse_args(argv=None):45def parse_args(argv=None):
46 parser = HPArgumentParser(46 parser = HPArgumentParser(
47 prog='cron_ssacli',47 prog="cron_ssacli", def_write_file=OUTPUT_FILE, def_exclude_file=EXCLUDE_FILE
48 def_write_file=OUTPUT_FILE,
49 def_exclude_file=EXCLUDE_FILE
50 )48 )
51 return parser.parse_args(args=argv)49 return parser.parse_args(args=argv)
5250
@@ -60,38 +58,46 @@ def check_array(slot):
60 physicaldrive 1:1 (box 1:bay 1, 72 GB): OK58 physicaldrive 1:1 (box 1:bay 1, 72 GB): OK
61 physicaldrive 1:2 (box 1:bay 2, 72 GB): OK59 physicaldrive 1:2 (box 1:bay 2, 72 GB): OK
62 """60 """
63 if os.path.isfile('/etc/nagios/skip-cat-hp-array.txt'):61 if os.path.isfile("/etc/nagios/skip-cat-hp-array.txt"):
64 return62 return
6563
66 cmd = (64 cmd = (
67 '{ssacli} ctrl slot={slot} ld all show status; '65 "{ssacli} ctrl slot={slot} ld all show status; "
68 '{ssacli} ctrl slot={slot} pd all show status'.format(ssacli=SSACLI_BIN, slot=slot)66 "{ssacli} ctrl slot={slot} pd all show status".format(
67 ssacli=SSACLI_BIN, slot=slot
68 )
69 )69 )
70 try:70 try:
71 result = subprocess.check_output(cmd, shell=True).decode('UTF-8')71 result = subprocess.check_output(cmd, shell=True).decode("UTF-8")
72 return _parse_array_output(result)72 return _parse_array_output(result)
73 except subprocess.CalledProcessError as e:73 except subprocess.CalledProcessError as e:
74 return (74 return (
75 'UNKNOWN Call to ssacli to show ld/pd info failed. '75 "UNKNOWN Call to ssacli to show ld/pd info failed. "
76 'Array Slot {} - Return Code {} - {}'76 "Array Slot {} - Return Code {} - {}"
77 ''.format(slot, e.returncode, e.output)77 "".format(slot, e.returncode, e.output)
78 )78 )
7979
8080
81def _parse_array_output(output):81def _parse_array_output(output):
82 innocuous_errors = re.compile(r'^Error: The specified (device|controller) '82 innocuous_errors = re.compile(
83 'does not have any (logical|physical)')83 r"^Error: The specified (device|controller) "
84 drive_status_line = re.compile(r'^\s*(logicaldrive|physicaldrive)')84 "does not have any (logical|physical)"
85 ignore_file = '/etc/nagios/ignores/ignores-cat-hp-array.txt'85 )
86 drive_status_line = re.compile(r"^\s*(logicaldrive|physicaldrive)")
87 ignore_file = "/etc/nagios/ignores/ignores-cat-hp-array.txt"
86 ignores = read_ignore_file(ignore_file)88 ignores = read_ignore_file(ignore_file)
8789
88 for line in output.splitlines():90 for line in output.splitlines():
89 line = line.strip()91 line = line.strip()
90 if not line or innocuous_errors.search(line) or not drive_status_line.search(line):92 if (
93 not line
94 or innocuous_errors.search(line)
95 or not drive_status_line.search(line)
96 ):
91 continue97 continue
92 (drivetype, number) = line.split()[:2]98 (drivetype, number) = line.split()[:2]
93 status = line.split('):')[1].lstrip().upper()99 status = line.split("):")[1].lstrip().upper()
94 if status != 'OK':100 if status != "OK":
95 err = '{} {} is "{}"'.format(drivetype, number, status)101 err = '{} {} is "{}"'.format(drivetype, number, status)
96 if not ignore(err, ignores):102 if not ignore(err, ignores):
97 return err103 return err
@@ -107,24 +113,24 @@ def check_controller(slot):
107 Cache Status: OK113 Cache Status: OK
108 Battery Status: Failed (Replace Batteries)114 Battery Status: Failed (Replace Batteries)
109 """115 """
110 if os.path.isfile('/etc/nagios/skip-cat-hp-controller.txt'):116 if os.path.isfile("/etc/nagios/skip-cat-hp-controller.txt"):
111 return117 return
112118
113 cmd = '{ssacli} ctrl slot={slot} show status'.format(ssacli=SSACLI_BIN, slot=slot)119 cmd = "{ssacli} ctrl slot={slot} show status".format(ssacli=SSACLI_BIN, slot=slot)
114 try:120 try:
115 result = subprocess.check_output(cmd, shell=True).decode('UTF-8')121 result = subprocess.check_output(cmd, shell=True).decode("UTF-8")
116 return _parse_controller_output(result)122 return _parse_controller_output(result)
117 except subprocess.CalledProcessError as e:123 except subprocess.CalledProcessError as e:
118 return (124 return (
119 'UNKNOWN Call to ssacli to show ld/pd info failed. '125 "UNKNOWN Call to ssacli to show ld/pd info failed. "
120 'Array Slot {} - Return Code {} - {}'126 "Array Slot {} - Return Code {} - {}"
121 ''.format(slot, e.returncode, e.output)127 "".format(slot, e.returncode, e.output)
122 )128 )
123129
124130
125def _parse_controller_output(output):131def _parse_controller_output(output):
126 controller = "Unknown"132 controller = "Unknown"
127 ignore_file = '/etc/nagios/ignores/ignores-cat-hp-controller.txt'133 ignore_file = "/etc/nagios/ignores/ignores-cat-hp-controller.txt"
128 ignores = read_ignore_file(ignore_file)134 ignores = read_ignore_file(ignore_file)
129 for line in output.splitlines():135 for line in output.splitlines():
130 line = line.strip()136 line = line.strip()
@@ -151,7 +157,7 @@ def main():
151157
152 slots = get_hp_controller_slots()158 slots = get_hp_controller_slots()
153 if not slots:159 if not slots:
154 msg = 'OK: no controller/array found to check'160 msg = "OK: no controller/array found to check"
155 exit = 0161 exit = 0
156162
157 errors = []163 errors = []
@@ -160,19 +166,19 @@ def main():
160 errors += check_array(slot)166 errors += check_array(slot)
161167
162 if len(errors) > 0:168 if len(errors) > 0:
163 msg = 'CRIT {} error(s): {}'.format(len(errors), ' - '.join(errors))169 msg = "CRIT {} error(s): {}".format(len(errors), " - ".join(errors))
164 exit = 2170 exit = 2
165 else:171 else:
166 msg = 'OK No errors found'172 msg = "OK No errors found"
167 exit = 0173 exit = 0
168174
169 if ARGS.write:175 if ARGS.write:
170 with open(ARGS.write, 'w') as f:176 with open(ARGS.write, "w") as f:
171 f.write(msg)177 f.write(msg)
172 else:178 else:
173 print(msg)179 print(msg)
174 sys.exit(exit)180 sys.exit(exit)
175181
176182
177if __name__ == '__main__':183if __name__ == "__main__":
178 main()184 main()
diff --git a/src/lib/hwhealth/discovery/lshw.py b/src/lib/hwhealth/discovery/lshw.py
index c2f6653..1e52304 100644
--- a/src/lib/hwhealth/discovery/lshw.py
+++ b/src/lib/hwhealth/discovery/lshw.py
@@ -7,48 +7,45 @@ from charmhelpers.core import hookenv
77
88
9class Hardware(object):9class Hardware(object):
10 def __init__(self, filename='/var/run/hw_health_lshw.json'):10 def __init__(self, filename="/var/run/hw_health_lshw.json"):
11 self.__filename = filename11 self.__filename = filename
12 self._lshw = self.__load_hwinfo()12 self._lshw = self.__load_hwinfo()
1313
14 def __load_hwinfo(self):14 def __load_hwinfo(self):
15 try:15 try:
16 if os.path.exists(self.__filename):16 if os.path.exists(self.__filename):
17 with open(self.__filename, 'r') as fd:17 with open(self.__filename, "r") as fd:
18 hwinfo = json.load(fd)18 hwinfo = json.load(fd)
19 else:19 else:
20 output = subprocess.check_output(['lshw', '-json'])20 output = subprocess.check_output(["lshw", "-json"])
21 # Note(aluria): py35 does not support extra args on21 # Note(aluria): py35 does not support extra args on
22 # subprocess.check_output22 # subprocess.check_output
23 output_str = output.decode(errors='ignore')23 output_str = output.decode(errors="ignore")
24 hwinfo = json.loads(output_str)24 hwinfo = json.loads(output_str)
25 with open(self.__filename, 'w') as fd:25 with open(self.__filename, "w") as fd:
26 fd.write(output_str)26 fd.write(output_str)
2727
28 return hwinfo28 return hwinfo
29 except PermissionError as error:29 except PermissionError as error:
30 hookenv.log('lshw io error: {}'.format(error),30 hookenv.log("lshw io error: {}".format(error), hookenv.ERROR)
31 hookenv.ERROR)
32 return {}31 return {}
33 except subprocess.CalledProcessError as error:32 except subprocess.CalledProcessError as error:
34 hookenv.log('lshw subprocess error: {}'.format(error),33 hookenv.log("lshw subprocess error: {}".format(error), hookenv.ERROR)
35 hookenv.ERROR)
36 return {}34 return {}
37 except json.JSONDecodeError as error:35 except json.JSONDecodeError as error:
38 hookenv.log('lshw json error: {}'.format(error),36 hookenv.log("lshw json error: {}".format(error), hookenv.ERROR)
39 hookenv.ERROR)
40 return {}37 return {}
4138
42 @property39 @property
43 def get_system(self):40 def get_system(self):
44 """Helper to get vendor info retrieved via actions41 """Helper to get vendor info retrieved via actions
45 """42 """
46 keys = 'id description vendor product version serial'.split()43 keys = "id description vendor product version serial".split()
47 sysinfo = {}44 sysinfo = {}
48 for k in keys:45 for k in keys:
49 v = self._lshw.get(k)46 v = self._lshw.get(k)
50 if k == 'id':47 if k == "id":
51 k = 'hostname'48 k = "hostname"
52 sysinfo.update({k: v})49 sysinfo.update({k: v})
53 return sysinfo50 return sysinfo
5451
@@ -56,22 +53,20 @@ class Hardware(object):
56 def get_motherboard(self):53 def get_motherboard(self):
57 """Helper to get vendor info retrieved via actions54 """Helper to get vendor info retrieved via actions
58 """55 """
59 keys = 'description vendor product version serial'.split()56 keys = "description vendor product version serial".split()
60 buses = []57 buses = []
61 for child in self._lshw.get('children', [{}]):58 for child in self._lshw.get("children", [{}]):
62 if child.get('class') != 'bus':59 if child.get("class") != "bus":
63 continue60 continue
64 buses.append(dict([(k, child.get(k)) for k in keys]))61 buses.append(dict([(k, child.get(k)) for k in keys]))
65 return buses62 return buses
6663
67 def _get_inspect_bridges(self, bridge_item, bridge_class='storage'):64 def _get_inspect_bridges(self, bridge_item, bridge_class="storage"):
68 bridge_class_items = []65 bridge_class_items = []
69 for item in bridge_item.get('children', [{}]):66 for item in bridge_item.get("children", [{}]):
70 if item.get('class', '') == 'bridge':67 if item.get("class", "") == "bridge":
71 bridge_class_items.extend(68 bridge_class_items.extend(self._get_inspect_bridges(item, bridge_class))
72 self._get_inspect_bridges(item, bridge_class)69 elif item.get("class", "") == bridge_class:
73 )
74 elif item.get('class', '') == bridge_class:
75 bridge_class_items.append(item)70 bridge_class_items.append(item)
76 return bridge_class_items71 return bridge_class_items
7772
@@ -85,18 +80,16 @@ class Hardware(object):
85 """80 """
86 storage = []81 storage = []
87 # system -> bus -> bridge -> storage82 # system -> bus -> bridge -> storage
88 for bus in self._lshw.get('children', [{}]):83 for bus in self._lshw.get("children", [{}]):
89 if bus.get('class', '') != 'bus':84 if bus.get("class", "") != "bus":
90 continue85 continue
91 for bridge in bus.get('children', [{}]):86 for bridge in bus.get("children", [{}]):
92 if bridge.get('class', '') != 'bridge':87 if bridge.get("class", "") != "bridge":
93 continue88 continue
94 for item in bridge.get('children', [{}]):89 for item in bridge.get("children", [{}]):
95 if item.get('class', '') == 'bridge':90 if item.get("class", "") == "bridge":
96 storage.extend(91 storage.extend(self._get_inspect_bridges(item, "storage"))
97 self._get_inspect_bridges(item, 'storage')92 elif item.get("class", "") == "storage":
98 )
99 elif item.get('class', '') == 'storage':
100 storage.append(item)93 storage.append(item)
101 return storage94 return storage
10295
@@ -108,15 +101,15 @@ class Hardware(object):
108 businfo and linux driver The aim of the function is to easily parse101 businfo and linux driver The aim of the function is to easily parse
109 products to detect which tool(s) need to be used.102 products to detect which tool(s) need to be used.
110 """103 """
111 keys = 'vendor product businfo'.split()104 keys = "vendor product businfo".split()
112 config_keys = ['driver']105 config_keys = ["driver"]
113 storage = []106 storage = []
114 for item in self._get_storage_class:107 for item in self._get_storage_class:
115 storage_item = dict([(k, item.get(k)) for k in keys])108 storage_item = dict([(k, item.get(k)) for k in keys])
116 storage_item.update(109 storage_item.update(
117 dict([(k, item.get('configuration', {}).get(k))110 dict([(k, item.get("configuration", {}).get(k)) for k in config_keys])
118 for k in config_keys]))111 )
119 storage_item.update({'has_children': 'children' in item})112 storage_item.update({"has_children": "children" in item})
120 storage.append(storage_item)113 storage.append(storage_item)
121114
122 return storage115 return storage
@@ -133,14 +126,14 @@ class Hardware(object):
133 The aim of the function is to easily parse products to detect which126 The aim of the function is to easily parse products to detect which
134 tool(s) need to be used.127 tool(s) need to be used.
135 """128 """
136 keys = 'product serial businfo physid dev size logicalname'.split()129 keys = "product serial businfo physid dev size logicalname".split()
137 disks = []130 disks = []
138 for item in self._get_storage_class:131 for item in self._get_storage_class:
139 for child in item.get('children', [{}]):132 for child in item.get("children", [{}]):
140 if child.get('class', '') != 'disk':133 if child.get("class", "") != "disk":
141 continue134 continue
142 disk = dict([(k, child.get(k)) for k in keys])135 disk = dict([(k, child.get(k)) for k in keys])
143 disk.update({'storage_parent': item.get('product')})136 disk.update({"storage_parent": item.get("product")})
144 disks.append(disk)137 disks.append(disk)
145 return disks138 return disks
146139
@@ -153,28 +146,28 @@ class Hardware(object):
153 The aim of the function is to easily parse products to detect which146 The aim of the function is to easily parse products to detect which
154 tool(s) need to be used.147 tool(s) need to be used.
155 """148 """
156 keys = 'vendor product businfo logicalname serial'.split()149 keys = "vendor product businfo logicalname serial".split()
157 config_keys = 'driver driverversion firmware speed'.split()150 config_keys = "driver driverversion firmware speed".split()
158 nics = []151 nics = []
159 # system -> bus -> bridge -> network152 # system -> bus -> bridge -> network
160 for bus in self._lshw.get('children', [{}]):153 for bus in self._lshw.get("children", [{}]):
161 if bus.get('class', '') != 'bus':154 if bus.get("class", "") != "bus":
162 continue155 continue
163 for bridge in bus.get('children', [{}]):156 for bridge in bus.get("children", [{}]):
164 if bridge.get('class', '') != 'bridge':157 if bridge.get("class", "") != "bridge":
165 continue158 continue
166 for item in bridge.get('children', [{}]):159 for item in bridge.get("children", [{}]):
167 if item.get('class', '') == 'bridge':160 if item.get("class", "") == "bridge":
168 nics.extend(self._get_inspect_bridges(item, 'network'))161 nics.extend(self._get_inspect_bridges(item, "network"))
169 elif item.get('class', '') == 'network':162 elif item.get("class", "") == "network":
170 nics.append(item)163 nics.append(item)
171164
172 nics_filtered = []165 nics_filtered = []
173 for nic in nics:166 for nic in nics:
174 nic_item = dict([(k, nic.get(k)) for k in keys])167 nic_item = dict([(k, nic.get(k)) for k in keys])
175 nic_item.update(168 nic_item.update(
176 dict([(k, nic.get('configuration', {}).get(k))169 dict([(k, nic.get("configuration", {}).get(k)) for k in config_keys])
177 for k in config_keys]))170 )
178 nics_filtered.append(nic_item)171 nics_filtered.append(nic_item)
179 return nics_filtered172 return nics_filtered
180173
@@ -182,60 +175,67 @@ class Hardware(object):
182 def formatted_system_info(self):175 def formatted_system_info(self):
183 ctxt = self.get_system176 ctxt = self.get_system
184 return (177 return (
185 '{description}: vendor[{vendor}], product_name[{product}], '178 "{description}: vendor[{vendor}], product_name[{product}], "
186 'version[{version}], serial[{serial}], hostname[{hostname}]'179 "version[{version}], serial[{serial}], hostname[{hostname}]"
187 ).format(**ctxt)180 ).format(**ctxt)
188181
189 @property182 @property
190 def formatted_motherboard_info(self):183 def formatted_motherboard_info(self):
191 return '\n'.join([184 return "\n".join(
192 '{description}: vendor[{vendor}], product_name[{product}], '185 [
193 'version[{version}], serial[{serial}]'.format(**ctxt)186 "{description}: vendor[{vendor}], product_name[{product}], "
194 for ctxt in self.get_motherboard])187 "version[{version}], serial[{serial}]".format(**ctxt)
188 for ctxt in self.get_motherboard
189 ]
190 )
195191
196 @property192 @property
197 def formatted_storage_class_info(self):193 def formatted_storage_class_info(self):
198 LINE = ('driver[{driver}], businfo[{businfo}], '194 LINE = "driver[{driver}], businfo[{businfo}], has_children[{has_children}]"
199 'has_children[{has_children}]')
200 ctxts = []195 ctxts = []
201 for ctxt in self.get_storage_class_info:196 for ctxt in self.get_storage_class_info:
202 if ctxt.get('vendor') and ctxt.get('product'):197 if ctxt.get("vendor") and ctxt.get("product"):
203 tmpl = ('Storage class: vendor[{vendor}],'198 tmpl = (
204 'product_name[{product}], ') + LINE199 "Storage class: vendor[{vendor}],product_name[{product}], "
200 ) + LINE
205 else:201 else:
206 tmpl = 'Storage class: {}'.format(LINE)202 tmpl = "Storage class: {}".format(LINE)
207 ctxts.append(tmpl.format(**ctxt))203 ctxts.append(tmpl.format(**ctxt))
208204
209 if ctxts:205 if ctxts:
210 return '\n'.join(ctxts)206 return "\n".join(ctxts)
211207
212 @property208 @property
213 def formatted_disk_class_info(self):209 def formatted_disk_class_info(self):
214 return '\n'.join([210 return "\n".join(
215 'Disk class: ld[{logicalname}], dev[{dev}], physid[{physid}], '211 [
216 'businfo[{businfo}], product_name[{product}], serial[{serial}], '212 "Disk class: ld[{logicalname}], dev[{dev}], physid[{physid}], "
217 'size[{size}], storage_parent[{storage_parent}]'.format(**ctxt)213 "businfo[{businfo}], product_name[{product}], serial[{serial}], "
218 for ctxt in self.get_disk_class_info214 "size[{size}], storage_parent[{storage_parent}]".format(**ctxt)
219 ])215 for ctxt in self.get_disk_class_info
216 ]
217 )
220218
221 @property219 @property
222 def formatted_network_class_info(self):220 def formatted_network_class_info(self):
223 return '\n'.join([221 return "\n".join(
224 'NIC: iface[{logicalname}], businfo[{businfo}], vendor[{vendor}]'222 [
225 ', product_name[{product}], firmware[{firmware}], driver[{driver}'223 "NIC: iface[{logicalname}], businfo[{businfo}], vendor[{vendor}]"
226 ', {driverversion}], serial[{serial}]'224 ", product_name[{product}], firmware[{firmware}], driver[{driver}"
227 ', speed[{speed}]'.format(**ctxt)225 ", {driverversion}], serial[{serial}]"
228 for ctxt in self.get_network_class_info226 ", speed[{speed}]".format(**ctxt)
229 ])227 for ctxt in self.get_network_class_info
230228 ]
231229 )
232if __name__ == '__main__':230
231
232if __name__ == "__main__":
233 hw = Hardware()233 hw = Hardware()
234 print(hw.formatted_system_info)234 print(hw.formatted_system_info)
235 print(hw.formatted_motherboard_info)235 print(hw.formatted_motherboard_info)
236 print('\n== get_storage_classes')236 print("\n== get_storage_classes")
237 print(hw.formatted_storage_class_info)237 print(hw.formatted_storage_class_info)
238 print('== get_disk_classes')238 print("== get_disk_classes")
239 print(hw.formatted_disk_class_info)239 print(hw.formatted_disk_class_info)
240 print('\n== get_network_class_info')240 print("\n== get_network_class_info")
241 print(hw.formatted_network_class_info)241 print(hw.formatted_network_class_info)
diff --git a/src/lib/hwhealth/discovery/supported_vendors.py b/src/lib/hwhealth/discovery/supported_vendors.py
index ef37a5a..459f078 100644
--- a/src/lib/hwhealth/discovery/supported_vendors.py
+++ b/src/lib/hwhealth/discovery/supported_vendors.py
@@ -2,37 +2,32 @@
2from hwhealth import tools2from hwhealth import tools
33
4SUPPORTED_STORAGE = {4SUPPORTED_STORAGE = {
5 'LSI Logic / Symbios Logic': {5 "LSI Logic / Symbios Logic": {
6 'SAS2308 PCI-Express Fusion-MPT SAS-2': tools.Sas2Ircu,6 "SAS2308 PCI-Express Fusion-MPT SAS-2": tools.Sas2Ircu,
7 'SAS3008 PCI-Express Fusion-MPT SAS-3': tools.Sas3Ircu,7 "SAS3008 PCI-Express Fusion-MPT SAS-3": tools.Sas3Ircu,
8 'MegaRAID SAS-3 3108 [Invader]': tools.MegaCLI,8 "MegaRAID SAS-3 3108 [Invader]": tools.MegaCLI,
9 },9 },
10 # 'Mellanox Technologies': {10 # 'Mellanox Technologies': {
11 # 'MT27710 Family [ConnectX-4 Lx]': lambda: 'mlxconfig',11 # 'MT27710 Family [ConnectX-4 Lx]': lambda: 'mlxconfig',
12 # 'MT27700 Family [ConnectX-4]': lambda: 'mlxconfig',12 # 'MT27700 Family [ConnectX-4]': lambda: 'mlxconfig',
13 # },13 # },
14 'Intel Corporation': {14 "Intel Corporation": {"PCIe Data Center SSD": tools.Nvme},
15 'PCIe Data Center SSD': tools.Nvme,15 "Samsung Electronics Co Ltd": {
16 "NVMe SSD Controller SM961/PM961": tools.Nvme,
17 "NVMe SSD Controller 172Xa/172Xb": tools.Nvme,
16 },18 },
17 'Samsung Electronics Co Ltd': {19 "Hewlett-Packard Company": {
18 'NVMe SSD Controller SM961/PM961': tools.Nvme,20 "Smart Array Gen9 Controllers": tools.SsaCli,
19 'NVMe SSD Controller 172Xa/172Xb': tools.Nvme,21 "Smart Storage PQI 12G SAS/PCIe 3": tools.ILOrest,
20 },
21 'Hewlett-Packard Company': {
22 'Smart Array Gen9 Controllers': tools.SsaCli,
23 'Smart Storage PQI 12G SAS/PCIe 3': tools.ILOrest,
24 },22 },
25}23}
2624
27SUPPORTED_SYSTEMS = {25SUPPORTED_SYSTEMS = {
28 'HPE': {26 "HPE": {
29 # tools.HpLog, # not sure if this works on gen10+27 # tools.HpLog, # not sure if this works on gen10+
30 tools.ILOrest,28 tools.ILOrest,
31 },29 },
32 'HP': {30 "HP": {tools.HpLog, tools.SsaCli},
33 tools.HpLog,
34 tools.SsaCli,
35 }
36}31}
3732
38SUPPORTED_DRIVERS = {33SUPPORTED_DRIVERS = {
@@ -41,5 +36,5 @@ SUPPORTED_DRIVERS = {
41 # 'apt': 'hioa',36 # 'apt': 'hioa',
42 # 'tool': lambda: 'hio_info',37 # 'tool': lambda: 'hio_info',
43 # },38 # },
44 'nvme': tools.Nvme,39 "nvme": tools.Nvme,
45}40}
diff --git a/src/lib/hwhealth/hwdiscovery.py b/src/lib/hwhealth/hwdiscovery.py
index c23ee13..fe27403 100644
--- a/src/lib/hwhealth/hwdiscovery.py
+++ b/src/lib/hwhealth/hwdiscovery.py
@@ -10,23 +10,23 @@ from hwhealth.discovery.lshw import Hardware
10from hwhealth.discovery.supported_vendors import (10from hwhealth.discovery.supported_vendors import (
11 SUPPORTED_STORAGE,11 SUPPORTED_STORAGE,
12 SUPPORTED_DRIVERS,12 SUPPORTED_DRIVERS,
13 SUPPORTED_SYSTEMS13 SUPPORTED_SYSTEMS,
14)14)
1515
16from charmhelpers.core import hookenv16from charmhelpers.core import hookenv
1717
1818
19def get_tools(manufacturer='auto'):19def get_tools(manufacturer="auto"):
20 """Return list of tool classes relevent for the current hardware.20 """Return list of tool classes relevent for the current hardware.
2121
22 In testing, we set manufacturer = test in order to test all tools classes.22 In testing, we set manufacturer = test in order to test all tools classes.
23 Filtering added for known bad tools that don't work on all series combinations.23 Filtering added for known bad tools that don't work on all series combinations.
24 """24 """
25 if manufacturer == 'test':25 if manufacturer == "test":
26 # Return all possible tools to aid testing26 # Return all possible tools to aid testing
27 storage_tools = {tool27 storage_tools = {
28 for vendor in SUPPORTED_STORAGE.values()28 tool for vendor in SUPPORTED_STORAGE.values() for tool in vendor.values()
29 for tool in vendor.values()}29 }
30 # Some system vendors have multiple tools, have to iterate sets30 # Some system vendors have multiple tools, have to iterate sets
31 system_tools = set(chain.from_iterable(SUPPORTED_SYSTEMS.values()))31 system_tools = set(chain.from_iterable(SUPPORTED_SYSTEMS.values()))
32 driver_tools = set(SUPPORTED_DRIVERS.values())32 driver_tools = set(SUPPORTED_DRIVERS.values())
@@ -35,7 +35,7 @@ def get_tools(manufacturer='auto'):
35 tool for tool in all_tools if tool.is_series_supported()35 tool for tool in all_tools if tool.is_series_supported()
36 )36 )
37 return series_filtered_tools37 return series_filtered_tools
38 elif manufacturer == 'auto':38 elif manufacturer == "auto":
39 return _get_tools()39 return _get_tools()
40 else:40 else:
41 raise NotImplementedError41 raise NotImplementedError
@@ -46,41 +46,43 @@ def _get_tools():
46 hwinfo = Hardware()46 hwinfo = Hardware()
47 toolset = set()47 toolset = set()
48 for storage in hwinfo.get_storage_class_info:48 for storage in hwinfo.get_storage_class_info:
49 vendor = storage.get('vendor')49 vendor = storage.get("vendor")
50 product = storage.get('product')50 product = storage.get("product")
51 tool = SUPPORTED_STORAGE.get(vendor, {}).get(product)51 tool = SUPPORTED_STORAGE.get(vendor, {}).get(product)
52 if isinstance(tool, list) or isinstance(tool, set):52 if isinstance(tool, list) or isinstance(tool, set):
53 toolset.update(tool)53 toolset.update(tool)
54 elif tool:54 elif tool:
55 toolset.add(tool)55 toolset.add(tool)
56 else:56 else:
57 hookenv.log('Product not supported: [{}][{}]'57 hookenv.log(
58 ''.format(vendor, product), hookenv.DEBUG)58 "Product not supported: [{}][{}]".format(vendor, product),
59 hookenv.DEBUG,
60 )
5961
60 driver = storage.get('driver')62 driver = storage.get("driver")
61 if driver:63 if driver:
62 if driver in SUPPORTED_DRIVERS:64 if driver in SUPPORTED_DRIVERS:
63 toolset.add(SUPPORTED_DRIVERS[driver])65 toolset.add(SUPPORTED_DRIVERS[driver])
64 continue66 continue
65 hookenv.log('Driver not supported: {}'.format(driver),67 hookenv.log("Driver not supported: {}".format(driver), hookenv.DEBUG)
66 hookenv.DEBUG)
6768
68 # SW RAID?69 # SW RAID?
69 if _supports_mdadm():70 if _supports_mdadm():
70 toolset.add(tools.Mdadm)71 toolset.add(tools.Mdadm)
7172
72 if hookenv.config('enable_ipmi'):73 if hookenv.config("enable_ipmi"):
73 toolset.add(tools.Ipmi)74 toolset.add(tools.Ipmi)
7475
75 system_vendor = hwinfo.get_system.get('vendor')76 system_vendor = hwinfo.get_system.get("vendor")
76 tool = SUPPORTED_SYSTEMS.get(system_vendor)77 tool = SUPPORTED_SYSTEMS.get(system_vendor)
77 if isinstance(tool, list) or isinstance(tool, set):78 if isinstance(tool, list) or isinstance(tool, set):
78 toolset.update(tool)79 toolset.update(tool)
79 elif tool:80 elif tool:
80 toolset.add(tool)81 toolset.add(tool)
81 else:82 else:
82 hookenv.log('System vendor not supported: {}'.format(system_vendor),83 hookenv.log(
83 hookenv.DEBUG)84 "System vendor not supported: {}".format(system_vendor), hookenv.DEBUG
85 )
8486
85 executed_toolset = set([tool() for tool in toolset if tool.is_series_supported])87 executed_toolset = set([tool() for tool in toolset if tool.is_series_supported])
86 return executed_toolset88 return executed_toolset
@@ -91,27 +93,24 @@ def _supports_mdadm():
9193
92 Returns True when the first one is found; otherwise, it returns False)94 Returns True when the first one is found; otherwise, it returns False)
93 """95 """
94 if os.path.exists('/sbin/mdadm'):96 if os.path.exists("/sbin/mdadm"):
95 try:97 try:
96 devices_raw = subprocess.check_output(98 devices_raw = subprocess.check_output(["/sbin/mdadm", "--detail", "--scan"])
97 ['/sbin/mdadm', '--detail', '--scan']99 devices_re = re.compile(r"^ARRAY\s+(\S+) ")
98 )
99 devices_re = re.compile(r'^ARRAY\s+(\S+) ')
100 for line in devices_raw.splitlines():100 for line in devices_raw.splitlines():
101 line = line.decode().strip()101 line = line.decode().strip()
102 raid_dev = devices_re.search(line)102 raid_dev = devices_re.search(line)
103 if raid_dev:103 if raid_dev:
104 hookenv.log("Found md raid array {}"104 hookenv.log("Found md raid array {}".format(raid_dev.group(1)))
105 "".format(raid_dev.group(1)))
106 return True105 return True
107 except Exception as e:106 except Exception as e:
108 hookenv.log("mdadm scan failed with {}".format(e))107 hookenv.log("mdadm scan failed with {}".format(e))
109 return False108 return False
110109
111110
112if __name__ == '__main__':111if __name__ == "__main__":
113 toolset = get_tools()112 toolset = get_tools()
114 if not toolset:113 if not toolset:
115 print('No RAID')114 print("No RAID")
116 else:115 else:
117 print(toolset)116 print(toolset)
diff --git a/src/lib/hwhealth/tools.py b/src/lib/hwhealth/tools.py
index e90cb6b..da3615d 100644
--- a/src/lib/hwhealth/tools.py
+++ b/src/lib/hwhealth/tools.py
@@ -24,12 +24,14 @@ from charms import apt
24class JujuResourceNotFound(Exception):24class JujuResourceNotFound(Exception):
25 """Resource needed but not attached25 """Resource needed but not attached
26 """26 """
27
27 pass28 pass
2829
2930
30class ToolError(Exception):31class ToolError(Exception):
31 """Allows a dict to be shared with try-except32 """Allows a dict to be shared with try-except
32 """33 """
34
33 def __init__(self, keymap):35 def __init__(self, keymap):
34 self._keymap = keymap36 self._keymap = keymap
3537
@@ -41,16 +43,18 @@ class ToolError(Exception):
41class ToolChecksumError(ToolError):43class ToolChecksumError(ToolError):
42 """Resource does not match a whitelisted checksum44 """Resource does not match a whitelisted checksum
43 """45 """
46
44 pass47 pass
4548
4649
47class ToolNotFound(ToolError):50class ToolNotFound(ToolError):
48 """Resource found (zipfile) but does not contain a needed binary51 """Resource found (zipfile) but does not contain a needed binary
49 """52 """
53
50 pass54 pass
5155
5256
53class Tool():57class Tool:
54 """An abstract class representing a "tool".58 """An abstract class representing a "tool".
5559
56 The idea is to delegate install/configure duties to specific per-tool60 The idea is to delegate install/configure duties to specific per-tool
@@ -59,21 +63,22 @@ class Tool():
59 Every tool should implement its own internal logic regarding how to be63 Every tool should implement its own internal logic regarding how to be
60 installed, configured, and removed.64 installed, configured, and removed.
61 """65 """
62 CROND_DIR = '/etc/cron.d'66
67 CROND_DIR = "/etc/cron.d"
63 CRONJOB_SCRIPT_MODE = 0o10075568 CRONJOB_SCRIPT_MODE = 0o100755
64 CRONJOB_SCRIPT_UID = 069 CRONJOB_SCRIPT_UID = 0
65 CRONJOB_SCRIPT_GID = 070 CRONJOB_SCRIPT_GID = 0
66 CRONJOB_OUTPUT_DIR = '/var/lib/nagios'71 CRONJOB_OUTPUT_DIR = "/var/lib/nagios"
67 NRPE_PLUGINS_DIR = '/usr/local/lib/nagios/plugins'72 NRPE_PLUGINS_DIR = "/usr/local/lib/nagios/plugins"
68 NRPE_PLUGINS_MODE = 0o10075573 NRPE_PLUGINS_MODE = 0o100755
69 NRPE_PLUGINS_UID = 074 NRPE_PLUGINS_UID = 0
70 NRPE_PLUGINS_GID = 075 NRPE_PLUGINS_GID = 0
71 SUDOERS_DIR = '/etc/sudoers.d'76 SUDOERS_DIR = "/etc/sudoers.d"
72 SUDOERS_MODE = 0o10044077 SUDOERS_MODE = 0o100440
73 SUDOERS_UID = 078 SUDOERS_UID = 0
74 SUDOERS_GID = 079 SUDOERS_GID = 0
75 SUPPORTED_SERIES = ['xenial', 'bionic', 'focal']80 SUPPORTED_SERIES = ["xenial", "bionic", "focal"]
76 TOOLS_DIR = '/usr/local/bin'81 TOOLS_DIR = "/usr/local/bin"
77 TOOLS_MODE = 0o10075582 TOOLS_MODE = 0o100755
78 TOOLS_UID = 083 TOOLS_UID = 0
79 TOOLS_GID = 084 TOOLS_GID = 0
@@ -81,7 +86,7 @@ class Tool():
81 def __init__(86 def __init__(
82 self,87 self,
83 shortname=None,88 shortname=None,
84 nrpe_opts='',89 nrpe_opts="",
85 nrpe_script=None,90 nrpe_script=None,
86 nrpe_script_dir=None,91 nrpe_script_dir=None,
87 cron_script=None,92 cron_script=None,
@@ -90,25 +95,21 @@ class Tool():
90 ):95 ):
91 self._nagios_hostname = nrpe.get_nagios_hostname()96 self._nagios_hostname = nrpe.get_nagios_hostname()
92 self._nrpe_opts = nrpe_opts97 self._nrpe_opts = nrpe_opts
93 self._shortname = (shortname if shortname98 self._shortname = shortname if shortname else self.__class__.__name__.lower()
94 else self.__class__.__name__.lower())99 self._files_dir = os.path.join(hookenv.charm_dir(), "files", self._shortname)
95 self._files_dir = os.path.join(hookenv.charm_dir(),100 self._nrpe_script = (
96 'files',101 nrpe_script if nrpe_script else "check_{}.py".format(self._shortname)
97 self._shortname)102 )
98 self._nrpe_script = (nrpe_script if nrpe_script103 self._nrpe_script_dir = nrpe_script_dir if nrpe_script_dir else self._files_dir
99 else 'check_{}.py'.format(self._shortname))104 self._cron_script = (
100 self._nrpe_script_dir = (nrpe_script_dir if nrpe_script_dir105 cron_script if cron_script else "cron_{}.py".format(self._shortname)
101 else self._files_dir)106 )
102 self._cron_script = (cron_script if cron_script107 self._cron_script_dir = cron_script_dir if cron_script_dir else self._files_dir
103 else 'cron_{}.py'.format(self._shortname))108 self._templates_dir = os.path.join(
104 self._cron_script_dir = (cron_script_dir if cron_script_dir109 hookenv.charm_dir(), "templates", self._shortname
105 else self._files_dir)110 )
106 self._templates_dir = os.path.join(hookenv.charm_dir(),111 self._common_libs_dir = os.path.join(hookenv.charm_dir(), "files/common")
107 'templates',112 self._common_libs = ["hw_health_lib.py"]
108 self._shortname)
109 self._common_libs_dir = os.path.join(hookenv.charm_dir(),
110 'files/common')
111 self._common_libs = ['hw_health_lib.py']
112 self._cron_script_args = cron_script_args113 self._cron_script_args = cron_script_args
113114
114 def _install_nrpe_plugin(self):115 def _install_nrpe_plugin(self):
@@ -117,9 +118,9 @@ class Tool():
117 os.chmod(dst, self.NRPE_PLUGINS_MODE)118 os.chmod(dst, self.NRPE_PLUGINS_MODE)
118 os.chown(dst, uid=self.NRPE_PLUGINS_UID, gid=self.NRPE_PLUGINS_GID)119 os.chown(dst, uid=self.NRPE_PLUGINS_UID, gid=self.NRPE_PLUGINS_GID)
119 hookenv.log(120 hookenv.log(
120 'NRPE script for tool [{}] installed at as {}'121 "NRPE script for tool [{}] installed at as {}"
121 ''.format(self._shortname, dst),122 "".format(self._shortname, dst),
122 hookenv.DEBUG123 hookenv.DEBUG,
123 )124 )
124 return dst125 return dst
125126
@@ -131,9 +132,9 @@ class Tool():
131 os.chmod(dst, self.NRPE_PLUGINS_MODE)132 os.chmod(dst, self.NRPE_PLUGINS_MODE)
132 os.chown(dst, uid=self.NRPE_PLUGINS_UID, gid=self.NRPE_PLUGINS_GID)133 os.chown(dst, uid=self.NRPE_PLUGINS_UID, gid=self.NRPE_PLUGINS_GID)
133 hookenv.log(134 hookenv.log(
134 'Common Library {} for tool [{}] installed at as {}'135 "Common Library {} for tool [{}] installed at as {}"
135 ''.format(lib, self._shortname, dst),136 "".format(lib, self._shortname, dst),
136 hookenv.DEBUG137 hookenv.DEBUG,
137 )138 )
138 dsts.append(dst)139 dsts.append(dst)
139 return dsts140 return dsts
@@ -144,48 +145,42 @@ class Tool():
144 return145 return
145 os.remove(plugin_path)146 os.remove(plugin_path)
146 hookenv.log(147 hookenv.log(
147 'deleted NRPE script for tool [{}]'.format(self._shortname),148 "deleted NRPE script for tool [{}]".format(self._shortname), hookenv.DEBUG
148 hookenv.DEBUG
149 )149 )
150150
151 def configure_nrpe_check(self, nrpe_setup):151 def configure_nrpe_check(self, nrpe_setup):
152 cmd = ' '.join([os.path.basename(self._nrpe_script), self._nrpe_opts])152 cmd = " ".join([os.path.basename(self._nrpe_script), self._nrpe_opts])
153 nrpe_setup.add_check(153 nrpe_setup.add_check(
154 shortname=self._shortname,154 shortname=self._shortname,
155 description='{} Hardware Health'.format(self._shortname),155 description="{} Hardware Health".format(self._shortname),
156 check_cmd=cmd,156 check_cmd=cmd,
157 )157 )
158 hookenv.log(158 hookenv.log(
159 'configured NRPE check for tool [{}]'.format(self._shortname),159 "configured NRPE check for tool [{}]".format(self._shortname), hookenv.DEBUG
160 hookenv.DEBUG
161 )160 )
162161
163 def remove_nrpe_check(self):162 def remove_nrpe_check(self):
164 nrpe_setup = nrpe.NRPE(hostname=self._nagios_hostname, primary=False)163 nrpe_setup = nrpe.NRPE(hostname=self._nagios_hostname, primary=False)
165 cmd = ' '.join([self._nrpe_script, self._nrpe_opts])164 cmd = " ".join([self._nrpe_script, self._nrpe_opts])
166 nrpe_setup.remove_check(165 nrpe_setup.remove_check(shortname=self._shortname, check_cmd=cmd)
167 shortname=self._shortname,
168 check_cmd=cmd
169 )
170 nrpe_setup.write()166 nrpe_setup.write()
171 hookenv.log(167 hookenv.log(
172 'removed NRPE check for tool [{}]'.format(self._shortname),168 "removed NRPE check for tool [{}]".format(self._shortname), hookenv.DEBUG
173 hookenv.DEBUG
174 )169 )
175170
176 def install(self):171 def install(self):
177 self._install_common_libs()172 self._install_common_libs()
178 self._install_nrpe_plugin()173 self._install_nrpe_plugin()
179 hookenv.log('Installed tool [{}]'.format(self._shortname))174 hookenv.log("Installed tool [{}]".format(self._shortname))
180175
181 def remove(self):176 def remove(self):
182 self.remove_nrpe_check()177 self.remove_nrpe_check()
183 self._remove_nrpe_plugin()178 self._remove_nrpe_plugin()
184 hookenv.log('Removed tool [{}]'.format(self._shortname))179 hookenv.log("Removed tool [{}]".format(self._shortname))
185180
186 @classmethod181 @classmethod
187 def is_series_supported(cls):182 def is_series_supported(cls):
188 series = lsb_release()['DISTRIB_CODENAME']183 series = lsb_release()["DISTRIB_CODENAME"]
189184
190 # BUG(lp#1890652) The following works around xenial layer-apt bug during test185 # BUG(lp#1890652) The following works around xenial layer-apt bug during test
191 if (186 if (
@@ -197,7 +192,7 @@ class Tool():
197192
198 return series in cls.SUPPORTED_SERIES193 return series in cls.SUPPORTED_SERIES
199194
200 def _install_cronjob(self, cron_user='root'):195 def _install_cronjob(self, cron_user="root"):
201 assert self._cron_script is not None196 assert self._cron_script is not None
202197
203 # Copy the cronjob script to the nagios plugins directory198 # Copy the cronjob script to the nagios plugins directory
@@ -206,17 +201,15 @@ class Tool():
206 os.chmod(dst, self.CRONJOB_SCRIPT_MODE)201 os.chmod(dst, self.CRONJOB_SCRIPT_MODE)
207 os.chown(dst, uid=self.CRONJOB_SCRIPT_UID, gid=self.CRONJOB_SCRIPT_GID)202 os.chown(dst, uid=self.CRONJOB_SCRIPT_UID, gid=self.CRONJOB_SCRIPT_GID)
208 hookenv.log(203 hookenv.log(
209 'Cronjob script [{}] copied to {}'204 "Cronjob script [{}] copied to {}"
210 ''.format(self._cron_script, self.NRPE_PLUGINS_DIR),205 "".format(self._cron_script, self.NRPE_PLUGINS_DIR),
211 hookenv.DEBUG206 hookenv.DEBUG,
212 )207 )
213208
214 cmdline = [dst]209 cmdline = [dst]
215 if self._cron_script_args \210 if self._cron_script_args and isinstance(self._cron_script_args, str):
216 and isinstance(self._cron_script_args, str):211 cmdline.extend([shlex.quote(arg) for arg in self._cron_script_args.split()])
217 cmdline.extend([shlex.quote(arg)212 elif hookenv.config("manufacturer") != "test":
218 for arg in self._cron_script_args.split()])
219 elif hookenv.config('manufacturer') != 'test':
220 # Run it once to generate the temp file unless we're on a test213 # Run it once to generate the temp file unless we're on a test
221 # container, otherwise the nrpe check # might fail at first.214 # container, otherwise the nrpe check # might fail at first.
222 # For security reasons, cronjobs that allow parameters shared215 # For security reasons, cronjobs that allow parameters shared
@@ -227,39 +220,32 @@ class Tool():
227 # Generate random cronjob execution (internal in minutes)220 # Generate random cronjob execution (internal in minutes)
228 cron_interval = 5221 cron_interval = 5
229 minutes_offsets = []222 minutes_offsets = []
230 minute_num = binascii.crc_hqx(223 minute_num = binascii.crc_hqx("".join(cmdline).encode(), 0) % cron_interval
231 ''.join(cmdline).encode(), 0) % cron_interval
232 while minute_num < 60:224 while minute_num < 60:
233 minutes_offsets.append(str(minute_num))225 minutes_offsets.append(str(minute_num))
234 minute_num += cron_interval226 minute_num += cron_interval
235 cronjob_line = '{minutes} * * * * {user} {cmd}\n'.format(227 cronjob_line = "{minutes} * * * * {user} {cmd}\n".format(
236 minutes=','.join(minutes_offsets), user=cron_user,228 minutes=",".join(minutes_offsets), user=cron_user, cmd=" ".join(cmdline)
237 cmd=' '.join(cmdline))229 )
238230
239 crond_file = os.path.join(self.CROND_DIR,231 crond_file = os.path.join(self.CROND_DIR, "hwhealth_{}".format(self._shortname))
240 'hwhealth_{}'.format(self._shortname))232 with open(crond_file, "w") as crond_fd:
241 with open(crond_file, 'w') as crond_fd:
242 crond_fd.write(cronjob_line)233 crond_fd.write(cronjob_line)
243 hookenv.log(234 hookenv.log("Cronjob configured at {}".format(crond_file), hookenv.DEBUG)
244 'Cronjob configured at {}'.format(crond_file),
245 hookenv.DEBUG
246 )
247 return dst235 return dst
248236
249 def _remove_cronjob(self):237 def _remove_cronjob(self):
250 assert self._cron_script is not None238 assert self._cron_script is not None
251239
252 crond_file = os.path.join(self.CROND_DIR,240 crond_file = os.path.join(self.CROND_DIR, "hwhealth_{}".format(self._shortname))
253 'hwhealth_{}'.format(self._shortname))
254 cron_script = os.path.join(self.NRPE_PLUGINS_DIR, self._cron_script)241 cron_script = os.path.join(self.NRPE_PLUGINS_DIR, self._cron_script)
255 for filename in (crond_file, cron_script):242 for filename in (crond_file, cron_script):
256 if not os.path.exists(filename):243 if not os.path.exists(filename):
257 continue244 continue
258 os.remove(filename)245 os.remove(filename)
259 hookenv.log(246 hookenv.log(
260 'Removed cronjob files [{}, {}]'247 "Removed cronjob files [{}, {}]".format(crond_file, cron_script),
261 ''.format(crond_file, cron_script),248 hookenv.DEBUG,
262 hookenv.DEBUG
263 )249 )
264250
265 def _remove_sudoer(self):251 def _remove_sudoer(self):
@@ -267,8 +253,7 @@ class Tool():
267 if not sudoer_path.exists():253 if not sudoer_path.exists():
268 return254 return
269 sudoer_path.unlink()255 sudoer_path.unlink()
270 hookenv.log('deleted sudoer file: {}'.format(sudoer_path),256 hookenv.log("deleted sudoer file: {}".format(sudoer_path), hookenv.DEBUG)
271 hookenv.DEBUG)
272257
273258
274class VendorTool(Tool):259class VendorTool(Tool):
@@ -279,6 +264,7 @@ class VendorTool(Tool):
279 cronjob that runs as root and saves the tool output in a temporary file264 cronjob that runs as root and saves the tool output in a temporary file
280 that nrpe can read (as nagios user).265 that nrpe can read (as nagios user).
281 """266 """
267
282 def __init__(self, *args, **kwargs):268 def __init__(self, *args, **kwargs):
283 super().__init__(*args, **kwargs)269 super().__init__(*args, **kwargs)
284 self.checksums = []270 self.checksums = []
@@ -294,30 +280,32 @@ class VendorTool(Tool):
294 super().remove()280 super().remove()
295281
296 def _install_from_resource(self):282 def _install_from_resource(self):
297 resource = hookenv.resource_get('tools')283 resource = hookenv.resource_get("tools")
298 if not resource:284 if not resource:
299 raise JujuResourceNotFound('tools')285 raise JujuResourceNotFound("tools")
300 else:286 else:
301 hookenv.log(287 hookenv.log(
302 'Installing tool [{}] from resource'.format(self._shortname),288 "Installing tool [{}] from resource".format(self._shortname),
303 hookenv.DEBUG289 hookenv.DEBUG,
304 )290 )
305 # Move in from a temp directory to be atomic291 # Move in from a temp directory to be atomic
306 with TemporaryDirectory() as tmpdir:292 with TemporaryDirectory() as tmpdir:
307 try:293 try:
308 with ZipFile(resource, 'r') as zipfile:294 with ZipFile(resource, "r") as zipfile:
309 tmpfile = zipfile.extract(self._shortname, tmpdir)295 tmpfile = zipfile.extract(self._shortname, tmpdir)
310 # Verify checksum296 # Verify checksum
311 checksum = hashlib.sha256()297 checksum = hashlib.sha256()
312 with open(tmpfile, 'rb') as fd:298 with open(tmpfile, "rb") as fd:
313 checksum.update(fd.read())299 checksum.update(fd.read())
314 if checksum.hexdigest() not in self.checksums:300 if checksum.hexdigest() not in self.checksums:
315 checksums_string = ', '.join(self.checksums)301 checksums_string = ", ".join(self.checksums)
316 raise ToolChecksumError({302 raise ToolChecksumError(
317 'shortname': self._shortname,303 {
318 'checksum': checksum.hexdigest(),304 "shortname": self._shortname,
319 'expected_checksums': checksums_string305 "checksum": checksum.hexdigest(),
320 })306 "expected_checksums": checksums_string,
307 }
308 )
321 # We could just use self.TOOLS_DIR as a destination309 # We could just use self.TOOLS_DIR as a destination
322 # here, but shutil.move refuses to overwrite the310 # here, but shutil.move refuses to overwrite the
323 # destination file unless it receives a full path311 # destination file unless it receives a full path
@@ -327,28 +315,24 @@ class VendorTool(Tool):
327 os.chown(dst, uid=self.TOOLS_UID, gid=self.TOOLS_GID)315 os.chown(dst, uid=self.TOOLS_UID, gid=self.TOOLS_GID)
328316
329 except BadZipFile as error:317 except BadZipFile as error:
330 hookenv.log('BadZipFile: {}'.format(error), hookenv.ERROR)318 hookenv.log("BadZipFile: {}".format(error), hookenv.ERROR)
331319
332 except PermissionError as error:320 except PermissionError as error:
333 hookenv.log(321 hookenv.log(
334 'Unable to unzip tool {} '322 "Unable to unzip tool {} "
335 'from the provided resource: {}'323 "from the provided resource: {}"
336 ''.format(self._shortname, error),324 "".format(self._shortname, error),
337 hookenv.ERROR325 hookenv.ERROR,
338 )326 )
339 except KeyError as error:327 except KeyError as error:
340 raise ToolNotFound({'shortname': self._shortname,328 raise ToolNotFound({"shortname": self._shortname, "error": error})
341 'error': error})
342329
343 def _remove_binary(self):330 def _remove_binary(self):
344 binary_path = Path(self.TOOLS_DIR) / self._shortname331 binary_path = Path(self.TOOLS_DIR) / self._shortname
345 if not binary_path.exists():332 if not binary_path.exists():
346 return333 return
347 binary_path.unlink()334 binary_path.unlink()
348 hookenv.log(335 hookenv.log("Removed binary tool {}".format(binary_path), hookenv.DEBUG)
349 'Removed binary tool {}'.format(binary_path),
350 hookenv.DEBUG
351 )
352336
353337
354class AptVendorTool(Tool):338class AptVendorTool(Tool):
@@ -402,51 +386,42 @@ class AptVendorTool(Tool):
402 -----END PGP PUBLIC KEY BLOCK-----"""386 -----END PGP PUBLIC KEY BLOCK-----"""
403387
404 HPE_MCP_REPO_TMPL = (388 HPE_MCP_REPO_TMPL = (
405 'deb http://downloads.linux.hpe.com/SDR/repo/mcp {series}/current-gen9 non-free'389 "deb http://downloads.linux.hpe.com/SDR/repo/mcp {series}/current-gen9 non-free"
406 )390 )
407 HPE_ILOREST_REPO_TMPL = (391 HPE_ILOREST_REPO_TMPL = (
408 'deb http://downloads.linux.hpe.com/SDR/repo/ilorest {series}/current non-free'392 "deb http://downloads.linux.hpe.com/SDR/repo/ilorest {series}/current non-free"
409 )393 )
410394
411 # HP doesn't have focal APT sources as of yet395 # HP doesn't have focal APT sources as of yet
412 APT_SOURCES = {396 APT_SOURCES = {
413 'ssacli': {397 "ssacli": {
414 'xenial': HPE_MCP_REPO_TMPL.format(series='xenial'),398 "xenial": HPE_MCP_REPO_TMPL.format(series="xenial"),
415 'bionic': HPE_MCP_REPO_TMPL.format(series='bionic'),399 "bionic": HPE_MCP_REPO_TMPL.format(series="bionic"),
416 },400 },
417 'ilorest': {401 "ilorest": {
418 'xenial': HPE_ILOREST_REPO_TMPL.format(series='xenial'),402 "xenial": HPE_ILOREST_REPO_TMPL.format(series="xenial"),
419 'bionic': HPE_ILOREST_REPO_TMPL.format(series='bionic'),403 "bionic": HPE_ILOREST_REPO_TMPL.format(series="bionic"),
420 },404 },
421 'hplog': {405 "hplog": {
422 'xenial': HPE_MCP_REPO_TMPL.format(series='xenial'),406 "xenial": HPE_MCP_REPO_TMPL.format(series="xenial"),
423 'bionic': HPE_MCP_REPO_TMPL.format(series='bionic'),407 "bionic": HPE_MCP_REPO_TMPL.format(series="bionic"),
424 },408 },
425 }409 }
426 APT_KEYS = {410 APT_KEYS = {
427 'ssacli': {411 "ssacli": {"xenial": HPE_MCP_KEY, "bionic": HPE_MCP_KEY},
428 'xenial': HPE_MCP_KEY,412 "ilorest": {"xenial": HPE_ILOREST_KEY, "bionic": HPE_ILOREST_KEY},
429 'bionic': HPE_MCP_KEY,413 "hplog": {"xenial": HPE_MCP_KEY, "bionic": HPE_MCP_KEY},
430 },
431 'ilorest': {
432 'xenial': HPE_ILOREST_KEY,
433 'bionic': HPE_ILOREST_KEY,
434 },
435 'hplog': {
436 'xenial': HPE_MCP_KEY,
437 'bionic': HPE_MCP_KEY,
438 },
439 }414 }
440415
441 def __init__(self, shortname=None, apt_packages=[]):416 def __init__(self, shortname=None, apt_packages=[]):
442 super().__init__(417 super().__init__(
443 shortname=shortname,418 shortname=shortname,
444 nrpe_script='check_hw_health_cron_output.py',419 nrpe_script="check_hw_health_cron_output.py",
445 nrpe_opts='--filename {}/{}.out'.format(self.CRONJOB_OUTPUT_DIR,420 nrpe_opts="--filename {}/{}.out".format(self.CRONJOB_OUTPUT_DIR, shortname),
446 shortname)421 )
422 self.apt_packages = (
423 apt_packages if apt_packages else [self.__class__.__name__.lower()]
447 )424 )
448 self.apt_packages = (apt_packages if apt_packages
449 else [self.__class__.__name__.lower()])
450 self._nrpe_script_dir = self._common_libs_dir425 self._nrpe_script_dir = self._common_libs_dir
451426
452 def install(self):427 def install(self):
@@ -471,38 +446,45 @@ class AptVendorTool(Tool):
471 hardware present on the system.446 hardware present on the system.
472 """447 """
473 self._add_apt_source()448 self._add_apt_source()
474 if hookenv.config('manufacturer') == 'test':449 if hookenv.config("manufacturer") == "test":
475 # If we are forcing install on a container for functional tests,450 # If we are forcing install on a container for functional tests,
476 # we should only download and not install the packages, as some451 # we should only download and not install the packages, as some
477 # vendor tools depend on hardware to be preset to complete postinst452 # vendor tools depend on hardware to be preset to complete postinst
478 # need one option added per package453 # need one option added per package
479 apt.queue_install(self.apt_packages, options=["--download-only" for _ in self.apt_packages])454 apt.queue_install(
455 self.apt_packages,
456 options=["--download-only" for _ in self.apt_packages],
457 )
480 else:458 else:
481 apt.queue_install(self.apt_packages)459 apt.queue_install(self.apt_packages)
482460
483 def _add_apt_source(self):461 def _add_apt_source(self):
484 series = lsb_release()['DISTRIB_CODENAME']462 series = lsb_release()["DISTRIB_CODENAME"]
485 if self._shortname not in self.APT_SOURCES and self._shortname not in self.APT_KEYS:463 if (
464 self._shortname not in self.APT_SOURCES
465 and self._shortname not in self.APT_KEYS
466 ):
486 return467 return
487 if series in self.APT_SOURCES[self._shortname]:468 if series in self.APT_SOURCES[self._shortname]:
488 apt.add_source(self.APT_SOURCES[self._shortname][series],469 apt.add_source(
489 key=self.APT_KEYS[self._shortname][series])470 self.APT_SOURCES[self._shortname][series],
471 key=self.APT_KEYS[self._shortname][series],
472 )
490473
491 def _remove_packages(self):474 def _remove_packages(self):
492 apt.purge(self.apt_packages)475 apt.purge(self.apt_packages)
493476
494 def install_cronjob(self):477 def install_cronjob(self):
495 hookenv.log(478 hookenv.log(
496 'Attempting AptVendorTool cronjob script install [{}]'479 "Attempting AptVendorTool cronjob script install [{}]"
497 ''.format(self._cron_script),480 "".format(self._cron_script),
498 hookenv.DEBUG481 hookenv.DEBUG,
499 )482 )
500 # Don't install a cronjob until the tools are installed483 # Don't install a cronjob until the tools are installed
501 if self.is_apt_installed():484 if self.is_apt_installed():
502 hookenv.log(485 hookenv.log(
503 'calling _install_cronjob for {}'486 "calling _install_cronjob for {}".format(self._cron_script),
504 ''.format(self._cron_script),487 hookenv.DEBUG,
505 hookenv.DEBUG
506 )488 )
507 self._install_cronjob()489 self._install_cronjob()
508490
@@ -521,7 +503,7 @@ class AptVendorTool(Tool):
521 allows the reactive layer to know when the apt packages have finished503 allows the reactive layer to know when the apt packages have finished
522 installing so that it may go on to configure the nrpe layer of tools504 installing so that it may go on to configure the nrpe layer of tools
523 """505 """
524 if hookenv.config('manufacturer') == 'test':506 if hookenv.config("manufacturer") == "test":
525 # it is okay to skip this part in testing as layer-apt will block507 # it is okay to skip this part in testing as layer-apt will block
526 # if the package is not downloadable from sources508 # if the package is not downloadable from sources
527 return True509 return True
@@ -537,11 +519,12 @@ class Sas3Ircu(VendorTool):
537519
538 This is a tool supporting the LSI SAS 12Gb/s controllers520 This is a tool supporting the LSI SAS 12Gb/s controllers
539 """521 """
522
540 def __init__(self):523 def __init__(self):
541 super().__init__(cron_script='cron_sas3ircu.sh')524 super().__init__(cron_script="cron_sas3ircu.sh")
542 self.checksums = [525 self.checksums = [
543 'f150eb37bb332668949a3eccf9636e0e03f874aecd17a39d586082c6be1386bd',526 "f150eb37bb332668949a3eccf9636e0e03f874aecd17a39d586082c6be1386bd",
544 'd69967057992134df1b136f83bc775a641e32c4efc741def3ef6f6a25a9a14b5',527 "d69967057992134df1b136f83bc775a641e32c4efc741def3ef6f6a25a9a14b5",
545 ]528 ]
546529
547530
@@ -550,9 +533,12 @@ class Sas2Ircu(VendorTool):
550533
551 This is a tool supporting the LSI SAS 6Gb/s controllers534 This is a tool supporting the LSI SAS 6Gb/s controllers
552 """535 """
536
553 def __init__(self):537 def __init__(self):
554 super().__init__(cron_script='cron_sas2ircu.sh')538 super().__init__(cron_script="cron_sas2ircu.sh")
555 self.checksums = ['37467826d0b22aad47287efe70bb34e47f475d70e9b1b64cbd63f57607701e73'] # noqa: E501539 self.checksums = [
540 "37467826d0b22aad47287efe70bb34e47f475d70e9b1b64cbd63f57607701e73"
541 ] # noqa: E501
556542
557543
558class MegaCLI(VendorTool):544class MegaCLI(VendorTool):
@@ -560,12 +546,13 @@ class MegaCLI(VendorTool):
560546
561 This is a tool supporting the LSI MegaRAID SAS controllers547 This is a tool supporting the LSI MegaRAID SAS controllers
562 """548 """
549
563 def __init__(self):550 def __init__(self):
564 super().__init__(cron_script='cron_megacli.sh')551 super().__init__(cron_script="cron_megacli.sh")
565 self.checksums = [552 self.checksums = [
566 '34f1a235543662615ee35f458317380b3f89fac0e415dee755e0dbc7c4cf6f92',553 "34f1a235543662615ee35f458317380b3f89fac0e415dee755e0dbc7c4cf6f92",
567 '1c4effe33ee5db82227e05925dd629771fd49c7d2be2382d48c48a864452cdec',554 "1c4effe33ee5db82227e05925dd629771fd49c7d2be2382d48c48a864452cdec",
568 '1a68e6646d1e3dfb7039f581be994500d0ed02de2f928e57399e86473d4c8662',555 "1a68e6646d1e3dfb7039f581be994500d0ed02de2f928e57399e86473d4c8662",
569 ]556 ]
570557
571558
@@ -575,10 +562,10 @@ class HpLog(AptVendorTool):
575 This is a tool supporting the LSI MegaRAID SAS controllers562 This is a tool supporting the LSI MegaRAID SAS controllers
576 """563 """
577564
578 SUPPORTED_SERIES = ['xenial', 'bionic']565 SUPPORTED_SERIES = ["xenial", "bionic"]
579566
580 def __init__(self):567 def __init__(self):
581 super().__init__(apt_packages=['hp-health'])568 super().__init__(apt_packages=["hp-health"])
582569
583570
584class SsaCli(AptVendorTool):571class SsaCli(AptVendorTool):
@@ -587,7 +574,7 @@ class SsaCli(AptVendorTool):
587 This is a tool supporting the HP Smart Array controllers574 This is a tool supporting the HP Smart Array controllers
588 """575 """
589576
590 SUPPORTED_SERIES = ['xenial', 'bionic']577 SUPPORTED_SERIES = ["xenial", "bionic"]
591578
592 def __init__(self):579 def __init__(self):
593 super().__init__()580 super().__init__()
@@ -597,7 +584,7 @@ class ILOrest(AptVendorTool):
597 """A class representing the ILOrest vendor tool (HPE hardware (Gen 10+)584 """A class representing the ILOrest vendor tool (HPE hardware (Gen 10+)
598 """585 """
599586
600 SUPPORTED_SERIES = ['xenial', 'bionic']587 SUPPORTED_SERIES = ["xenial", "bionic"]
601588
602 def __init__(self):589 def __init__(self):
603 super().__init__()590 super().__init__()
@@ -609,12 +596,13 @@ class Mdadm(VendorTool):
609 Our mdadm check kind of behaves like a VendorTool for the purpose of596 Our mdadm check kind of behaves like a VendorTool for the purpose of
610 installation as it has a cronjob + check script597 installation as it has a cronjob + check script
611 """598 """
599
612 def __init__(self):600 def __init__(self):
613 super().__init__()601 super().__init__()
614602
615 def install(self):603 def install(self):
616 # mdadm should already be installed, but let's check604 # mdadm should already be installed, but let's check
617 fetch.apt_install(['mdadm'], fatal=True)605 fetch.apt_install(["mdadm"], fatal=True)
618 self._install_cronjob()606 self._install_cronjob()
619 # No vendor binary to install607 # No vendor binary to install
620 Tool.install(self)608 Tool.install(self)
@@ -634,12 +622,13 @@ class Ipmi(Tool):
634 install; the plugin relies only on freeipmi, a few perl modules, and the622 install; the plugin relies only on freeipmi, a few perl modules, and the
635 actual nrpe check, which is imported as a git submodule623 actual nrpe check, which is imported as a git submodule
636 """624 """
637 def __init__(self, nrpe_opts=''):625
626 def __init__(self, nrpe_opts=""):
638 super().__init__(627 super().__init__(
639 cron_script='cron_ipmi_sensors.py',628 cron_script="cron_ipmi_sensors.py",
640 cron_script_args=hookenv.config('ipmi_check_options')629 cron_script_args=hookenv.config("ipmi_check_options"),
641 )630 )
642 self._sudoer_file = '99-check_ipmi_sensor'631 self._sudoer_file = "99-check_ipmi_sensor"
643632
644 def configure_nrpe_check(self, nrpe_setup):633 def configure_nrpe_check(self, nrpe_setup):
645 # extra options for check_ipmi_sensors Perl script are configured in634 # extra options for check_ipmi_sensors Perl script are configured in
@@ -652,7 +641,7 @@ class Ipmi(Tool):
652 self._install_sudoer()641 self._install_sudoer()
653 # Install Perl script called by the (Python) cronjob642 # Install Perl script called by the (Python) cronjob
654 self._install_nrpe_helper_plugin()643 self._install_nrpe_helper_plugin()
655 self._install_cronjob(cron_user='nagios')644 self._install_cronjob(cron_user="nagios")
656645
657 # Install the Python script called by check_nrpe646 # Install the Python script called by check_nrpe
658 super().install()647 super().install()
@@ -665,13 +654,13 @@ class Ipmi(Tool):
665654
666 def _install_nrpe_helper_plugin(self):655 def _install_nrpe_helper_plugin(self):
667 original_nrpe_script = self._nrpe_script656 original_nrpe_script = self._nrpe_script
668 self._nrpe_script = 'check_ipmi_sensor'657 self._nrpe_script = "check_ipmi_sensor"
669 super()._install_nrpe_plugin()658 super()._install_nrpe_plugin()
670 self._nrpe_script = original_nrpe_script659 self._nrpe_script = original_nrpe_script
671660
672 def _remove_nrpe_helper_plugin(self):661 def _remove_nrpe_helper_plugin(self):
673 original_nrpe_script = self._nrpe_script662 original_nrpe_script = self._nrpe_script
674 self._nrpe_script = 'check_ipmi_sensor'663 self._nrpe_script = "check_ipmi_sensor"
675 super()._remove_nrpe_plugin()664 super()._remove_nrpe_plugin()
676 self._nrpe_script = original_nrpe_script665 self._nrpe_script = original_nrpe_script
677666
@@ -683,9 +672,8 @@ class Ipmi(Tool):
683 os.chmod(dst, self.SUDOERS_MODE)672 os.chmod(dst, self.SUDOERS_MODE)
684 os.chown(dst, uid=self.SUDOERS_UID, gid=self.SUDOERS_GID)673 os.chown(dst, uid=self.SUDOERS_UID, gid=self.SUDOERS_GID)
685 hookenv.log(674 hookenv.log(
686 'sudoer file for tool [{}] installed at {}'675 "sudoer file for tool [{}] installed at {}".format(self._shortname, dst),
687 ''.format(self._shortname, dst),676 hookenv.DEBUG,
688 hookenv.DEBUG
689 )677 )
690 return dst678 return dst
691679
@@ -696,15 +684,16 @@ class Nvme(Tool):
696 This is a direct subclass of Tool because unlike a VendorTool we are not684 This is a direct subclass of Tool because unlike a VendorTool we are not
697 using a cronjob script685 using a cronjob script
698 """686 """
687
699 def __init__(self):688 def __init__(self):
700 super().__init__()689 super().__init__()
701 self._sudoer_template = '99-check_nvme.tmpl'690 self._sudoer_template = "99-check_nvme.tmpl"
702 self._sudoer_file = '99-check_nvme'691 self._sudoer_file = "99-check_nvme"
703 self._cron_script = None692 self._cron_script = None
704693
705 def install(self):694 def install(self):
706 # mdadm should already be installed, but let's check695 # mdadm should already be installed, but let's check
707 fetch.apt_install(['nvme-cli'], fatal=True)696 fetch.apt_install(["nvme-cli"], fatal=True)
708 self._render_sudoer()697 self._render_sudoer()
709 super().install()698 super().install()
710699
@@ -722,24 +711,29 @@ class Nvme(Tool):
722 if not devices:711 if not devices:
723 return712 return
724713
725 devices = dict([('CHECK{}'.format(dev.replace('/', '_').upper()), dev)714 devices = dict(
726 for dev in devices])715 [("CHECK{}".format(dev.replace("/", "_").upper()), dev) for dev in devices]
727 ctxt = {'devices': devices}716 )
728 ctxt['devices_cmnd_aliases'] = ', '.join(devices.keys())717 ctxt = {"devices": devices}
729718 ctxt["devices_cmnd_aliases"] = ", ".join(devices.keys())
730 render(source=src, target=dst, context=ctxt, perms=self.SUDOERS_MODE,719
731 templates_dir=None)720 render(
721 source=src,
722 target=dst,
723 context=ctxt,
724 perms=self.SUDOERS_MODE,
725 templates_dir=None,
726 )
732 hookenv.log(727 hookenv.log(
733 'sudoer file for tool [{}] installed at {}'.format(self._shortname,728 "sudoer file for tool [{}] installed at {}".format(self._shortname, dst),
734 dst),729 hookenv.DEBUG,
735 hookenv.DEBUG
736 )730 )
737 return dst731 return dst
738732
739 def __get_nvme_devices(self):733 def __get_nvme_devices(self):
740 devices = []734 devices = []
741 for device in glob.glob('/dev/nvme*'):735 for device in glob.glob("/dev/nvme*"):
742 nvme_re = re.match(r'^/dev/nvme\d+$', device)736 nvme_re = re.match(r"^/dev/nvme\d+$", device)
743 if not nvme_re:737 if not nvme_re:
744 continue738 continue
745 devices.append(nvme_re.group())739 devices.append(nvme_re.group())
diff --git a/src/reactive/hw_health.py b/src/reactive/hw_health.py
index a04f790..ff72e85 100644
--- a/src/reactive/hw_health.py
+++ b/src/reactive/hw_health.py
@@ -12,167 +12,176 @@ from hwhealth import tools
1212
13def _set_install_status(tool):13def _set_install_status(tool):
14 if isinstance(tool, tools.VendorTool) and not isinstance(tool, tools.Mdadm):14 if isinstance(tool, tools.VendorTool) and not isinstance(tool, tools.Mdadm):
15 status.maintenance('Installing from attached resource')15 status.maintenance("Installing from attached resource")
16 elif isinstance(tool, tools.AptVendorTool):16 elif isinstance(tool, tools.AptVendorTool):
17 status.maintenance('Installing vendor tools via apt')17 status.maintenance("Installing vendor tools via apt")
18 set_flag('hw-health.wait-for-vendor-apt')18 set_flag("hw-health.wait-for-vendor-apt")
1919
2020
21@when_none('hw-health.installed', 'hw-health.unsupported')21@when_none("hw-health.installed", "hw-health.unsupported")
22@when('nrpe-external-master.available')22@when("nrpe-external-master.available")
23@when('general-info.connected')23@when("general-info.connected")
24def install():24def install():
25 manufacturer = hookenv.config('manufacturer')25 manufacturer = hookenv.config("manufacturer")
26 if host.is_container() and manufacturer != 'test':26 if host.is_container() and manufacturer != "test":
27 status.blocked('Containers are not supported')27 status.blocked("Containers are not supported")
28 set_flag('hw-health.unsupported')28 set_flag("hw-health.unsupported")
29 return29 return
3030
31 if manufacturer not in ['auto', 'test']:31 if manufacturer not in ["auto", "test"]:
32 status.blocked('manufacturer needs to be set to auto')32 status.blocked("manufacturer needs to be set to auto")
33 return33 return
3434
35 # Detect hardware and return a list of tools we need to use35 # Detect hardware and return a list of tools we need to use
36 status.maintenance('Autodiscovering hardware')36 status.maintenance("Autodiscovering hardware")
37 toolset = get_tools(manufacturer)37 toolset = get_tools(manufacturer)
38 if not toolset:38 if not toolset:
39 status.blocked('Hardware not supported')39 status.blocked("Hardware not supported")
40 set_flag('hw-health.unsupported')40 set_flag("hw-health.unsupported")
41 else:41 else:
42 try:42 try:
43 tool_list = list()43 tool_list = list()
44 for toolClass in toolset:44 for toolClass in toolset:
45 tool = toolClass()45 tool = toolClass()
46 _set_install_status(tool)46 _set_install_status(tool)
47 status.maintenance('Installing tool {}'.format(type(tool).__name__))47 status.maintenance("Installing tool {}".format(type(tool).__name__))
48 tool.install()48 tool.install()
49 # Save the class name in the unit kv db. This will be reused when49 # Save the class name in the unit kv db. This will be reused when
50 # reconfiguring or removing the checks50 # reconfiguring or removing the checks
51 tool_list.append(type(tool).__name__)51 tool_list.append(type(tool).__name__)
52 unitdb = unitdata.kv()52 unitdb = unitdata.kv()
53 unitdb.set('toolset', tool_list)53 unitdb.set("toolset", tool_list)
54 set_flag('hw-health.installed')54 set_flag("hw-health.installed")
55 except tools.JujuResourceNotFound as error:55 except tools.JujuResourceNotFound as error:
56 hookenv.log('Missing Juju resource: {} - alternative method is not '56 hookenv.log(
57 ' available yet'.format(error), hookenv.ERROR)57 "Missing Juju resource: {} - alternative method is not "
58 status.blocked('Missing Juju resource: {}'.format(error))58 " available yet".format(error),
59 set_flag('hw-health.unsupported')59 hookenv.ERROR,
60 )
61 status.blocked("Missing Juju resource: {}".format(error))
62 set_flag("hw-health.unsupported")
60 except tools.ToolChecksumError as error:63 except tools.ToolChecksumError as error:
61 msg = error.message64 msg = error.message
62 hookenv.log('checksum error: tool [{shortname}], checksum[{checksum}],'65 hookenv.log(
63 ' expected[{expected_checksums}]'.format(**msg), hookenv.ERROR)66 "checksum error: tool [{shortname}], checksum[{checksum}],"
64 status.blocked('Tool {shortname} - checksum error'.format(**msg))67 " expected[{expected_checksums}]".format(**msg),
65 set_flag('hw-health.unsupported')68 hookenv.ERROR,
69 )
70 status.blocked("Tool {shortname} - checksum error".format(**msg))
71 set_flag("hw-health.unsupported")
66 except tools.ToolNotFound as error:72 except tools.ToolNotFound as error:
67 msg = error.message73 msg = error.message
68 hookenv.log('Tool {shortname} not found in the provided resource: '74 hookenv.log(
69 '{error}'.format(**msg), hookenv.ERROR)75 "Tool {shortname} not found in the provided resource: "
70 status.blocked('Tool {shortname} not found'.format(**msg))76 "{error}".format(**msg),
71 set_flag('hw-health.unsupported')77 hookenv.ERROR,
78 )
79 status.blocked("Tool {shortname} not found".format(**msg))
80 set_flag("hw-health.unsupported")
7281
7382
74@hook('upgrade-charm')83@hook("upgrade-charm")
75def upgrade():84def upgrade():
76 clear_flag('hw-health.installed')85 clear_flag("hw-health.installed")
77 clear_flag('hw-health.unsupported')86 clear_flag("hw-health.unsupported")
78 clear_flag('hw-health.configured')87 clear_flag("hw-health.configured")
79 status.maintenance('Charm upgrade in progress')88 status.maintenance("Charm upgrade in progress")
8089
8190
82@when('hw-health.installed')91@when("hw-health.installed")
83@when_not('general-info.available')92@when_not("general-info.available")
84def remove_tools():93def remove_tools():
85 # If general-info is unavailable, the subordinate relationship towards the94 # If general-info is unavailable, the subordinate relationship towards the
86 # principal charm has been broken, so we need to remove the installed tools95 # principal charm has been broken, so we need to remove the installed tools
87 unitdb = unitdata.kv()96 unitdb = unitdata.kv()
88 for tool_class_name in unitdb.get('toolset', set()):97 for tool_class_name in unitdb.get("toolset", set()):
89 # Re-instantiate the tool from the saved class name98 # Re-instantiate the tool from the saved class name
90 tool_class = getattr(tools, tool_class_name)99 tool_class = getattr(tools, tool_class_name)
91 tool_class().remove()100 tool_class().remove()
92 clear_flag('hw-health.installed')101 clear_flag("hw-health.installed")
93 clear_flag('hw-health.unsupported')102 clear_flag("hw-health.unsupported")
94 clear_flag('hw-health.configured')103 clear_flag("hw-health.configured")
95104
96105
97@when('hw-health.wait-for-vendor-apt')106@when("hw-health.wait-for-vendor-apt")
98def wait_for_vendor_apt():107def wait_for_vendor_apt():
99 # cycle through any vendor tools that are of type AptVendorTool and108 # cycle through any vendor tools that are of type AptVendorTool and
100 # check if all packages needed are installed. If not, eject and wait109 # check if all packages needed are installed. If not, eject and wait
101 unitdb = unitdata.kv()110 unitdb = unitdata.kv()
102 for tool_class_name in unitdb.get('toolset', set()):111 for tool_class_name in unitdb.get("toolset", set()):
103 # Re-instantiate the tool from the saved class name112 # Re-instantiate the tool from the saved class name
104 tool_class = getattr(tools, tool_class_name)113 tool_class = getattr(tools, tool_class_name)
105 if isinstance(tool_class, tools.AptVendorTool):114 if isinstance(tool_class, tools.AptVendorTool):
106 if tool_class.is_apt_installed():115 if tool_class.is_apt_installed():
107 tool_class.install_cronjob()116 tool_class.install_cronjob()
108 else:117 else:
109 status.maintenance('Waiting for vendor tools to install via apt')118 status.maintenance("Waiting for vendor tools to install via apt")
110 return119 return
111 clear_flag('hw-health.wait-for-vendor-apt')120 clear_flag("hw-health.wait-for-vendor-apt")
112 clear_flag('hw-health.configured')121 clear_flag("hw-health.configured")
113122
114123
115@when('config.changed')124@when("config.changed")
116@when_not('config.changed.manufacturer')125@when_not("config.changed.manufacturer")
117def config_changed():126def config_changed():
118 clear_flag('hw-health.configured')127 clear_flag("hw-health.configured")
119128
120129
121@when('config.changed.manufacturer')130@when("config.changed.manufacturer")
122def toolset_changed():131def toolset_changed():
123 if not is_flag_set('hw-health.installed'):132 if not is_flag_set("hw-health.installed"):
124 # Note(aluria): useful for testing purposes133 # Note(aluria): useful for testing purposes
125 clear_flag('hw-health.unsupported')134 clear_flag("hw-health.unsupported")
126 return135 return
127136
128 # Changing the manufacturer option will trigger a reinstallation of the137 # Changing the manufacturer option will trigger a reinstallation of the
129 # tools138 # tools
130 remove_tools()139 remove_tools()
131 status.maintenance('Reinstallation of tools in progress')140 status.maintenance("Reinstallation of tools in progress")
132141
133142
134@when('hw-health.installed')143@when("hw-health.installed")
135@when_not('nrpe-external-master.available')144@when_not("nrpe-external-master.available")
136@when_not('hw-health.configured')145@when_not("hw-health.configured")
137def blocked_on_nrpe():146def blocked_on_nrpe():
138 status.blocked('Missing relations: nrpe-external-master')147 status.blocked("Missing relations: nrpe-external-master")
139148
140149
141@when('hw-health.installed')150@when("hw-health.installed")
142@when('nrpe-external-master.available')151@when("nrpe-external-master.available")
143@when_not('hw-health.configured')152@when_not("hw-health.configured")
144def configure_nrpe():153def configure_nrpe():
145 if not os.path.exists('/var/lib/nagios'):154 if not os.path.exists("/var/lib/nagios"):
146 status.waiting('Waiting for nrpe package installation')155 status.waiting("Waiting for nrpe package installation")
147 return156 return
148157
149 status.maintenance('Configuring nrpe checks')158 status.maintenance("Configuring nrpe checks")
150159
151 nrpe_setup = nrpe.NRPE(primary=False)160 nrpe_setup = nrpe.NRPE(primary=False)
152 unitdb = unitdata.kv()161 unitdb = unitdata.kv()
153 for tool_class_name in unitdb.get('toolset', set()):162 for tool_class_name in unitdb.get("toolset", set()):
154 # Re-instantiate the tool from the saved class name163 # Re-instantiate the tool from the saved class name
155 tool_class = getattr(tools, tool_class_name)164 tool_class = getattr(tools, tool_class_name)
156 tool_class().configure_nrpe_check(nrpe_setup)165 tool_class().configure_nrpe_check(nrpe_setup)
157166
158 if unitdb.get('toolset'):167 if unitdb.get("toolset"):
159 # Note(aluria): This needs to be run outside of168 # Note(aluria): This needs to be run outside of
160 # tool_class().configure_nrpe_check or shared dictionary with the169 # tool_class().configure_nrpe_check or shared dictionary with the
161 # nagios unit will list the last added check (LP#1821602)170 # nagios unit will list the last added check (LP#1821602)
162 nrpe_setup.write()171 nrpe_setup.write()
163172
164 status.active('ready')173 status.active("ready")
165 set_flag('hw-health.configured')174 set_flag("hw-health.configured")
166175
167176
168@when('hw-health.installed')177@when("hw-health.installed")
169@when_not('nrpe-external-master.available')178@when_not("nrpe-external-master.available")
170@when('hw-health.configured')179@when("hw-health.configured")
171def remove_nrpe_checks():180def remove_nrpe_checks():
172 status.maintenance('Removing nrpe checks')181 status.maintenance("Removing nrpe checks")
173 unitdb = unitdata.kv()182 unitdb = unitdata.kv()
174 for tool_class_name in unitdb.get('toolset', set()):183 for tool_class_name in unitdb.get("toolset", set()):
175 # Re-instantiate the tool from the saved class name184 # Re-instantiate the tool from the saved class name
176 tool_class = getattr(tools, tool_class_name)185 tool_class = getattr(tools, tool_class_name)
177 tool_class().remove_nrpe_check()186 tool_class().remove_nrpe_check()
178 clear_flag('hw-health.configured')187 clear_flag("hw-health.configured")
diff --git a/src/tests/download_nagios_plugin3.py b/src/tests/download_nagios_plugin3.py
index b454caf..ded2851 100755
--- a/src/tests/download_nagios_plugin3.py
+++ b/src/tests/download_nagios_plugin3.py
@@ -3,9 +3,10 @@ from glob import glob
3import os.path3import os.path
4import urllib.request4import urllib.request
55
6MODULE_NAME = 'nagios_plugin3.py'6MODULE_NAME = "nagios_plugin3.py"
7MODULE_URL = os.path.join('https://git.launchpad.net/nrpe-charm/plain/files',7MODULE_URL = os.path.join(
8 MODULE_NAME)8 "https://git.launchpad.net/nrpe-charm/plain/files", MODULE_NAME
9)
9_cache = None10_cache = None
1011
1112
@@ -18,11 +19,11 @@ def content():
1819
1920
20def main():21def main():
21 for i in glob('.tox/unit/lib/python3*/site-packages'):22 for i in glob(".tox/unit/lib/python3*/site-packages"):
22 mod_path = os.path.join(i, MODULE_NAME)23 mod_path = os.path.join(i, MODULE_NAME)
23 if os.path.isdir(i) and not os.path.exists(mod_path):24 if os.path.isdir(i) and not os.path.exists(mod_path):
24 open(mod_path, 'wb').write(content())25 open(mod_path, "wb").write(content())
2526
2627
27if __name__ == '__main__':28if __name__ == "__main__":
28 main()29 main()
diff --git a/src/tests/functional/conftest.py b/src/tests/functional/conftest.py
index 9a65e83..5734800 100644
--- a/src/tests/functional/conftest.py
+++ b/src/tests/functional/conftest.py
@@ -1,5 +1,5 @@
1#!/usr/bin/python31#!/usr/bin/python3
2'''2"""
3Reusable pytest fixtures for functional testing3Reusable pytest fixtures for functional testing
44
5Environment variables5Environment variables
@@ -7,7 +7,7 @@ Environment variables
77
8test_preserve_model:8test_preserve_model:
9if set, the testing model won't be torn down at the end of the testing session9if set, the testing model won't be torn down at the end of the testing session
10'''10"""
1111
12import asyncio12import asyncio
13import json13import json
@@ -18,7 +18,7 @@ import juju
18from juju.controller import Controller18from juju.controller import Controller
19from juju.errors import JujuError19from juju.errors import JujuError
2020
21STAT_CMD = '''python3 - <<EOF21STAT_CMD = """python3 - <<EOF
22import json22import json
23import os23import os
2424
@@ -33,13 +33,13 @@ stat_json = json.dumps(stat_hash)
33print(stat_json)33print(stat_json)
3434
35EOF35EOF
36'''36"""
3737
3838
39@pytest.yield_fixture(scope='module')39@pytest.yield_fixture(scope="module")
40def event_loop():40def event_loop():
41 '''Override the default pytest event loop to allow for fixtures using a41 """Override the default pytest event loop to allow for fixtures using a
42 broader scope'''42 broader scope"""
43 loop = asyncio.get_event_loop_policy().new_event_loop()43 loop = asyncio.get_event_loop_policy().new_event_loop()
44 asyncio.set_event_loop(loop)44 asyncio.set_event_loop(loop)
45 loop.set_debug(True)45 loop.set_debug(True)
@@ -48,23 +48,23 @@ def event_loop():
48 asyncio.set_event_loop(None)48 asyncio.set_event_loop(None)
4949
5050
51@pytest.fixture(scope='module')51@pytest.fixture(scope="module")
52async def controller():52async def controller():
53 '''Connect to the current controller'''53 """Connect to the current controller"""
54 _controller = Controller()54 _controller = Controller()
55 await _controller.connect_current()55 await _controller.connect_current()
56 yield _controller56 yield _controller
57 await _controller.disconnect()57 await _controller.disconnect()
5858
5959
60@pytest.fixture(scope='module')60@pytest.fixture(scope="module")
61async def model(controller): # pylint: disable=redefined-outer-name61async def model(controller): # pylint: disable=redefined-outer-name
62 '''This model lives only for the duration of the test'''62 """This model lives only for the duration of the test"""
63 model_name = "functest-{}".format(uuid.uuid4())63 model_name = "functest-{}".format(uuid.uuid4())
64 _model = await controller.add_model(model_name)64 _model = await controller.add_model(model_name)
65 yield _model65 yield _model
66 await _model.disconnect()66 await _model.disconnect()
67 if not os.getenv('test_preserve_model'):67 if not os.getenv("test_preserve_model"):
68 await controller.destroy_model(model_name)68 await controller.destroy_model(model_name)
69 while model_name in await controller.list_models():69 while model_name in await controller.list_models():
70 await asyncio.sleep(1)70 await asyncio.sleep(1)
@@ -72,30 +72,35 @@ async def model(controller): # pylint: disable=redefined-outer-name
7272
73@pytest.fixture()73@pytest.fixture()
74async def get_app(model): # pylint: disable=redefined-outer-name74async def get_app(model): # pylint: disable=redefined-outer-name
75 '''Returns the application requested'''75 """Returns the application requested"""
76
76 async def _get_app(name):77 async def _get_app(name):
77 try:78 try:
78 return model.applications[name]79 return model.applications[name]
79 except KeyError:80 except KeyError:
80 raise JujuError("Cannot find application {}".format(name))81 raise JujuError("Cannot find application {}".format(name))
82
81 return _get_app83 return _get_app
8284
8385
84@pytest.fixture()86@pytest.fixture()
85async def get_unit(model): # pylint: disable=redefined-outer-name87async def get_unit(model): # pylint: disable=redefined-outer-name
86 '''Returns the requested <app_name>/<unit_number> unit'''88 """Returns the requested <app_name>/<unit_number> unit"""
89
87 async def _get_unit(name):90 async def _get_unit(name):
88 try:91 try:
89 (app_name, unit_number) = name.split('/')92 (app_name, unit_number) = name.split("/")
90 return model.applications[app_name].units[unit_number]93 return model.applications[app_name].units[unit_number]
91 except (KeyError, ValueError):94 except (KeyError, ValueError):
92 raise JujuError("Cannot find unit {}".format(name))95 raise JujuError("Cannot find unit {}".format(name))
96
93 return _get_unit97 return _get_unit
9498
9599
96@pytest.fixture()100@pytest.fixture()
97async def get_entity(get_unit, get_app): # pylint: disable=redefined-outer-name101async def get_entity(get_unit, get_app): # pylint: disable=redefined-outer-name
98 '''Returns a unit or an application'''102 """Returns a unit or an application"""
103
99 async def _get_entity(name):104 async def _get_entity(name):
100 try:105 try:
101 return await get_unit(name)106 return await get_unit(name)
@@ -104,69 +109,72 @@ async def get_entity(get_unit, get_app): # pylint: disable=redefined-outer-name
104 return await get_app(name)109 return await get_app(name)
105 except JujuError:110 except JujuError:
106 raise JujuError("Cannot find entity {}".format(name))111 raise JujuError("Cannot find entity {}".format(name))
112
107 return _get_entity113 return _get_entity
108114
109115
110@pytest.fixture116@pytest.fixture
111async def run_command(get_unit): # pylint: disable=redefined-outer-name117async def run_command(get_unit): # pylint: disable=redefined-outer-name
112 '''118 """
113 Runs a command on a unit.119 Runs a command on a unit.
114120
115 :param cmd: Command to be run121 :param cmd: Command to be run
116 :param target: Unit object or unit name string122 :param target: Unit object or unit name string
117 '''123 """
124
118 async def _run_command(cmd, target):125 async def _run_command(cmd, target):
119 unit = (126 unit = target if isinstance(target, juju.unit.Unit) else await get_unit(target)
120 target
121 if isinstance(target, juju.unit.Unit)
122 else await get_unit(target)
123 )
124 action = await unit.run(cmd)127 action = await unit.run(cmd)
125 return action.results128 return action.results
129
126 return _run_command130 return _run_command
127131
128132
129@pytest.fixture133@pytest.fixture
130async def file_stat(run_command): # pylint: disable=redefined-outer-name134async def file_stat(run_command): # pylint: disable=redefined-outer-name
131 '''135 """
132 Runs stat on a file136 Runs stat on a file
133137
134 :param path: File path138 :param path: File path
135 :param target: Unit object or unit name string139 :param target: Unit object or unit name string
136 '''140 """
141
137 async def _file_stat(path, target):142 async def _file_stat(path, target):
138 cmd = STAT_CMD % path143 cmd = STAT_CMD % path
139 results = await run_command(cmd, target)144 results = await run_command(cmd, target)
140 if results['Code'] != '0':145 if results["Code"] != "0":
141 # A common possible error is simply ENOENT, the file ain't there.146 # A common possible error is simply ENOENT, the file ain't there.
142 # A better solution would be to retrieve the exception that the147 # A better solution would be to retrieve the exception that the
143 # remote python code raised, but that would probably require a real148 # remote python code raised, but that would probably require a real
144 # RPC setup149 # RPC setup
145 raise RuntimeError('Stat failed: {}'.format(results))150 raise RuntimeError("Stat failed: {}".format(results))
146 else:151 else:
147 return json.loads(results['Stdout'])152 return json.loads(results["Stdout"])
148153
149 return _file_stat154 return _file_stat
150155
151156
152@pytest.fixture157@pytest.fixture
153async def file_contents(run_command): # pylint: disable=redefined-outer-name158async def file_contents(run_command): # pylint: disable=redefined-outer-name
154 '''159 """
155 Returns the contents of a file160 Returns the contents of a file
156161
157 :param path: File path162 :param path: File path
158 :param target: Unit object or unit name string163 :param target: Unit object or unit name string
159 '''164 """
165
160 async def _file_contents(path, target):166 async def _file_contents(path, target):
161 cmd = 'cat {}'.format(path)167 cmd = "cat {}".format(path)
162 results = await run_command(cmd, target)168 results = await run_command(cmd, target)
163 return results['Stdout']169 return results["Stdout"]
170
164 return _file_contents171 return _file_contents
165172
166173
167@pytest.fixture174@pytest.fixture
168async def reconfigure_app(get_app, model): # pylint: disable=redefined-outer-name175async def reconfigure_app(get_app, model): # pylint: disable=redefined-outer-name
169 '''Applies a different config to the requested app'''176 """Applies a different config to the requested app"""
177
170 async def _reconfigure_app(cfg, target):178 async def _reconfigure_app(cfg, target):
171 application = (179 application = (
172 target180 target
@@ -175,5 +183,6 @@ async def reconfigure_app(get_app, model): # pylint: disable=redefined-outer-na
175 )183 )
176 await application.set_config(cfg)184 await application.set_config(cfg)
177 await application.get_config()185 await application.get_config()
178 await model.block_until(lambda: application.status == 'active')186 await model.block_until(lambda: application.status == "active")
187
179 return _reconfigure_app188 return _reconfigure_app
diff --git a/src/tests/functional/test_hwhealth.py b/src/tests/functional/test_hwhealth.py
index cae8fd4..d17aae7 100644
--- a/src/tests/functional/test_hwhealth.py
+++ b/src/tests/functional/test_hwhealth.py
@@ -5,22 +5,22 @@ import subprocess
5import asyncio5import asyncio
6from os.path import abspath, dirname6from os.path import abspath, dirname
77
8sys.path.append('lib')8sys.path.append("lib")
99
10from hwhealth import hwdiscovery # noqa: E40210from hwhealth import hwdiscovery # noqa: E402
11from hwhealth import tools # noqa: E40211from hwhealth import tools # noqa: E402
1212
1313
14# Treat all tests as coroutines14# Treat all tests as coroutines
15pytestmark = pytest.mark.asyncio15pytestmark = pytest.mark.asyncio
16SERIES = [16SERIES = [
17 'focal',17 "focal",
18 'bionic',18 "bionic",
19 'xenial',19 "xenial",
20]20]
21CHARM_DIR = dirname(dirname(dirname(abspath(__file__))))21CHARM_DIR = dirname(dirname(dirname(abspath(__file__))))
22CHARM_BUILD_DIR = dirname(CHARM_DIR)22CHARM_BUILD_DIR = dirname(CHARM_DIR)
23NRPECFG_DIR = '/etc/nagios/nrpe.d'23NRPECFG_DIR = "/etc/nagios/nrpe.d"
24DEF_TIMEOUT = 60024DEF_TIMEOUT = 600
25# These go along with the hpe repos for the hp* tools25# These go along with the hpe repos for the hp* tools
2626
@@ -33,46 +33,50 @@ async def deploy_hwhealth_res(model, app_name, res_filename):
33 # Attaching resources is not implemented yet in libjuju33 # Attaching resources is not implemented yet in libjuju
34 # see https://github.com/juju/python-libjuju/issues/29434 # see https://github.com/juju/python-libjuju/issues/294
35 tools_res_path = os.path.join(CHARM_BUILD_DIR, res_filename)35 tools_res_path = os.path.join(CHARM_BUILD_DIR, res_filename)
36 subprocess.check_call([36 subprocess.check_call(
37 'juju',37 [
38 'deploy',38 "juju",
39 '-m',39 "deploy",
40 model.info.name,40 "-m",
41 os.path.join(CHARM_BUILD_DIR, 'hw-health'),41 model.info.name,
42 app_name,42 os.path.join(CHARM_BUILD_DIR, "hw-health"),
43 '--resource',43 app_name,
44 'tools={}'.format(tools_res_path),44 "--resource",
45 ])45 "tools={}".format(tools_res_path),
46 ]
47 )
4648
4749
48async def update_hwhealth_res(model, app_name, res_filename):50async def update_hwhealth_res(model, app_name, res_filename):
49 tools_res_path = os.path.join(CHARM_BUILD_DIR, res_filename)51 tools_res_path = os.path.join(CHARM_BUILD_DIR, res_filename)
50 subprocess.check_call([52 subprocess.check_call(
51 'juju',53 [
52 'attach-resource',54 "juju",
53 '-m',55 "attach-resource",
54 model.info.name,56 "-m",
55 app_name,57 model.info.name,
56 'tools={}'.format(tools_res_path),58 app_name,
57 ])59 "tools={}".format(tools_res_path),
60 ]
61 )
62
5863
59###################64###################
60# Custom fixtures #65# Custom fixtures #
61###################66###################
6267
6368
64@pytest.fixture(scope='module',69@pytest.fixture(scope="module", params=SERIES)
65 params=SERIES)
66async def deploy_app(request, model):70async def deploy_app(request, model):
67 '''Deploys the hw-health charm as a subordinate of ubuntu'''71 """Deploys the hw-health charm as a subordinate of ubuntu"""
68 # TODO: this might look nicer if we deployed a bundle instead. It could be72 # TODO: this might look nicer if we deployed a bundle instead. It could be
69 # a jinja template to handle the parametrization73 # a jinja template to handle the parametrization
70 release = request.param74 release = request.param
71 channel = 'stable'75 channel = "stable"
72 hw_health_app_name = 'hw-health-{}'.format(release)76 hw_health_app_name = "hw-health-{}".format(release)
73 hw_health_checksum_app_name = 'hw-health-checksum-{}'.format(release)77 hw_health_checksum_app_name = "hw-health-checksum-{}".format(release)
7478
75 for principal_app in ['ubuntu', 'nagios']:79 for principal_app in ["ubuntu", "nagios"]:
76 relname = series = release80 relname = series = release
77 if principal_app == "nagios" and release == "focal":81 if principal_app == "nagios" and release == "focal":
78 # NOTE(aluria): cs:nagios was not available in focal82 # NOTE(aluria): cs:nagios was not available in focal
@@ -82,97 +86,90 @@ async def deploy_app(request, model):
82 series = "bionic"86 series = "bionic"
83 await model.deploy(87 await model.deploy(
84 principal_app,88 principal_app,
85 application_name='{}-{}'.format(principal_app, relname),89 application_name="{}-{}".format(principal_app, relname),
86 series=series,90 series=series,
87 channel=channel,91 channel=channel,
88 )92 )
89 await model.deploy(93 await model.deploy(
90 'ubuntu',94 "ubuntu",
91 application_name='ubuntu-checksum-{}'.format(release),95 application_name="ubuntu-checksum-{}".format(release),
92 series=release,96 series=release,
93 channel=channel97 channel=channel,
94 )98 )
95 nrpe_app = await model.deploy(99 nrpe_app = await model.deploy(
96 'nrpe',100 "nrpe",
97 application_name='nrpe-{}'.format(release),101 application_name="nrpe-{}".format(release),
98 series=release,102 series=release,
99 num_units=0,103 num_units=0,
100 channel=channel,104 channel=channel,
101 )105 )
102 for ubuntu_unit in ['ubuntu', 'ubuntu-checksum']:106 for ubuntu_unit in ["ubuntu", "ubuntu-checksum"]:
103 await nrpe_app.add_relation(107 await nrpe_app.add_relation(
104 'general-info',108 "general-info", "{}-{}:juju-info".format(ubuntu_unit, release)
105 '{}-{}:juju-info'.format(ubuntu_unit, release)
106 )109 )
107 await nrpe_app.add_relation(110 await nrpe_app.add_relation("monitors", "nagios-{}:monitors".format(relname))
108 'monitors',
109 'nagios-{}:monitors'.format(relname)
110 )
111111
112 # Attaching resources is not implemented yet in libjuju112 # Attaching resources is not implemented yet in libjuju
113 # see https://github.com/juju/python-libjuju/issues/294113 # see https://github.com/juju/python-libjuju/issues/294
114 await deploy_hwhealth_res(model, hw_health_app_name, 'tools.zip')114 await deploy_hwhealth_res(model, hw_health_app_name, "tools.zip")
115 await deploy_hwhealth_res(model, hw_health_checksum_app_name,115 await deploy_hwhealth_res(model, hw_health_checksum_app_name, "tools-checksum.zip")
116 'tools-checksum.zip')
117116
118 # This is pretty horrible, but we can't deploy via libjuju117 # This is pretty horrible, but we can't deploy via libjuju
119 while True:118 while True:
120 try:119 try:
121 hw_health_app = model.applications[hw_health_app_name]120 hw_health_app = model.applications[hw_health_app_name]
122 hw_health_checksum_app = \121 hw_health_checksum_app = model.applications[hw_health_checksum_app_name]
123 model.applications[hw_health_checksum_app_name]
124 break122 break
125 except KeyError:123 except KeyError:
126 await asyncio.sleep(5)124 await asyncio.sleep(5)
127125
128 await hw_health_app.add_relation(126 await hw_health_app.add_relation(
129 'general-info',127 "general-info", "ubuntu-{}:juju-info".format(release)
130 'ubuntu-{}:juju-info'.format(release)
131 )128 )
132 await hw_health_app.add_relation(129 await hw_health_app.add_relation(
133 'nrpe-external-master',130 "nrpe-external-master", "{}:nrpe-external-master".format(nrpe_app.name)
134 '{}:nrpe-external-master'.format(nrpe_app.name)
135 )131 )
136132
137 await hw_health_checksum_app.add_relation(133 await hw_health_checksum_app.add_relation(
138 'general-info',134 "general-info", "ubuntu-checksum-{}:juju-info".format(release)
139 'ubuntu-checksum-{}:juju-info'.format(release)
140 )135 )
141 await hw_health_checksum_app.add_relation(136 await hw_health_checksum_app.add_relation(
142 'nrpe-external-master',137 "nrpe-external-master", "{}:nrpe-external-master".format(nrpe_app.name)
143 '{}:nrpe-external-master'.format(nrpe_app.name)
144 )138 )
145139
146 # The app will initially be in blocked state because it's running in a140 # The app will initially be in blocked state because it's running in a
147 # container141 # container
148 await model.block_until(142 await model.block_until(
149 lambda: (hw_health_app.status == 'blocked' and # noqa:W504143 lambda: (
150 hw_health_checksum_app.status == 'blocked'),144 hw_health_app.status == "blocked"
151 timeout=DEF_TIMEOUT145 and hw_health_checksum_app.status == "blocked" # noqa:W504
146 ),
147 timeout=DEF_TIMEOUT,
152 )148 )
153 yield hw_health_app149 yield hw_health_app
154150
155151
156@pytest.fixture(scope='module')152@pytest.fixture(scope="module")
157async def deployed_unit(deploy_app):153async def deployed_unit(deploy_app):
158 '''Returns the hw-health unit we've deployed'''154 """Returns the hw-health unit we've deployed"""
159 return deploy_app.units[0]155 return deploy_app.units[0]
160156
161157
162@pytest.fixture(scope='function')158@pytest.fixture(scope="function")
163async def toolset(monkeypatch):159async def toolset(monkeypatch):
164 # All tool classes know which files should be installed and how, so we can160 # All tool classes know which files should be installed and how, so we can
165 # use them to read the expected stat results. Monkeypatching is however161 # use them to read the expected stat results. Monkeypatching is however
166 # required as the classes code is not expected to be run outside of a162 # required as the classes code is not expected to be run outside of a
167 # deployed charm163 # deployed charm
168 with monkeypatch.context() as m:164 with monkeypatch.context() as m:
169 m.setattr('charmhelpers.core.hookenv.charm_dir',165 m.setattr("charmhelpers.core.hookenv.charm_dir", lambda: CHARM_BUILD_DIR)
170 lambda: CHARM_BUILD_DIR)166 m.setattr("charmhelpers.core.hookenv.config", lambda x=None: dict())
171 m.setattr('charmhelpers.core.hookenv.config',167 m.setattr(
172 lambda x=None: dict())168 "charmhelpers.contrib.charmsupport.nrpe.get_nagios_hostname",
173 m.setattr('charmhelpers.contrib.charmsupport.nrpe.get_nagios_hostname',169 lambda: "pytest",
174 lambda: 'pytest')170 )
175 return [tool() for tool in hwdiscovery.get_tools('test')]171 return [tool() for tool in hwdiscovery.get_tools("test")]
172
176173
177#########174#########
178# Tests #175# Tests #
@@ -180,69 +177,69 @@ async def toolset(monkeypatch):
180177
181178
182async def test_cannot_run_in_container(deploy_app):179async def test_cannot_run_in_container(deploy_app):
183 assert deploy_app.status == 'blocked'180 assert deploy_app.status == "blocked"
184181
185182
186async def test_forced_deploy(deploy_app, model, run_command):183async def test_forced_deploy(deploy_app, model, run_command):
187 # Create a fake NVMe device for the cronjob to be configured184 # Create a fake NVMe device for the cronjob to be configured
188 CREATE_FAKE_NVME = "/bin/bash -c 'touch /dev/nvme0'"185 CREATE_FAKE_NVME = "/bin/bash -c 'touch /dev/nvme0'"
189 series = deploy_app.name.split('-')[-1]186 series = deploy_app.name.split("-")[-1]
190 for unit in model.units.values():187 for unit in model.units.values():
191 if unit.entity_id.startswith('ubuntu-{}'.format(series)):188 if unit.entity_id.startswith("ubuntu-{}".format(series)):
192 ubuntu_unit = unit189 ubuntu_unit = unit
193 await model.block_until(190 await model.block_until(
194 lambda: ubuntu_unit.workload_status == 'active',191 lambda: ubuntu_unit.workload_status == "active", timeout=DEF_TIMEOUT
195 timeout=DEF_TIMEOUT
196 )192 )
197 await run_command(CREATE_FAKE_NVME, ubuntu_unit)193 await run_command(CREATE_FAKE_NVME, ubuntu_unit)
198 break194 break
199195
200 await deploy_app.set_config({'manufacturer': 'test'})196 await deploy_app.set_config({"manufacturer": "test"})
201 await model.block_until(197 await model.block_until(lambda: deploy_app.status == "active", timeout=DEF_TIMEOUT)
202 lambda: deploy_app.status == 'active',198 assert deploy_app.status == "active"
203 timeout=DEF_TIMEOUT
204 )
205 assert deploy_app.status == 'active'
206199
207200
208async def test_checksum_forced_deploy(deploy_app, model, run_command):201async def test_checksum_forced_deploy(deploy_app, model, run_command):
209 # Create a fake NVMe device for the cronjob to be configured202 # Create a fake NVMe device for the cronjob to be configured
210 CREATE_FAKE_NVME = "/bin/bash -c 'touch /dev/nvme0'"203 CREATE_FAKE_NVME = "/bin/bash -c 'touch /dev/nvme0'"
211 series = deploy_app.name.split('-')[-1]204 series = deploy_app.name.split("-")[-1]
212 checksum_app_name = 'hw-health-checksum-{}'.format(series)205 checksum_app_name = "hw-health-checksum-{}".format(series)
213 checksum_app = model.applications[checksum_app_name]206 checksum_app = model.applications[checksum_app_name]
214 for unit in model.units.values():207 for unit in model.units.values():
215 if unit.entity_id.startswith('ubuntu-checksum-{}'.format(series)):208 if unit.entity_id.startswith("ubuntu-checksum-{}".format(series)):
216 ubuntu_unit = unit209 ubuntu_unit = unit
217 await model.block_until(210 await model.block_until(
218 lambda: ubuntu_unit.workload_status == 'active',211 lambda: ubuntu_unit.workload_status == "active", timeout=DEF_TIMEOUT
219 timeout=DEF_TIMEOUT
220 )212 )
221 await run_command(CREATE_FAKE_NVME, ubuntu_unit)213 await run_command(CREATE_FAKE_NVME, ubuntu_unit)
222 elif unit.entity_id.startswith(checksum_app_name):214 elif unit.entity_id.startswith(checksum_app_name):
223 checksum_unit = unit215 checksum_unit = unit
224216
225 await checksum_app.set_config({'manufacturer': 'test'})217 await checksum_app.set_config({"manufacturer": "test"})
226 try:218 try:
227 await model.block_until(219 await model.block_until(
228 lambda: (220 lambda: (
229 checksum_app.status == 'blocked' and checksum_unit.workload_status_message == 'Tool megacli - checksum error' # noqa E501221 checksum_app.status == "blocked"
222 and checksum_unit.workload_status_message
223 == "Tool megacli - checksum error" # noqa E501
230 ),224 ),
231 timeout=DEF_TIMEOUT)225 timeout=DEF_TIMEOUT,
226 )
232 except asyncio.exceptions.TimeoutError:227 except asyncio.exceptions.TimeoutError:
233 print(228 print(
234 "failed to get expected state 'blocked:Tool megacli - checksum error', "229 "failed to get expected state 'blocked:Tool megacli - checksum error', "
235 "witnessed '{}:{}'".format(checksum_app.status, checksum_unit.workload_status_message)230 "witnessed '{}:{}'".format(
231 checksum_app.status, checksum_unit.workload_status_message
232 )
236 )233 )
237 assert checksum_app.status == 'blocked'234 assert checksum_app.status == "blocked"
238 assert checksum_unit.workload_status_message == 'Tool megacli - checksum error'235 assert checksum_unit.workload_status_message == "Tool megacli - checksum error"
239236
240237
241async def test_checksum_updated_resource_missing(deploy_app, model):238async def test_checksum_updated_resource_missing(deploy_app, model):
242 series = deploy_app.name.split('-')[-1]239 series = deploy_app.name.split("-")[-1]
243 checksum_app_name = 'hw-health-checksum-{}'.format(series)240 checksum_app_name = "hw-health-checksum-{}".format(series)
244 checksum_app = model.applications[checksum_app_name]241 checksum_app = model.applications[checksum_app_name]
245 await update_hwhealth_res(model, checksum_app_name, 'tools-missing.zip')242 await update_hwhealth_res(model, checksum_app_name, "tools-missing.zip")
246 for unit in model.units.values():243 for unit in model.units.values():
247 if unit.entity_id.startswith(checksum_app_name):244 if unit.entity_id.startswith(checksum_app_name):
248 checksum_unit = unit245 checksum_unit = unit
@@ -250,209 +247,242 @@ async def test_checksum_updated_resource_missing(deploy_app, model):
250247
251 await model.block_until(248 await model.block_until(
252 lambda: (249 lambda: (
253 checksum_app.status == 'blocked' and checksum_unit.workload_status_message == 'Tool megacli not found' # noqa E501250 checksum_app.status == "blocked"
251 and checksum_unit.workload_status_message
252 == "Tool megacli not found" # noqa E501
254 ),253 ),
255 timeout=DEF_TIMEOUT254 timeout=DEF_TIMEOUT,
256 )255 )
257256
258257
259async def test_checksum_updated_resource_ok(deploy_app, model):258async def test_checksum_updated_resource_ok(deploy_app, model):
260 series = deploy_app.name.split('-')[-1]259 series = deploy_app.name.split("-")[-1]
261 checksum_app_name = 'hw-health-checksum-{}'.format(series)260 checksum_app_name = "hw-health-checksum-{}".format(series)
262 checksum_app = model.applications[checksum_app_name]261 checksum_app = model.applications[checksum_app_name]
263 await update_hwhealth_res(model, checksum_app_name, 'tools.zip')262 await update_hwhealth_res(model, checksum_app_name, "tools.zip")
264 for unit in model.units.values():263 for unit in model.units.values():
265 if unit.entity_id.startswith(checksum_app_name):264 if unit.entity_id.startswith(checksum_app_name):
266 checksum_unit = unit265 checksum_unit = unit
267 break266 break
268267
269 await model.block_until(268 await model.block_until(
270 lambda: (checksum_app.status == 'active' and # noqa:W504269 lambda: (
271 checksum_unit.workload_status_message == 'ready'),270 checksum_app.status == "active"
272 timeout=DEF_TIMEOUT271 and checksum_unit.workload_status_message == "ready" # noqa:W504
272 ),
273 timeout=DEF_TIMEOUT,
273 )274 )
274275
275276
276async def test_deployed_file_stats(monkeypatch, toolset, deploy_app, deployed_unit, file_stat):277async def test_deployed_file_stats(
278 monkeypatch, toolset, deploy_app, deployed_unit, file_stat
279):
277 # This should really be a parametrized test, but fixtures cannot be used as280 # This should really be a parametrized test, but fixtures cannot be used as
278 # params value as if they were iterators281 # params value as if they were iterators
279 # It should also check for other installed files and differentiate between282 # It should also check for other installed files and differentiate between
280 # tool types (e.g. tools.Ipmi does not use a vendor binary)283 # tool types (e.g. tools.Ipmi does not use a vendor binary)
281 series = deploy_app.name.split('-')[-1]284 series = deploy_app.name.split("-")[-1]
282 for tool in toolset:285 for tool in toolset:
283 # Skip tools that are out of series for the currently deployed application286 # Skip tools that are out of series for the currently deployed application
284 with monkeypatch.context() as m:287 with monkeypatch.context() as m:
285 m.setattr('hwhealth.tools.lsb_release',288 m.setattr(
286 lambda x=None: {'DISTRIB_CODENAME': series})289 "hwhealth.tools.lsb_release",
287 m.setattr('charmhelpers.core.hookenv.config',290 lambda x=None: {"DISTRIB_CODENAME": series},
288 lambda x=None: {'manufacturer': 'test'})291 )
292 m.setattr(
293 "charmhelpers.core.hookenv.config",
294 lambda x=None: {"manufacturer": "test"},
295 )
289 if not tool.is_series_supported():296 if not tool.is_series_supported():
290 print('Skipping tool {}. Distribution {} not supported.'.format(tool, series))297 print(
298 "Skipping tool {}. Distribution {} not supported.".format(
299 tool, series
300 )
301 )
291 continue302 continue
292 # Have we rendered the nrpe check cfg?303 # Have we rendered the nrpe check cfg?
293 nrpecfg_path = os.path.join(NRPECFG_DIR,304 nrpecfg_path = os.path.join(NRPECFG_DIR, "check_{}.cfg".format(tool._shortname))
294 'check_{}.cfg'.format(tool._shortname))305 print("Checking {}".format(nrpecfg_path))
295 print('Checking {}'.format(nrpecfg_path))
296 test_stat = await file_stat(nrpecfg_path, deployed_unit)306 test_stat = await file_stat(nrpecfg_path, deployed_unit)
297 assert test_stat['size'] > 0307 assert test_stat["size"] > 0
298308
299 # Have we installed the nrpe check script?309 # Have we installed the nrpe check script?
300 nrpescript_path = os.path.join(tool.NRPE_PLUGINS_DIR,310 nrpescript_path = os.path.join(
301 os.path.basename(tool._nrpe_script))311 tool.NRPE_PLUGINS_DIR, os.path.basename(tool._nrpe_script)
302 print('Checking {}'.format(nrpescript_path))312 )
313 print("Checking {}".format(nrpescript_path))
303 test_stat = await file_stat(nrpescript_path, deployed_unit)314 test_stat = await file_stat(nrpescript_path, deployed_unit)
304 assert test_stat['size'] > 0315 assert test_stat["size"] > 0
305 assert test_stat['gid'] == tool.NRPE_PLUGINS_GID316 assert test_stat["gid"] == tool.NRPE_PLUGINS_GID
306 assert test_stat['uid'] == tool.NRPE_PLUGINS_UID317 assert test_stat["uid"] == tool.NRPE_PLUGINS_UID
307 assert test_stat['mode'] == oct(tool.NRPE_PLUGINS_MODE)318 assert test_stat["mode"] == oct(tool.NRPE_PLUGINS_MODE)
308319
309 # Have we installed any common libs?320 # Have we installed any common libs?
310 for lib in tool._common_libs:321 for lib in tool._common_libs:
311 lib_path = os.path.join(tool.NRPE_PLUGINS_DIR,322 lib_path = os.path.join(tool.NRPE_PLUGINS_DIR, os.path.basename(lib))
312 os.path.basename(lib))323 print("Checking {}".format(nrpescript_path))
313 print('Checking {}'.format(nrpescript_path))
314 test_stat = await file_stat(lib_path, deployed_unit)324 test_stat = await file_stat(lib_path, deployed_unit)
315 assert test_stat['size'] > 0325 assert test_stat["size"] > 0
316 assert test_stat['gid'] == tool.NRPE_PLUGINS_GID326 assert test_stat["gid"] == tool.NRPE_PLUGINS_GID
317 assert test_stat['uid'] == tool.NRPE_PLUGINS_UID327 assert test_stat["uid"] == tool.NRPE_PLUGINS_UID
318 assert test_stat['mode'] == oct(tool.NRPE_PLUGINS_MODE)328 assert test_stat["mode"] == oct(tool.NRPE_PLUGINS_MODE)
319329
320 if isinstance(tool, tools.Ipmi) or isinstance(tool, tools.Nvme):330 if isinstance(tool, tools.Ipmi) or isinstance(tool, tools.Nvme):
321 # Have we added sudo rights for running freeipmi commands?331 # Have we added sudo rights for running freeipmi commands?
322 sudoer_path = os.path.join(tool.SUDOERS_DIR, tool._sudoer_file)332 sudoer_path = os.path.join(tool.SUDOERS_DIR, tool._sudoer_file)
323 print('Checking {}'.format(sudoer_path))333 print("Checking {}".format(sudoer_path))
324 test_stat = await file_stat(sudoer_path, deployed_unit)334 test_stat = await file_stat(sudoer_path, deployed_unit)
325 assert test_stat['size'] > 0335 assert test_stat["size"] > 0
326 assert test_stat['gid'] == tool.SUDOERS_GID336 assert test_stat["gid"] == tool.SUDOERS_GID
327 assert test_stat['uid'] == tool.SUDOERS_UID337 assert test_stat["uid"] == tool.SUDOERS_UID
328 assert test_stat['mode'] == oct(tool.SUDOERS_MODE)338 assert test_stat["mode"] == oct(tool.SUDOERS_MODE)
329339
330 if isinstance(tool, tools.Ipmi) or isinstance(tool, tools.VendorTool):340 if isinstance(tool, tools.Ipmi) or isinstance(tool, tools.VendorTool):
331 # Have we installed the cronjob script helper?341 # Have we installed the cronjob script helper?
332 cron_script_path = os.path.join(tool.NRPE_PLUGINS_DIR,342 cron_script_path = os.path.join(tool.NRPE_PLUGINS_DIR, tool._cron_script)
333 tool._cron_script)343 print("Checking {}".format(cron_script_path))
334 print('Checking {}'.format(cron_script_path))
335 test_stat = await file_stat(cron_script_path, deployed_unit)344 test_stat = await file_stat(cron_script_path, deployed_unit)
336 assert test_stat['size'] > 0345 assert test_stat["size"] > 0
337 assert test_stat['gid'] == tool.CRONJOB_SCRIPT_GID346 assert test_stat["gid"] == tool.CRONJOB_SCRIPT_GID
338 assert test_stat['uid'] == tool.CRONJOB_SCRIPT_UID347 assert test_stat["uid"] == tool.CRONJOB_SCRIPT_UID
339 assert test_stat['mode'] == oct(tool.CRONJOB_SCRIPT_MODE)348 assert test_stat["mode"] == oct(tool.CRONJOB_SCRIPT_MODE)
340349
341 # Have we installed the cronjob itself?350 # Have we installed the cronjob itself?
342 cronjob_path = os.path.join(tool.CROND_DIR,351 cronjob_path = os.path.join(
343 'hwhealth_{}'.format(tool._shortname))352 tool.CROND_DIR, "hwhealth_{}".format(tool._shortname)
344 print('Checking {}'.format(cronjob_path))353 )
354 print("Checking {}".format(cronjob_path))
345 test_stat = await file_stat(cronjob_path, deployed_unit)355 test_stat = await file_stat(cronjob_path, deployed_unit)
346 assert test_stat['size'] > 0356 assert test_stat["size"] > 0
347357
348 if isinstance(tool, tools.VendorTool):358 if isinstance(tool, tools.VendorTool):
349 # Have we installed the vendor binary?359 # Have we installed the vendor binary?
350 if isinstance(tool, tools.Mdadm):360 if isinstance(tool, tools.Mdadm):
351 bin_path = os.path.join('/sbin', tool._shortname)361 bin_path = os.path.join("/sbin", tool._shortname)
352 else:362 else:
353 bin_path = os.path.join(tool.TOOLS_DIR, tool._shortname)363 bin_path = os.path.join(tool.TOOLS_DIR, tool._shortname)
354 print('Checking {}'.format(bin_path))364 print("Checking {}".format(bin_path))
355 test_stat = await file_stat(bin_path, deployed_unit)365 test_stat = await file_stat(bin_path, deployed_unit)
356 assert test_stat['size'] > 0366 assert test_stat["size"] > 0
357 assert test_stat['gid'] == tool.TOOLS_GID367 assert test_stat["gid"] == tool.TOOLS_GID
358 assert test_stat['uid'] == tool.TOOLS_UID368 assert test_stat["uid"] == tool.TOOLS_UID
359 assert test_stat['mode'] == oct(tool.TOOLS_MODE)369 assert test_stat["mode"] == oct(tool.TOOLS_MODE)
360370
361371
362@pytest.mark.parametrize('script_type', ['_nrpe_script', '_cron_script'])372@pytest.mark.parametrize("script_type", ["_nrpe_script", "_cron_script"])
363async def test_imports(script_type, monkeypatch, toolset, deploy_app, deployed_unit, run_command):373async def test_imports(
364 '''Dry run all auxiliary files to ensure we have all needed dependecies'''374 script_type, monkeypatch, toolset, deploy_app, deployed_unit, run_command
365 series = deploy_app.name.split('-')[-1]375):
376 """Dry run all auxiliary files to ensure we have all needed dependecies"""
377 series = deploy_app.name.split("-")[-1]
366 for tool in toolset:378 for tool in toolset:
367 # Skip tools that are out of series for the currently deployed application379 # Skip tools that are out of series for the currently deployed application
368 with monkeypatch.context() as m:380 with monkeypatch.context() as m:
369 m.setattr('hwhealth.tools.lsb_release',381 m.setattr(
370 lambda x=None: {'DISTRIB_CODENAME': series})382 "hwhealth.tools.lsb_release",
371 m.setattr('charmhelpers.core.hookenv.config',383 lambda x=None: {"DISTRIB_CODENAME": series},
372 lambda x=None: {'manufacturer': 'test'})384 )
385 m.setattr(
386 "charmhelpers.core.hookenv.config",
387 lambda x=None: {"manufacturer": "test"},
388 )
373 if not tool.is_series_supported():389 if not tool.is_series_supported():
374 print('Skipping tool {}. Distribution {} not supported.'.format(tool, series))390 print(
391 "Skipping tool {}. Distribution {} not supported.".format(
392 tool, series
393 )
394 )
375 continue395 continue
376 script_name = getattr(tool, script_type)396 script_name = getattr(tool, script_type)
377 tool_name = tool.__class__.__name__397 tool_name = tool.__class__.__name__
378 if not script_name:398 if not script_name:
379 # Cannot pytest.skip because it would break out of the loop399 # Cannot pytest.skip because it would break out of the loop
380 print('Skipping test as {} does not have a {}'400 print(
381 ''.format(tool_name, script_type))401 "Skipping test as {} does not have a {}"
402 "".format(tool_name, script_type)
403 )
382 else:404 else:
383 print('Checking {}: {}'.format(tool_name, script_name))405 print("Checking {}: {}".format(tool_name, script_name))
384 path = os.path.join(tool.NRPE_PLUGINS_DIR, script_name)406 path = os.path.join(tool.NRPE_PLUGINS_DIR, script_name)
385 cmd = path + " --help"407 cmd = path + " --help"
386 results = await run_command(cmd, deployed_unit)408 results = await run_command(cmd, deployed_unit)
387 rc = results['Code']409 rc = results["Code"]
388 assert rc == '0', ('{}, {}. RC is non-zero. results={}'410 assert rc == "0", "{}, {}. RC is non-zero. results={}".format(
389 ''.format(tool_name, script_name, results))411 tool_name, script_name, results
412 )
390413
391414
392async def test_removal(monkeypatch, toolset, model, deploy_app, file_stat):415async def test_removal(monkeypatch, toolset, model, deploy_app, file_stat):
393 '''Remove the unit, test that all files have been cleaned up'''416 """Remove the unit, test that all files have been cleaned up"""
394 hw_health_app_name = deploy_app.name417 hw_health_app_name = deploy_app.name
395 series = deploy_app.name.split('-')[-1]418 series = deploy_app.name.split("-")[-1]
396 await deploy_app.remove()419 await deploy_app.remove()
397 await model.block_until(420 await model.block_until(
398 lambda: hw_health_app_name not in model.applications,421 lambda: hw_health_app_name not in model.applications, timeout=DEF_TIMEOUT
399 timeout=DEF_TIMEOUT
400 )422 )
401 # Since we've removed the hw-health app, we can't target it anymore, we423 # Since we've removed the hw-health app, we can't target it anymore, we
402 # need to find the principal unit424 # need to find the principal unit
403 for unit in model.units.values():425 for unit in model.units.values():
404 if unit.entity_id.startswith('ubuntu-{}'.format(series)):426 if unit.entity_id.startswith("ubuntu-{}".format(series)):
405 ubuntu_unit = unit427 ubuntu_unit = unit
406 for tool in toolset:428 for tool in toolset:
407 # Skip tools that are out of series for the currently deployed application429 # Skip tools that are out of series for the currently deployed application
408 with monkeypatch.context() as m:430 with monkeypatch.context() as m:
409 m.setattr('hwhealth.tools.lsb_release',431 m.setattr(
410 lambda x=None: {'DISTRIB_CODENAME': series})432 "hwhealth.tools.lsb_release",
411 m.setattr('charmhelpers.core.hookenv.config',433 lambda x=None: {"DISTRIB_CODENAME": series},
412 lambda x=None: {'manufacturer': 'test'})434 )
435 m.setattr(
436 "charmhelpers.core.hookenv.config",
437 lambda x=None: {"manufacturer": "test"},
438 )
413 if not tool.is_series_supported():439 if not tool.is_series_supported():
414 print('Skipping tool {}. Distribution {} not supported.'.format(tool, series))440 print(
441 "Skipping tool {}. Distribution {} not supported.".format(
442 tool, series
443 )
444 )
415 continue445 continue
416 # Have we removed the nrpe check cfg?446 # Have we removed the nrpe check cfg?
417 nrpecfg_path = os.path.join(NRPECFG_DIR,447 nrpecfg_path = os.path.join(NRPECFG_DIR, "check_{}.cfg".format(tool._shortname))
418 'check_{}.cfg'.format(tool._shortname))448 print("Checking {}".format(nrpecfg_path))
419 print('Checking {}'.format(nrpecfg_path))
420 with pytest.raises(RuntimeError):449 with pytest.raises(RuntimeError):
421 await file_stat(nrpecfg_path, ubuntu_unit)450 await file_stat(nrpecfg_path, ubuntu_unit)
422451
423 # Have we removed the nrpe check script?452 # Have we removed the nrpe check script?
424 nrpescript_path = os.path.join(tool.NRPE_PLUGINS_DIR,453 nrpescript_path = os.path.join(tool.NRPE_PLUGINS_DIR, tool._nrpe_script)
425 tool._nrpe_script)454 print("Checking {}".format(nrpescript_path))
426 print('Checking {}'.format(nrpescript_path))
427 with pytest.raises(RuntimeError):455 with pytest.raises(RuntimeError):
428 await file_stat(nrpescript_path, ubuntu_unit)456 await file_stat(nrpescript_path, ubuntu_unit)
429457
430 if isinstance(tool, tools.Ipmi) or isinstance(tool, tools.Nvme):458 if isinstance(tool, tools.Ipmi) or isinstance(tool, tools.Nvme):
431 # Have we removed sudo rights for running freeipmi commands?459 # Have we removed sudo rights for running freeipmi commands?
432 sudoer_path = os.path.join(tool.SUDOERS_DIR, tool._sudoer_file)460 sudoer_path = os.path.join(tool.SUDOERS_DIR, tool._sudoer_file)
433 print('Checking {}'.format(sudoer_path))461 print("Checking {}".format(sudoer_path))
434 with pytest.raises(RuntimeError):462 with pytest.raises(RuntimeError):
435 await file_stat(sudoer_path, ubuntu_unit)463 await file_stat(sudoer_path, ubuntu_unit)
436464
437 if isinstance(tool, tools.Ipmi) or isinstance(tool, tools.VendorTool):465 if isinstance(tool, tools.Ipmi) or isinstance(tool, tools.VendorTool):
438 # Have we removed the cronjob script helper?466 # Have we removed the cronjob script helper?
439 cronjob_path = os.path.join(tool.NRPE_PLUGINS_DIR,467 cronjob_path = os.path.join(tool.NRPE_PLUGINS_DIR, tool._cron_script)
440 tool._cron_script)468 print("Checking {}".format(cronjob_path))
441 print('Checking {}'.format(cronjob_path))
442 with pytest.raises(RuntimeError):469 with pytest.raises(RuntimeError):
443 await file_stat(cronjob_path, ubuntu_unit)470 await file_stat(cronjob_path, ubuntu_unit)
444471
445 # Have we removed the cronjob itself?472 # Have we removed the cronjob itself?
446 cronjob_path = os.path.join(tool.CROND_DIR,473 cronjob_path = os.path.join(
447 'hwhealth_{}'.format(tool._shortname))474 tool.CROND_DIR, "hwhealth_{}".format(tool._shortname)
448 print('Checking {}'.format(cronjob_path))475 )
476 print("Checking {}".format(cronjob_path))
449 with pytest.raises(RuntimeError):477 with pytest.raises(RuntimeError):
450 await file_stat(cronjob_path, ubuntu_unit)478 await file_stat(cronjob_path, ubuntu_unit)
451479
452 if isinstance(tool, tools.VendorTool) and not isinstance(tool, tools.Mdadm): # noqa E501480 if isinstance(tool, tools.VendorTool) and not isinstance(
481 tool, tools.Mdadm
482 ): # noqa E501
453 # /sbin/mdadm will not be removed, but the vendor binaries483 # /sbin/mdadm will not be removed, but the vendor binaries
454 # should have been484 # should have been
455 bin_path = os.path.join(tool.TOOLS_DIR, tool._shortname)485 bin_path = os.path.join(tool.TOOLS_DIR, tool._shortname)
456 print('Checking {}'.format(bin_path))486 print("Checking {}".format(bin_path))
457 with pytest.raises(RuntimeError):487 with pytest.raises(RuntimeError):
458 await file_stat(bin_path, ubuntu_unit)488 await file_stat(bin_path, ubuntu_unit)
diff --git a/src/tests/unit/lib/samples.py b/src/tests/unit/lib/samples.py
index a0790ee..db34828 100644
--- a/src/tests/unit/lib/samples.py
+++ b/src/tests/unit/lib/samples.py
@@ -1,12 +1,7 @@
1import os1import os
2import glob2import glob
33
4SAMPLES_DIR = os.path.join(4SAMPLES_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "hw-health-samples")
5 os.path.dirname(__file__),
6 '..',
7 '..',
8 'hw-health-samples'
9)
105
116
12def get_sample(name):7def get_sample(name):
diff --git a/src/tests/unit/test_actions.py b/src/tests/unit/test_actions.py
index dfa7323..3a3f4f2 100644
--- a/src/tests/unit/test_actions.py
+++ b/src/tests/unit/test_actions.py
@@ -17,110 +17,149 @@ import sys
17import unittest17import unittest
18import unittest.mock as mock18import unittest.mock as mock
1919
20sys.path.append('.')20sys.path.append(".")
21from actions.actions import clear_sel, show_sel # noqa:E40221from actions.actions import clear_sel, show_sel # noqa:E402
2222
2323
24class ClearSelTestCase(unittest.TestCase):24class ClearSelTestCase(unittest.TestCase):
2525 @mock.patch("actions.actions.log")
26 @mock.patch('actions.actions.log')26 @mock.patch("subprocess.check_output")
27 @mock.patch('subprocess.check_output')27 @mock.patch("subprocess.check_call")
28 @mock.patch('subprocess.check_call')
29 def test_clear_sel(self, mock_check_call, mock_subprocess, mock_log):28 def test_clear_sel(self, mock_check_call, mock_subprocess, mock_log):
30 sel_output = "Unittest system event log output".encode()29 sel_output = "Unittest system event log output".encode()
31 mock_subprocess.return_value = sel_output30 mock_subprocess.return_value = sel_output
32 mock_check_call.return_value = None31 mock_check_call.return_value = None
33 clear_sel()32 clear_sel()
34 mock_check_call.assert_called_once_with(['action-set', "message={}".format(sel_output.decode())])33 mock_check_call.assert_called_once_with(
34 ["action-set", "message={}".format(sel_output.decode())]
35 )
3536
3637
37class ShowSelTestCase(unittest.TestCase):38class ShowSelTestCase(unittest.TestCase):
3839 @mock.patch("actions.actions.log")
39 @mock.patch('actions.actions.log')40 @mock.patch("actions.actions.action_set")
40 @mock.patch('actions.actions.action_set')41 @mock.patch("actions.actions.action_get")
41 @mock.patch('actions.actions.action_get')42 @mock.patch("subprocess.check_output")
42 @mock.patch('subprocess.check_output')
43 def test_empty_output_from_ipmi_sel(43 def test_empty_output_from_ipmi_sel(
44 self, mock_check_output, mock_action_get, mock_action_set, mock_log):44 self, mock_check_output, mock_action_get, mock_action_set, mock_log
45 ):
45 show_all_flag = False46 show_all_flag = False
46 output_body = ""47 output_body = ""
47 expected_output = "No matching entries found"48 expected_output = "No matching entries found"
48 self._test_valid_show_sel_call(show_all_flag, output_body, expected_output,49 self._test_valid_show_sel_call(
49 mock_check_output, mock_action_get, mock_action_set)50 show_all_flag,
51 output_body,
52 expected_output,
53 mock_check_output,
54 mock_action_get,
55 mock_action_set,
56 )
5057
51 @mock.patch('actions.actions.log')58 @mock.patch("actions.actions.log")
52 @mock.patch('actions.actions.action_set')59 @mock.patch("actions.actions.action_set")
53 @mock.patch('actions.actions.action_get')60 @mock.patch("actions.actions.action_get")
54 @mock.patch('subprocess.check_output')61 @mock.patch("subprocess.check_output")
55 def test_only_nominal_entries_with_show_all_false(62 def test_only_nominal_entries_with_show_all_false(
56 self, mock_check_output, mock_action_get, mock_action_set, mock_log):63 self, mock_check_output, mock_action_get, mock_action_set, mock_log
64 ):
57 show_all_flag = False65 show_all_flag = False
58 output_body = "\n".join([66 output_body = "\n".join(
59 "Header line",67 [
60 "Nominal body line #1",68 "Header line",
61 "Nominal body line #2",69 "Nominal body line #1",
62 "Nominal body line #3",70 "Nominal body line #2",
63 ])71 "Nominal body line #3",
72 ]
73 )
64 expected_output = "No matching entries found"74 expected_output = "No matching entries found"
65 self._test_valid_show_sel_call(show_all_flag, output_body, expected_output,75 self._test_valid_show_sel_call(
66 mock_check_output, mock_action_get, mock_action_set)76 show_all_flag,
77 output_body,
78 expected_output,
79 mock_check_output,
80 mock_action_get,
81 mock_action_set,
82 )
6783
68 @mock.patch('actions.actions.log')84 @mock.patch("actions.actions.log")
69 @mock.patch('actions.actions.action_set')85 @mock.patch("actions.actions.action_set")
70 @mock.patch('actions.actions.action_get')86 @mock.patch("actions.actions.action_get")
71 @mock.patch('subprocess.check_output')87 @mock.patch("subprocess.check_output")
72 def test_only_nominal_entries_with_show_all_true(88 def test_only_nominal_entries_with_show_all_true(
73 self, mock_check_output, mock_action_get, mock_action_set, mock_log):89 self, mock_check_output, mock_action_get, mock_action_set, mock_log
90 ):
74 show_all_flag = True91 show_all_flag = True
75 output_body = "\n".join([92 output_body = "\n".join(
76 "Header line",93 [
77 "Nominal body line #1",94 "Header line",
78 "Nominal body line #2",95 "Nominal body line #1",
79 "Nominal body line #3",96 "Nominal body line #2",
80 ])97 "Nominal body line #3",
98 ]
99 )
81 expected_output = output_body100 expected_output = output_body
82 self._test_valid_show_sel_call(show_all_flag, output_body, expected_output,101 self._test_valid_show_sel_call(
83 mock_check_output, mock_action_get, mock_action_set)102 show_all_flag,
103 output_body,
104 expected_output,
105 mock_check_output,
106 mock_action_get,
107 mock_action_set,
108 )
84109
85 @mock.patch('actions.actions.log')110 @mock.patch("actions.actions.log")
86 @mock.patch('actions.actions.action_set')111 @mock.patch("actions.actions.action_set")
87 @mock.patch('actions.actions.action_get')112 @mock.patch("actions.actions.action_get")
88 @mock.patch('subprocess.check_output')113 @mock.patch("subprocess.check_output")
89 def test_non_nominal_entries_present_with_show_all_false(114 def test_non_nominal_entries_present_with_show_all_false(
90 self, mock_check_output, mock_action_get, mock_action_set, mock_log):115 self, mock_check_output, mock_action_get, mock_action_set, mock_log
116 ):
91 show_all_flag = False117 show_all_flag = False
92 output_body = "\n".join([118 output_body = "\n".join(
93 "Header line",119 [
94 "Nominal body line #1",120 "Header line",
95 "Warning line #1",121 "Nominal body line #1",
96 "Critical line #1",122 "Warning line #1",
97 "Nominal body line #2",123 "Critical line #1",
98 "Nominal body line #3",124 "Nominal body line #2",
99 "Warning line #2",125 "Nominal body line #3",
100 ])126 "Warning line #2",
101 expected_output = "\n".join([127 ]
102 "Header line",128 )
103 "Warning line #1",129 expected_output = "\n".join(
104 "Critical line #1",130 ["Header line", "Warning line #1", "Critical line #1", "Warning line #2"]
105 "Warning line #2",131 )
106 ])132 self._test_valid_show_sel_call(
107 self._test_valid_show_sel_call(show_all_flag, output_body, expected_output,133 show_all_flag,
108 mock_check_output, mock_action_get, mock_action_set)134 output_body,
135 expected_output,
136 mock_check_output,
137 mock_action_get,
138 mock_action_set,
139 )
109140
110 def _test_valid_show_sel_call(self, show_all_flag, output_body, expected_output,141 def _test_valid_show_sel_call(
111 mock_check_output, mock_action_get, mock_action_set):142 self,
143 show_all_flag,
144 output_body,
145 expected_output,
146 mock_check_output,
147 mock_action_get,
148 mock_action_set,
149 ):
112 mock_action_get.return_value = show_all_flag150 mock_action_get.return_value = show_all_flag
113 mock_check_output.return_value = output_body.encode()151 mock_check_output.return_value = output_body.encode()
114 show_sel()152 show_sel()
115 self.assertEqual(mock_action_set.call_args[0][0]['message'],153 self.assertEqual(mock_action_set.call_args[0][0]["message"], expected_output)
116 expected_output)
117154
118 @mock.patch('actions.actions.action_fail')155 @mock.patch("actions.actions.action_fail")
119 @mock.patch('actions.actions.action_get')156 @mock.patch("actions.actions.action_get")
120 @mock.patch('subprocess.check_output')157 @mock.patch("subprocess.check_output")
121 def test_subprocess_error(self, mock_check_output, mock_action_get, mock_action_fail):158 def test_subprocess_error(
159 self, mock_check_output, mock_action_get, mock_action_fail
160 ):
122 def raise_error(*args, **kwargs):161 def raise_error(*args, **kwargs):
123 raise subprocess.CalledProcessError(1, ['bogus-cmd'])162 raise subprocess.CalledProcessError(1, ["bogus-cmd"])
124163
125 show_all_flag = False164 show_all_flag = False
126 mock_action_get.return_value = show_all_flag165 mock_action_get.return_value = show_all_flag
@@ -128,4 +167,6 @@ class ShowSelTestCase(unittest.TestCase):
128 show_sel()167 show_sel()
129 self.assertEqual(168 self.assertEqual(
130 mock_action_fail.call_args[0][0],169 mock_action_fail.call_args[0][0],
131 "Action failed with Command '['bogus-cmd']' returned non-zero exit status 1.")170 "Action failed with Command '['bogus-cmd']' "
171 "returned non-zero exit status 1.",
172 )
diff --git a/src/tests/unit/test_check_mdadm.py b/src/tests/unit/test_check_mdadm.py
index ca20633..fe7e357 100644
--- a/src/tests/unit/test_check_mdadm.py
+++ b/src/tests/unit/test_check_mdadm.py
@@ -6,67 +6,56 @@ import unittest.mock as mock
66
7import nagios_plugin37import nagios_plugin3
88
9sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))9sys.path.append(os.path.join(os.path.dirname(__file__), "lib"))
10from samples import get_sample # noqa: E40210from samples import get_sample # noqa: E402
1111
12sys.path.append(os.path.join(os.path.dirname(__file__), '../../files/mdadm'))12sys.path.append(os.path.join(os.path.dirname(__file__), "../../files/mdadm"))
13import check_mdadm # noqa: E40213import check_mdadm # noqa: E402
1414
1515
16class TestCheckMdadm(unittest.TestCase):16class TestCheckMdadm(unittest.TestCase):
17 def setUp(self):17 def setUp(self):
18 self.samples_dir = os.path.join(18 self.samples_dir = os.path.join(os.getcwd(), "tests", "hw-health-samples")
19 os.getcwd(),
20 'tests',
21 'hw-health-samples'
22 )
2319
24 def test_parse_output_crit(self):20 def test_parse_output_crit(self):
25 check_mdadm.ARGS.input_file = get_sample('mdadm.output.nrpe.critical')21 check_mdadm.ARGS.input_file = get_sample("mdadm.output.nrpe.critical")
26 expected = 'CRITICAL: critical msg'22 expected = "CRITICAL: critical msg"
27 with self.assertRaises(nagios_plugin3.CriticalError) as context:23 with self.assertRaises(nagios_plugin3.CriticalError) as context:
28 check_mdadm.parse_output()24 check_mdadm.parse_output()
29 self.assertTrue(expected in str(context.exception))25 self.assertTrue(expected in str(context.exception))
3026
31 def test_parse_output_warn(self):27 def test_parse_output_warn(self):
32 check_mdadm.ARGS.input_file = get_sample('mdadm.output.nrpe.warning')28 check_mdadm.ARGS.input_file = get_sample("mdadm.output.nrpe.warning")
33 expected = 'WARNING: warning msg'29 expected = "WARNING: warning msg"
34 with self.assertRaises(nagios_plugin3.WarnError) as context:30 with self.assertRaises(nagios_plugin3.WarnError) as context:
35 check_mdadm.parse_output()31 check_mdadm.parse_output()
36 self.assertTrue(expected in str(context.exception))32 self.assertTrue(expected in str(context.exception))
3733
38 @mock.patch('sys.stdout', new_callable=io.StringIO)34 @mock.patch("sys.stdout", new_callable=io.StringIO)
39 def test_parse_output_ok(self, mock_print):35 def test_parse_output_ok(self, mock_print):
40 check_mdadm.ARGS.input_file = get_sample('mdadm.output.nrpe.ok')36 check_mdadm.ARGS.input_file = get_sample("mdadm.output.nrpe.ok")
41 check_mdadm.parse_output()37 check_mdadm.parse_output()
42 self.assertEqual(38 self.assertEqual(
43 mock_print.getvalue(),39 mock_print.getvalue(),
44 'OK: /dev/md0 ok; /dev/md1 ok; /dev/md3 ok; /dev/md2 ok\n'40 "OK: /dev/md0 ok; /dev/md1 ok; /dev/md3 ok; /dev/md2 ok\n",
45 )41 )
4642
47 @mock.patch('sys.stdout', new_callable=io.StringIO)43 @mock.patch("sys.stdout", new_callable=io.StringIO)
48 def test_parse_output_unknown_filenotfound(self, mock_print):44 def test_parse_output_unknown_filenotfound(self, mock_print):
49 check_mdadm.ARGS.input_file = get_sample('thisfiledoesnotexist')45 check_mdadm.ARGS.input_file = get_sample("thisfiledoesnotexist")
50 expected = 'UNKNOWN: file not found ({})'.format(46 expected = "UNKNOWN: file not found ({})".format(check_mdadm.ARGS.input_file)
51 check_mdadm.ARGS.input_file)
52 with self.assertRaises(nagios_plugin3.UnknownError) as context:47 with self.assertRaises(nagios_plugin3.UnknownError) as context:
53 check_mdadm.parse_output()48 check_mdadm.parse_output()
54 self.assertTrue(expected in str(context.exception))49 self.assertTrue(expected in str(context.exception))
5550
56 @mock.patch('sys.stdout', new_callable=io.StringIO)51 @mock.patch("sys.stdout", new_callable=io.StringIO)
57 def test_parse_output_unknown1(self, mock_print):52 def test_parse_output_unknown1(self, mock_print):
58 check_mdadm.ARGS.input_file = get_sample('mdadm.output.nrpe.unknown.1')53 check_mdadm.ARGS.input_file = get_sample("mdadm.output.nrpe.unknown.1")
59 check_mdadm.parse_output()54 check_mdadm.parse_output()
60 self.assertEqual(55 self.assertEqual(mock_print.getvalue(), "UNKNOWN: unknown msg\n")
61 mock_print.getvalue(),
62 'UNKNOWN: unknown msg\n'
63 )
6456
65 @mock.patch('sys.stdout', new_callable=io.StringIO)57 @mock.patch("sys.stdout", new_callable=io.StringIO)
66 def test_parse_output_unknown2(self, mock_print):58 def test_parse_output_unknown2(self, mock_print):
67 check_mdadm.ARGS.input_file = get_sample('mdadm.output.nrpe.unknown.2')59 check_mdadm.ARGS.input_file = get_sample("mdadm.output.nrpe.unknown.2")
68 check_mdadm.parse_output()60 check_mdadm.parse_output()
69 self.assertEqual(61 self.assertEqual(mock_print.getvalue(), "unknown msg2\n")
70 mock_print.getvalue(),
71 'unknown msg2\n'
72 )
diff --git a/src/tests/unit/test_check_megacli.py b/src/tests/unit/test_check_megacli.py
index 00b9f4e..a6e7501 100644
--- a/src/tests/unit/test_check_megacli.py
+++ b/src/tests/unit/test_check_megacli.py
@@ -6,42 +6,40 @@ import unittest.mock as mock
66
7import nagios_plugin37import nagios_plugin3
88
9sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))9sys.path.append(os.path.join(os.path.dirname(__file__), "lib"))
10from samples import get_sample # noqa: E40210from samples import get_sample # noqa: E402
1111
12sys.path.append(os.path.join(os.path.dirname(__file__), '../../files/megacli'))12sys.path.append(os.path.join(os.path.dirname(__file__), "../../files/megacli"))
13import check_megacli # noqa: E40213import check_megacli # noqa: E402
1414
1515
16class TestCheckMegaCLI(unittest.TestCase):16class TestCheckMegaCLI(unittest.TestCase):
17 def setUp(self):17 def setUp(self):
18 self.samples_dir = os.path.join(18 self.samples_dir = os.path.join(os.getcwd(), "tests", "hw-health-samples")
19 os.getcwd(),
20 'tests',
21 'hw-health-samples'
22 )
2319
24 @mock.patch('sys.stdout', new_callable=io.StringIO)20 @mock.patch("sys.stdout", new_callable=io.StringIO)
25 def test_parse_output(self, mock_print):21 def test_parse_output(self, mock_print):
26 check_megacli.ARGS.input_file = get_sample('megacli.output.1')22 check_megacli.ARGS.input_file = get_sample("megacli.output.1")
27 check_megacli.parse_output()23 check_megacli.parse_output()
28 actual = mock_print.getvalue()24 actual = mock_print.getvalue()
29 expected = 'OK: Optimal, ldrives[1], pdrives[4]\n'25 expected = "OK: Optimal, ldrives[1], pdrives[4]\n"
30 self.assertEqual(actual, expected)26 self.assertEqual(actual, expected)
3127
32 @mock.patch('sys.stdout', new_callable=io.StringIO)28 @mock.patch("sys.stdout", new_callable=io.StringIO)
33 def test_parse_output_critical_singledrive(self, mock_print):29 def test_parse_output_critical_singledrive(self, mock_print):
34 check_megacli.ARGS.input_file = get_sample('megacli.output.nrpe.critical.1')30 check_megacli.ARGS.input_file = get_sample("megacli.output.nrpe.critical.1")
35 expected = 'CRITICAL: adapter(0):ld(0):state(Degraded)'31 expected = "CRITICAL: adapter(0):ld(0):state(Degraded)"
36 with self.assertRaises(nagios_plugin3.CriticalError) as context:32 with self.assertRaises(nagios_plugin3.CriticalError) as context:
37 check_megacli.parse_output()33 check_megacli.parse_output()
38 self.assertEqual(expected, str(context.exception))34 self.assertEqual(expected, str(context.exception))
3935
40 @mock.patch('sys.stdout', new_callable=io.StringIO)36 @mock.patch("sys.stdout", new_callable=io.StringIO)
41 def test_parse_output_critical_multiple(self, mock_print):37 def test_parse_output_critical_multiple(self, mock_print):
42 check_megacli.ARGS.input_file = get_sample('megacli.output.nrpe.critical.2')38 check_megacli.ARGS.input_file = get_sample("megacli.output.nrpe.critical.2")
43 expected = ('CRITICAL: adapter(0):ld(0):state(Degraded);'39 expected = (
44 ' adapter(0):ld(4):state(Degraded)')40 "CRITICAL: adapter(0):ld(0):state(Degraded);"
41 " adapter(0):ld(4):state(Degraded)"
42 )
45 with self.assertRaises(nagios_plugin3.CriticalError) as context:43 with self.assertRaises(nagios_plugin3.CriticalError) as context:
46 check_megacli.parse_output()44 check_megacli.parse_output()
47 self.assertEqual(expected, str(context.exception))45 self.assertEqual(expected, str(context.exception))
diff --git a/src/tests/unit/test_check_nvme.py b/src/tests/unit/test_check_nvme.py
index 097fd76..4218a85 100644
--- a/src/tests/unit/test_check_nvme.py
+++ b/src/tests/unit/test_check_nvme.py
@@ -4,21 +4,21 @@ import sys
4import unittest4import unittest
5import unittest.mock as mock5import unittest.mock as mock
66
7sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))7sys.path.append(os.path.join(os.path.dirname(__file__), "lib"))
8from samples import get_sample # noqa: E4028from samples import get_sample # noqa: E402
99
10sys.path.append(os.path.join(os.path.dirname(__file__), '../../files/nvme'))10sys.path.append(os.path.join(os.path.dirname(__file__), "../../files/nvme"))
11import check_nvme # noqa: E40211import check_nvme # noqa: E402
1212
1313
14class TestCheckNvme(unittest.TestCase):14class TestCheckNvme(unittest.TestCase):
15 @mock.patch('check_nvme.glob.glob')15 @mock.patch("check_nvme.glob.glob")
16 @mock.patch('check_nvme.subprocess.check_output')16 @mock.patch("check_nvme.subprocess.check_output")
17 @mock.patch('sys.stdout', new_callable=io.StringIO)17 @mock.patch("sys.stdout", new_callable=io.StringIO)
18 def test_parse_output(self, mock_print, mock_subprocess, mock_glob):18 def test_parse_output(self, mock_print, mock_subprocess, mock_glob):
19 mock_glob.return_value = ['/dev/nvme0']19 mock_glob.return_value = ["/dev/nvme0"]
20 input_file = get_sample('nvme.output.1')20 input_file = get_sample("nvme.output.1")
21 with open(input_file, 'r') as fd:21 with open(input_file, "r") as fd:
22 mock_subprocess.return_value = fd.read().encode()22 mock_subprocess.return_value = fd.read().encode()
23 check_nvme.parse_output()23 check_nvme.parse_output()
24 expected = (24 expected = (
diff --git a/src/tests/unit/test_check_sas2ircu.py b/src/tests/unit/test_check_sas2ircu.py
index cd5e854..e2053d7 100644
--- a/src/tests/unit/test_check_sas2ircu.py
+++ b/src/tests/unit/test_check_sas2ircu.py
@@ -4,18 +4,18 @@ import sys
4import unittest4import unittest
5import unittest.mock as mock5import unittest.mock as mock
66
7sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))7sys.path.append(os.path.join(os.path.dirname(__file__), "lib"))
8from samples import get_sample # noqa: E4028from samples import get_sample # noqa: E402
99
10sys.path.append(os.path.join(os.path.dirname(__file__), '../../files/sas2ircu'))10sys.path.append(os.path.join(os.path.dirname(__file__), "../../files/sas2ircu"))
11import check_sas2ircu # noqa: E40211import check_sas2ircu # noqa: E402
1212
1313
14class TestCheckMegaCLI(unittest.TestCase):14class TestCheckMegaCLI(unittest.TestCase):
15 @mock.patch('sys.stdout', new_callable=io.StringIO)15 @mock.patch("sys.stdout", new_callable=io.StringIO)
16 def test_parse_output(self, mock_print):16 def test_parse_output(self, mock_print):
17 check_sas2ircu.ARGS.input_file = get_sample('sas2ircu.huawei.output.1')17 check_sas2ircu.ARGS.input_file = get_sample("sas2ircu.huawei.output.1")
18 check_sas2ircu.parse_output()18 check_sas2ircu.parse_output()
19 actual = mock_print.getvalue()19 actual = mock_print.getvalue()
20 expected = 'OK: Ready[1:0,1:1,1:2,1:3,1:4,1:5,1:6,1:7]\n'20 expected = "OK: Ready[1:0,1:1,1:2,1:3,1:4,1:5,1:6,1:7]\n"
21 self.assertEqual(actual, expected)21 self.assertEqual(actual, expected)
diff --git a/src/tests/unit/test_check_sas3ircu.py b/src/tests/unit/test_check_sas3ircu.py
index 1379369..bb79688 100644
--- a/src/tests/unit/test_check_sas3ircu.py
+++ b/src/tests/unit/test_check_sas3ircu.py
@@ -4,19 +4,19 @@ import sys
4import unittest4import unittest
5import unittest.mock as mock5import unittest.mock as mock
66
7sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))7sys.path.append(os.path.join(os.path.dirname(__file__), "lib"))
8from samples import get_sample # noqa: E4028from samples import get_sample # noqa: E402
99
10sys.path.append(os.path.join(os.path.dirname(__file__), '../../files/sas3ircu'))10sys.path.append(os.path.join(os.path.dirname(__file__), "../../files/sas3ircu"))
11import check_sas3ircu # noqa: E40211import check_sas3ircu # noqa: E402
1212
1313
14class TestCheckMegaCLI(unittest.TestCase):14class TestCheckMegaCLI(unittest.TestCase):
15 @mock.patch('sys.stdout', new_callable=io.StringIO)15 @mock.patch("sys.stdout", new_callable=io.StringIO)
16 def test_parse_output_ok(self, mock_print):16 def test_parse_output_ok(self, mock_print):
17 _filepath = get_sample('sas3ircu.supermicro.ok.output.1')17 _filepath = get_sample("sas3ircu.supermicro.ok.output.1")
18 data = check_sas3ircu.parse_output(_filepath)18 data = check_sas3ircu.parse_output(_filepath)
19 check_sas3ircu.eval_status(data)19 check_sas3ircu.eval_status(data)
20 actual = mock_print.getvalue()20 actual = mock_print.getvalue()
21 expected = 'OK: no errors\n'21 expected = "OK: no errors\n"
22 self.assertEqual(actual, expected)22 self.assertEqual(actual, expected)
diff --git a/src/tests/unit/test_cron_hplog.py b/src/tests/unit/test_cron_hplog.py
index 87c5034..b17904e 100644
--- a/src/tests/unit/test_cron_hplog.py
+++ b/src/tests/unit/test_cron_hplog.py
@@ -4,27 +4,30 @@ import unittest
4from argparse import Namespace4from argparse import Namespace
5from pathlib import Path5from pathlib import Path
66
7sys.path.append('files/hplog')7sys.path.append("files/hplog")
8import cron_hplog # noqa: E4028import cron_hplog # noqa: E402
99
1010
11class TestCronHPlog(unittest.TestCase):11class TestCronHPlog(unittest.TestCase):
12 def setUp(self):12 def setUp(self):
13 # Skip the v flag, it serves a different purpose13 # Skip the v flag, it serves a different purpose
14 self.test_flags = {'t', 'f', 'p'}14 self.test_flags = {"t", "f", "p"}
1515
16 def _get_no_error_sample(self, flag):16 def _get_no_error_sample(self, flag):
17 _filepath = os.path.join(os.getcwd(), 'tests', 'hw-health-samples',17 _filepath = os.path.join(
18 'hplog.{}.ewah.out'.format(flag))18 os.getcwd(), "tests", "hw-health-samples", "hplog.{}.ewah.out".format(flag)
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches

to all changes: