Merge ~aieri/charm-hw-health:lp1832906 into ~nagios-charmers/charm-hw-health:master

Proposed by Andrea Ieri
Status: Superseded
Proposed branch: ~aieri/charm-hw-health:lp1832906
Merge into: ~nagios-charmers/charm-hw-health:master
Diff against target: 814 lines (+306/-102)
17 files modified
src/README.md (+25/-0)
src/files/ipmi/cron_ipmi_sensors.py (+15/-11)
src/files/mdadm/cron_mdadm.py (+100/-36)
src/lib/hwhealth/tools.py (+9/-2)
src/metadata.yaml (+2/-1)
src/tests/download_nagios_plugin3.py (+1/-1)
src/tests/functional/test_hwhealth.py (+15/-4)
src/tests/hw-health-samples/mdadm.output.critical.2 (+33/-0)
src/tests/hw-health-samples/mdadm.output.warning (+33/-0)
src/tests/unit/test_check_mdadm.py (+10/-18)
src/tests/unit/test_check_megacli.py (+7/-7)
src/tests/unit/test_check_nvme.py (+5/-3)
src/tests/unit/test_check_sas2ircu.py (+5/-4)
src/tests/unit/test_check_sas3ircu.py (+5/-3)
src/tests/unit/test_cron_mdadm.py (+35/-8)
src/tests/unit/test_hwdiscovery.py (+5/-4)
src/tox.ini (+1/-0)
Reviewer Review Type Date Requested Status
Stuart Bishop Pending
Drew Freiberger Pending
Xav Paice Pending
BootStack Reviewers Pending
Giuseppe Petralia Pending
Review via email: mp+385837@code.launchpad.net

This proposal supersedes a proposal from 2019-10-29.

Commit message

Fix issue with cron_mdadm.py which causes degraded state to not be reported

Description of the change

    Fix issue with cron_mdadm.py which causes degraded state to not be reported

    There was a formatting assumption which broke the detection of the degraded
    flag in the State section of each device report from mdadm --detail <devices>
    This merge adds code to split the State flags and check for both degraded and
    recovering states and sets alert status based on the combination of states.

    Also added is the direct detection of a "removed" member of the raid.

    Closes-Bug: 1832906

To post a comment you must log in.
Revision history for this message
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : Posted in a previous version of this proposal

This merge proposal is being monitored by mergebot. Change the status to Approved to merge.

Revision history for this message
Alvaro Uria (aluria) wrote : Posted in a previous version of this proposal

Logic looks good to me. Unit tests exist for this script, which should be extended for the extra flags (rebuilding, "clean, degraded", recovering...). Thank you!

Revision history for this message
Stuart Bishop (stub) wrote : Posted in a previous version of this proposal

All looks good, apart from the use of os.getcwd() in tests per inline comments.

review: Needs Fixing
Revision history for this message
Drew Freiberger (afreiberger) wrote : Posted in a previous version of this proposal

Those tests were directly copied from the tests above and are working as-is. This is a critical patch for maintenance kicking off in 24 hours. I believe this should be approved per the "follow the style of the code already present" and a new bug filed against the charm to clean up all tests for this issue.

Revision history for this message
Giuseppe Petralia (peppepetra) wrote : Posted in a previous version of this proposal

All Looks good to me

review: Approve
Revision history for this message
Xav Paice (xavpaice) wrote : Posted in a previous version of this proposal

https://code.launchpad.net/~peter-sabaini/hw-health-charm/+git/hw-health-charm/+merge/378123 starts to address the getcwd() issues, I think we can complete that change with these fixed too after a rebase.

Revision history for this message
Xav Paice (xavpaice) wrote : Posted in a previous version of this proposal

This change unfortunately needs rebasing against master, currently there's more merge conflicts than Git can handle.

review: Needs Fixing
Revision history for this message
Drew Freiberger (afreiberger) wrote : Posted in a previous version of this proposal

Change has been rebased and has had work from both Andrea and Peter to resolve comments from Stu.

Revision history for this message
Drew Freiberger (afreiberger) wrote : Posted in a previous version of this proposal

ready for review

review: Needs Resubmitting
Revision history for this message
Drew Freiberger (afreiberger) wrote : Posted in a previous version of this proposal
Revision history for this message
Stuart Bishop (stub) wrote : Posted in a previous version of this proposal

Looks good. Some inline comments on some things worth fixing before landing, in particular moving the lint pragmas into global flake8 config since black is just going to keep making more of them. There are also some remaining os.getcwd() calls in the test suite that can easily be replaced by making them relative to the TESTS_DIR constant that now exists, making the test suite less fragile.

review: Approve
Revision history for this message
Drew Freiberger (afreiberger) wrote : Posted in a previous version of this proposal

Thanks for the feedback! Thanks for picking up those bits. There are some clunky bits that could use some clean up after yesterday's merge of other contributions and rebase.

Unmerged commits

6b6fa66... by Andrea Ieri

Refactor obtaining samples into a separate shared function

Make imports more robust by switching to absolute paths

cb933c9... by Drew Freiberger

Add test sample inputs for new mdadm tests

0d7caac... by Drew Freiberger

Updated unit tests for cron_mdadm.py to include new failures

8375697... by Drew Freiberger

Fix issue with cron_mdadm.py which causes degraded state to not be reported

There was a formatting assumption which broke the detection of the degraded
flag in the State section of each device report from mdadm --detail <devices>
This merge adds code to split the State flags and check for both degraded and
recovering states and sets alert status based on the combination of states.

Also added is the direct detection of a "removed" member of the raid.

Closes-Bug: 1832906

09564d9... by Márton Kiss

Fix LP#1882978 ipmi check alert logic

Fix the logic of ipmi_sensors.out file creation and exception
handling.

9896fd4... by Alvaro Uria

Merge remote-tracking branch 'origin/str-bytes-conversion'

Reviewed-on: https://code.launchpad.net/~llama-charmers/charm-hw-health/+git/charm-hw-health/+merge/384228
Reviewed-by: Xav Paice <email address hidden>
Reviewed-by: Alvaro Uria <email address hidden>
Signed-off-by: Alvaro Uria <email address hidden>

d4bf6d5... by Alvaro Uria

Merge branch 'feature/add-focal'

Reviewed-on: https://code.launchpad.net/~canonical-is-bootstack/charm-hw-health/+git/charm-hw-health/+merge/383853
Reviewed-by: Paul Goins <email address hidden>
Signed-off-by: Alvaro Uria <email address hidden>

93b270d... by Alvaro Uria

Add README details, MegaCli64 checksum

8c01c0d... by Joe Guo

cron_ipmi_sensors.py: rm encoding header

should defaults to utf-8.

Signed-off-by: Joe Guo <email address hidden>

56169b4... by Joe Guo

cron_ipmi_sensors.py: subprocess.check_output decode

`subprocess.check_output` always return bytes, even in python3.
decode output to str for python3.

Signed-off-by: Joe Guo <email address hidden>

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/src/README.md b/src/README.md
2index 12a33f0..45b48ed 100644
3--- a/src/README.md
4+++ b/src/README.md
5@@ -87,6 +87,31 @@ Zip file size: 1204457 bytes, number of entries: 3
6 3 files, 3842044 bytes uncompressed, 1204005 bytes compressed: 68.7%
7 ```
8
9+Two more zip resources may be needed for functional tests to succeed:
10+ * tools-checksum.zip replaces the megacli tool by an empty file.
11+ * tools-missing.zip removes the megacli tool from the resource
12+
13+```
14+$ zipinfo tools-checksum.zip
15+Archive: tools-checksum.zip
16+Zip file size: 547860 bytes, number of entries: 3
17+-rwxr-xr-x 3.0 unx 0 bx stor 19-Jan-16 11:35 megacli
18+-rwxr-xr-x 3.0 unx 559164 bx defN 19-Jan-16 11:31 sas2ircu
19+-rwxr-xr-x 3.0 unx 660560 bx defN 19-Jan-16 11:31 sas3ircu
20+3 files, 1219724 bytes uncompressed, 547408 bytes compressed: 55.1%
21+
22+$ zipinfo tools-missing.zip
23+Archive: tools-missing.zip
24+Zip file size: 547718 bytes, number of entries: 2
25+-rwxr-xr-x 3.0 unx 559164 bx defN 19-Jan-16 11:31 sas2ircu
26+-rwxr-xr-x 3.0 unx 660560 bx defN 19-Jan-16 11:31 sas3ircu
27+2 files, 1219724 bytes uncompressed, 547408 bytes compressed: 55.1%
28+```
29+
30+Note: vendor tools may be updated over time. The charm verifies that the shared
31+binaries match a set of known checksums. If you feel a checksum is missing, please
32+file a bug (see link below) and it will be added.
33+
34 # Configuration
35
36 Manufacturer option needs to be left in auto mode.
37diff --git a/src/files/ipmi/cron_ipmi_sensors.py b/src/files/ipmi/cron_ipmi_sensors.py
38index a11f9fb..2b6cdd5 100644
39--- a/src/files/ipmi/cron_ipmi_sensors.py
40+++ b/src/files/ipmi/cron_ipmi_sensors.py
41@@ -1,5 +1,4 @@
42 #!/usr/bin/env python3
43-# -*- coding: us-ascii -*-
44
45 import os
46 import subprocess
47@@ -16,6 +15,16 @@ NAGIOS_ERRORS = {
48 }
49
50
51+def write_output_file(output):
52+ try:
53+ with open(TMP_OUTPUT_FILE, 'w') as fd:
54+ fd.write(output)
55+ except IOError as e:
56+ print("Cannot write output file {}, error {}".format(TMP_OUTPUT_FILE, e))
57+ sys.exit(1)
58+ os.rename(TMP_OUTPUT_FILE, OUTPUT_FILE)
59+
60+
61 def gather_metrics():
62 # Check if a PID file exists
63 if os.path.exists(CHECK_IPMI_PID):
64@@ -40,18 +49,13 @@ def gather_metrics():
65 if len(sys.argv) > 1:
66 cmdline.extend(sys.argv[1:])
67 try:
68- output = subprocess.check_output(cmdline)
69+ output = subprocess.check_output(cmdline).decode('utf8')
70+ write_output_file(output)
71 except subprocess.CalledProcessError as error:
72 output = error.stdout.decode(errors='ignore')
73- with open(TMP_OUTPUT_FILE, 'w') as fd:
74- fd.write('{}: {}'.format(NAGIOS_ERRORS[error.returncode], output))
75- try:
76- with open(TMP_OUTPUT_FILE, 'w') as fd:
77- fd.write(output)
78- except IOError as e:
79- print("Cannot write output file {}, error {}".format(TMP_OUTPUT_FILE, e))
80- sys.exit(1)
81- os.rename(TMP_OUTPUT_FILE, OUTPUT_FILE)
82+ write_output_file('{}: {}'.format(NAGIOS_ERRORS[error.returncode], output))
83+ except PermissionError as error:
84+ write_output_file('UNKNOWN: {}'.format(error))
85
86 # remove pid reference
87 os.remove(CHECK_IPMI_PID)
88diff --git a/src/files/mdadm/cron_mdadm.py b/src/files/mdadm/cron_mdadm.py
89index 0b0b365..d57c44c 100755
90--- a/src/files/mdadm/cron_mdadm.py
91+++ b/src/files/mdadm/cron_mdadm.py
92@@ -6,24 +6,28 @@ import shutil
93 import subprocess
94 import sys
95
96-OUTPUT_FILE = "/var/lib/nagios/mdadm.out"
97-TEMP_FILE = "/tmp/mdadm.out"
98+OUTPUT_FILE = '/var/lib/nagios/mdadm.out'
99+TEMP_FILE = '/tmp/mdadm.out'
100
101
102 def get_devices():
103- if os.path.exists("/sbin/mdadm"):
104+ if os.path.exists('/sbin/mdadm'):
105 try:
106- devices_raw = subprocess.check_output(["/sbin/mdadm", "--detail", "--scan"])
107- devices_re = re.compile(r"^ARRAY\s+([^ ]+) ")
108+ devices_raw = subprocess.check_output(
109+ ['/sbin/mdadm', '--detail', '--scan']
110+ )
111+ devices_re = re.compile(r'^ARRAY\s+([^ ]+) ')
112 devices = set()
113- for line in devices_raw.decode().split("\n"):
114+ for line in devices_raw.decode().split('\n'):
115 line = line.strip()
116 device_re = devices_re.match(line)
117 if device_re is not None:
118 devices.add(device_re.group(1))
119 return devices
120 except subprocess.CalledProcessError as error:
121- rc = generate_output("CRITICAL: get_devices error - {}".format(error))
122+ rc = generate_output(
123+ 'CRITICAL: get_devices error - {}'.format(error)
124+ )
125 if rc:
126 sys.exit(0)
127 return set()
128@@ -31,90 +35,150 @@ def get_devices():
129
130 def generate_output(msg):
131 try:
132- with open(TEMP_FILE, "w") as fd:
133+ with open(TEMP_FILE, 'w') as fd:
134 fd.write(msg)
135 shutil.move(TEMP_FILE, OUTPUT_FILE)
136 return True
137 except Exception as error:
138- print("Unable to generate output file:", error)
139+ print('Unable to generate output file:', error)
140 return False
141
142
143 def get_devices_stats(devices):
144- mdadm_detail = ["/sbin/mdadm", "--detail"]
145+ mdadm_detail = ['/sbin/mdadm', '--detail']
146 mdadm_detail.extend(sorted(devices))
147- devices_details_raw = subprocess.check_output(mdadm_detail)
148-
149- devices_re = r"^(/\S+):$"
150- state_re = r"^\s*State\s+:\s+(\S+)$"
151- status_re = r"^\s*(Active|Working|Failed|Spare) Devices\s+:\s+(\d+)$"
152+ try:
153+ devices_details_raw = subprocess.check_output(mdadm_detail)
154+ except subprocess.CalledProcessError as error:
155+ return generate_output(
156+ 'WARNING: error executing mdadm: {}'.format(error)
157+ )
158+
159+ devices_re = r'^(/\S+):$'
160+ state_re = r'^\s*State\s+:\s+(.+)\s*$'
161+ status_re = r'^\s*(Active|Working|Failed|Spare) Devices\s+:\s+(\d+)$'
162+ rebuild_status_re = r'^\s*Rebuild Status\s+:\s+(\d+%\s+\S+)$'
163+ removed_re = r'^\s*-\s+0\s+0\s+(\d+)\s+removed$'
164+ # 4 8 162 3 spare rebuilding /dev/sdk2
165+ rebuilding_re = r'^\s*\d+\s+\d+\s+\d+\s+\d+\s+\S+\s+rebuilding\s+(\S+)$'
166
167 devices_cre = re.compile(devices_re)
168 state_cre = re.compile(state_re)
169 status_cre = re.compile(status_re)
170+ rebuild_status_cre = re.compile(rebuild_status_re)
171+ removed_cre = re.compile(removed_re)
172+ rebuilding_cre = re.compile(rebuilding_re)
173
174 device = None
175 devices_stats = {}
176- for line in devices_details_raw.decode().split("\n"):
177+ for line in devices_details_raw.decode().split('\n'):
178 line = line.rstrip()
179 m = devices_cre.match(line)
180 if m:
181 device = m.group(1)
182 devices_stats[device] = {
183- "stats": {"Active": 0, "Working": 0, "Failed": 0, "Spare": 0},
184- "degraded": False,
185+ 'stats': {
186+ 'Active': 0,
187+ 'Working': 0,
188+ 'Failed': 0,
189+ 'Spare': 0,
190+ },
191+ 'rebuild_status': '',
192+ 'degraded': False,
193+ 'recovering': False,
194+ 'removed': [],
195+ 'rebuilding': [],
196 }
197 continue
198
199 m = state_cre.match(line)
200 if m:
201- if "degraded" in m.group(1) and device:
202- devices_stats[device]["degraded"] = True
203+ # format for State line can be "clean" or "clean, degraded" or "active, degraded, rebuilding", etc.
204+ states = m.group(1).split(", ")
205+ if 'degraded' in states and device:
206+ devices_stats[device]['degraded'] = True
207+ if 'recovering' in states and device:
208+ devices_stats[device]['recovering'] = True
209 continue
210
211 m = status_cre.match(line)
212 if m and device:
213- devices_stats[device]["stats"][m.group(1)] = int(m.group(2))
214+ devices_stats[device]['stats'][m.group(1)] = int(m.group(2))
215+ continue
216+
217+ m = removed_cre.match(line)
218+ if m and device:
219+ devices_stats[device]['removed'].append(m.group(1))
220+ continue
221+
222+ m = rebuild_status_cre.match(line)
223+ if m and device:
224+ devices_stats[device]['rebuild_status'] = m.group(1)
225+ continue
226+
227+ m = rebuilding_cre.match(line)
228+ if m and device:
229+ devices_stats[device]['rebuilding'].append(m.group(1))
230+ continue
231+
232 return devices_stats
233
234
235 def parse_output():
236 devices = get_devices()
237 if len(devices) == 0:
238- return generate_output("WARNING: unexpectedly checked no devices")
239+ return generate_output('WARNING: unexpectedly checked no devices')
240
241- try:
242- devices_stats = get_devices_stats(devices)
243- except subprocess.CalledProcessError as error:
244- return generate_output("WARNING: error executing mdadm: {}".format(error))
245+ devices_stats = get_devices_stats(devices)
246+ if isinstance(devices_stats, bool):
247+ # if the device_stats is boolean, generate_output was already called
248+ return devices_stats
249
250 msg = []
251 critical = False
252+ warning = False
253 for device in devices_stats:
254+ parts = []
255 # Is device degraded?
256- if devices_stats[device]["degraded"]:
257+ if devices_stats[device]['degraded'] and devices_stats[device]['recovering']:
258+ warning = True
259+ parts = ['{} recovering'.format(device)]
260+ elif devices_stats[device]['degraded']:
261 critical = True
262- parts = ["{} degraded".format(device)]
263+ parts = ['{} degraded'.format(device)]
264 else:
265- parts = ["{} ok".format(device)]
266+ parts = ['{} ok'.format(device)]
267
268 # If Failed drives are found, list counters (how many?)
269- failed_cnt = devices_stats[device]["stats"].get("Failed", 0)
270+ failed_cnt = devices_stats[device]['stats'].get('Failed', 0)
271 if failed_cnt > 0:
272 critical = True
273 dev_stats = [
274- "{}[{}]".format(status, devices_stats[device]["stats"][status])
275- for status in sorted(devices_stats[device]["stats"])
276+ '{}[{}]'.format(status, devices_stats[device]['stats'][status])
277+ for status in sorted(devices_stats[device]['stats'])
278 ]
279 parts.extend(dev_stats)
280- msg.append(", ".join(parts))
281+
282+ if len(devices_stats[device]['removed']) != 0:
283+ critical = True
284+ members = " and ".join(devices_stats[device]['removed'])
285+ parts.append('RaidDevice(s) {} marked removed'.format(members))
286+
287+ if len(devices_stats[device]['rebuilding']) != 0:
288+ rebuilding_members = " ".join(devices_stats[device]['rebuilding'])
289+ rebuild_status = devices_stats[device]['rebuild_status']
290+ parts.append('{} rebuilding ({})'.format(rebuilding_members, rebuild_status))
291+
292+ msg.append(', '.join(parts))
293
294 if critical:
295- msg = "CRITICAL: {}".format("; ".join(msg))
296+ msg = 'CRITICAL: {}'.format('; '.join(msg))
297+ elif warning:
298+ msg = 'WARNING: {}'.format('; '.join(msg))
299 else:
300- msg = "OK: {}".format("; ".join(msg))
301+ msg = 'OK: {}'.format('; '.join(msg))
302 return generate_output(msg)
303
304
305-if __name__ == "__main__":
306+if __name__ == '__main__':
307 parse_output()
308diff --git a/src/lib/hwhealth/tools.py b/src/lib/hwhealth/tools.py
309index 0bf0121..970ac7d 100644
310--- a/src/lib/hwhealth/tools.py
311+++ b/src/lib/hwhealth/tools.py
312@@ -303,7 +303,10 @@ class Sas3Ircu(VendorTool):
313 """
314 def __init__(self):
315 super().__init__(shortname='sas3ircu')
316- self.checksums = ['f150eb37bb332668949a3eccf9636e0e03f874aecd17a39d586082c6be1386bd']
317+ self.checksums = [
318+ 'f150eb37bb332668949a3eccf9636e0e03f874aecd17a39d586082c6be1386bd',
319+ 'd69967057992134df1b136f83bc775a641e32c4efc741def3ef6f6a25a9a14b5',
320+ ]
321
322
323 class Sas2Ircu(VendorTool):
324@@ -323,7 +326,11 @@ class MegaCLI(VendorTool):
325 """
326 def __init__(self):
327 super().__init__(shortname='megacli')
328- self.checksums = ['34f1a235543662615ee35f458317380b3f89fac0e415dee755e0dbc7c4cf6f92']
329+ self.checksums = [
330+ '34f1a235543662615ee35f458317380b3f89fac0e415dee755e0dbc7c4cf6f92',
331+ '1c4effe33ee5db82227e05925dd629771fd49c7d2be2382d48c48a864452cdec',
332+ '1a68e6646d1e3dfb7039f581be994500d0ed02de2f928e57399e86473d4c8662',
333+ ]
334
335
336 class Mdadm(VendorTool):
337diff --git a/src/metadata.yaml b/src/metadata.yaml
338index db91438..58c709a 100644
339--- a/src/metadata.yaml
340+++ b/src/metadata.yaml
341@@ -1,6 +1,6 @@
342 name: hw-health
343 summary: Hardware Monitoring for Nagios
344-maintainer: Nagios Charm Developers <nagios-charmers@lists.launchpad.net>
345+maintainer: Llama (LMA) Charmers <llama-charmers@lists.ubuntu.com>
346 description: |
347 This addon installs hardware monitoring tools and configures Nagios checks
348 for the system hardware and storage monitoring.
349@@ -23,6 +23,7 @@ tags:
350 - monitoring
351 - hardware
352 series:
353+- focal
354 - bionic
355 - xenial
356 subordinate: true
357diff --git a/src/tests/download_nagios_plugin3.py b/src/tests/download_nagios_plugin3.py
358index 173fa31..b454caf 100755
359--- a/src/tests/download_nagios_plugin3.py
360+++ b/src/tests/download_nagios_plugin3.py
361@@ -18,7 +18,7 @@ def content():
362
363
364 def main():
365- for i in glob('.tox/unit/lib/python3*'):
366+ for i in glob('.tox/unit/lib/python3*/site-packages'):
367 mod_path = os.path.join(i, MODULE_NAME)
368 if os.path.isdir(i) and not os.path.exists(mod_path):
369 open(mod_path, 'wb').write(content())
370diff --git a/src/tests/functional/test_hwhealth.py b/src/tests/functional/test_hwhealth.py
371index f16878f..51d407a 100644
372--- a/src/tests/functional/test_hwhealth.py
373+++ b/src/tests/functional/test_hwhealth.py
374@@ -12,7 +12,11 @@ from hwhealth import tools # noqa: E402
375
376 # Treat all tests as coroutines
377 pytestmark = pytest.mark.asyncio
378-SERIES = ['xenial', 'bionic']
379+SERIES = [
380+ 'focal',
381+ 'bionic',
382+ 'xenial',
383+]
384 JUJU_REPOSITORY = os.getenv('JUJU_REPOSITORY', '.')
385 NRPECFG_DIR = '/etc/nagios/nrpe.d'
386
387@@ -65,10 +69,17 @@ async def deploy_app(request, model):
388 hw_health_checksum_app_name = 'hw-health-checksum-{}'.format(release)
389
390 for principal_app in ['ubuntu', 'nagios']:
391+ relname = series = release
392+ if principal_app == "nagios" and release == "focal":
393+ # NOTE(aluria): cs:nagios was not available in focal
394+ # On the other hand, bionic testing would create nagios-bionic
395+ # hence nagios-bionic2
396+ relname = "bionic2"
397+ series = "bionic"
398 await model.deploy(
399 principal_app,
400- application_name='{}-{}'.format(principal_app, release),
401- series=release,
402+ application_name='{}-{}'.format(principal_app, relname),
403+ series=series,
404 channel=channel,
405 )
406 await model.deploy('ubuntu', application_name='ubuntu-checksum-{}'.format(release),
407@@ -87,7 +98,7 @@ async def deploy_app(request, model):
408 )
409 await nrpe_app.add_relation(
410 'monitors',
411- 'nagios-{}:monitors'.format(release)
412+ 'nagios-{}:monitors'.format(relname)
413 )
414
415 # Attaching resources is not implemented yet in libjuju
416diff --git a/src/tests/hw-health-samples/mdadm.output.critical b/src/tests/hw-health-samples/mdadm.output.critical.1
417similarity index 100%
418rename from src/tests/hw-health-samples/mdadm.output.critical
419rename to src/tests/hw-health-samples/mdadm.output.critical.1
420diff --git a/src/tests/hw-health-samples/mdadm.output.critical.2 b/src/tests/hw-health-samples/mdadm.output.critical.2
421new file mode 100644
422index 0000000..2220eae
423--- /dev/null
424+++ b/src/tests/hw-health-samples/mdadm.output.critical.2
425@@ -0,0 +1,33 @@
426+/dev/md1:
427+ Version : 1.2
428+ Creation Time : Wed Jun 26 18:18:55 2019
429+ Raid Level : raid10
430+ Array Size : 7812235264 (7450.33 GiB 7999.73 GB)
431+ Used Dev Size : 3906117632 (3725.16 GiB 3999.86 GB)
432+ Raid Devices : 4
433+ Total Devices : 3
434+ Persistence : Superblock is persistent
435+
436+ Intent Bitmap : Internal
437+
438+ Update Time : Mon Oct 28 17:27:54 2019
439+ State : active, degraded
440+ Active Devices : 3
441+ Working Devices : 3
442+ Failed Devices : 0
443+ Spare Devices : 0
444+
445+ Layout : near=2
446+ Chunk Size : 512K
447+
448+Consistency Policy : bitmap
449+
450+ Name : CMFOSCHSTUP7411:1 (local to host CMFOSCHSTUP7411)
451+ UUID : 3887c84d:ebd28589:5a4b5d9e:3c83a25d
452+ Events : 491275
453+
454+ Number Major Minor RaidDevice State
455+ 0 8 98 0 active sync set-A /dev/sdg2
456+ 1 8 114 1 active sync set-B /dev/sdh2
457+ - 0 0 2 removed
458+ 3 8 162 3 active sync set-B /dev/sdk2
459diff --git a/src/tests/hw-health-samples/mdadm.output.warning b/src/tests/hw-health-samples/mdadm.output.warning
460new file mode 100644
461index 0000000..bba0d18
462--- /dev/null
463+++ b/src/tests/hw-health-samples/mdadm.output.warning
464@@ -0,0 +1,33 @@
465+/dev/md1:
466+ Version : 1.2
467+ Creation Time : Thu Oct 18 17:41:01 2018
468+ Raid Level : raid10
469+ Array Size : 7812235264 (7450.33 GiB 7999.73 GB)
470+ Used Dev Size : 3906117632 (3725.16 GiB 3999.86 GB)
471+ Raid Devices : 4
472+ Total Devices : 4
473+ Persistence : Superblock is persistent
474+
475+ Intent Bitmap : Internal
476+
477+ Update Time : Mon Oct 28 18:46:34 2019
478+ State : active, degraded, recovering
479+ Active Devices : 3
480+Working Devices : 4
481+ Failed Devices : 0
482+ Spare Devices : 1
483+
484+ Layout : near=2
485+ Chunk Size : 512K
486+
487+ Rebuild Status : 13% complete
488+
489+ Name : CMOOSCHSTUP7305:1 (local to host CMOOSCHSTUP7305)
490+ UUID : fed3a645:1f742fd3:1685dda5:71794407
491+ Events : 750593
492+
493+ Number Major Minor RaidDevice State
494+ 0 8 98 0 active sync set-A /dev/sdg2
495+ 1 8 114 1 active sync set-B /dev/sdh2
496+ 2 8 146 2 active sync set-A /dev/sdj2
497+ 4 8 162 3 spare rebuilding /dev/sdk2
498diff --git a/src/tests/unit/test_check_mdadm.py b/src/tests/unit/test_check_mdadm.py
499index 231ca4f..0314032 100644
500--- a/src/tests/unit/test_check_mdadm.py
501+++ b/src/tests/unit/test_check_mdadm.py
502@@ -6,24 +6,23 @@ import unittest.mock as mock
503
504 import nagios_plugin3
505
506-sys.path.append('files/mdadm')
507+sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
508+from samples import get_sample # noqa: E402
509+
510+sys.path.append(os.path.join(os.path.dirname(__file__), '../../files/mdadm'))
511 import check_mdadm # noqa: E402
512
513
514 class TestCheckMdadm(unittest.TestCase):
515 def test_parse_output_crit(self):
516- check_mdadm.INPUT_FILE = os.path.join(
517- os.getcwd(), 'tests', 'hw-health-samples',
518- 'mdadm.output.nrpe.critical')
519+ check_mdadm.INPUT_FILE = get_sample('mdadm.output.nrpe.critical')
520 expected = 'CRITICAL: critical msg'
521 with self.assertRaises(nagios_plugin3.CriticalError) as context:
522 check_mdadm.parse_output()
523 self.assertTrue(expected in str(context.exception))
524
525 def test_parse_output_warn(self):
526- check_mdadm.INPUT_FILE = os.path.join(
527- os.getcwd(), 'tests', 'hw-health-samples',
528- 'mdadm.output.nrpe.warning')
529+ check_mdadm.INPUT_FILE = get_sample('mdadm.output.nrpe.warning')
530 expected = 'WARNING: warning msg'
531 with self.assertRaises(nagios_plugin3.WarnError) as context:
532 check_mdadm.parse_output()
533@@ -31,8 +30,7 @@ class TestCheckMdadm(unittest.TestCase):
534
535 @mock.patch('sys.stdout', new_callable=io.StringIO)
536 def test_parse_output_ok(self, mock_print):
537- check_mdadm.INPUT_FILE = os.path.join(
538- os.getcwd(), 'tests', 'hw-health-samples', 'mdadm.output.nrpe.ok')
539+ check_mdadm.INPUT_FILE = get_sample('mdadm.output.nrpe.ok')
540 check_mdadm.parse_output()
541 self.assertEqual(
542 mock_print.getvalue(),
543@@ -41,9 +39,7 @@ class TestCheckMdadm(unittest.TestCase):
544
545 @mock.patch('sys.stdout', new_callable=io.StringIO)
546 def test_parse_output_unknown_filenotfound(self, mock_print):
547- check_mdadm.INPUT_FILE = os.path.join(
548- os.getcwd(), 'tests', 'hw-health-samples',
549- 'thisfiledoesnotexist')
550+ check_mdadm.INPUT_FILE = get_sample('thisfiledoesnotexist')
551 expected = 'UNKNOWN: file not found ({})'.format(
552 check_mdadm.INPUT_FILE)
553 with self.assertRaises(nagios_plugin3.UnknownError) as context:
554@@ -52,9 +48,7 @@ class TestCheckMdadm(unittest.TestCase):
555
556 @mock.patch('sys.stdout', new_callable=io.StringIO)
557 def test_parse_output_unknown1(self, mock_print):
558- check_mdadm.INPUT_FILE = os.path.join(
559- os.getcwd(), 'tests', 'hw-health-samples',
560- 'mdadm.output.nrpe.unknown.1')
561+ check_mdadm.INPUT_FILE = get_sample('mdadm.output.nrpe.unknown.1')
562 check_mdadm.parse_output()
563 self.assertEqual(
564 mock_print.getvalue(),
565@@ -63,9 +57,7 @@ class TestCheckMdadm(unittest.TestCase):
566
567 @mock.patch('sys.stdout', new_callable=io.StringIO)
568 def test_parse_output_unknown2(self, mock_print):
569- check_mdadm.INPUT_FILE = os.path.join(
570- os.getcwd(), 'tests', 'hw-health-samples',
571- 'mdadm.output.nrpe.unknown.2')
572+ check_mdadm.INPUT_FILE = get_sample('mdadm.output.nrpe.unknown.2')
573 check_mdadm.parse_output()
574 self.assertEqual(
575 mock_print.getvalue(),
576diff --git a/src/tests/unit/test_check_megacli.py b/src/tests/unit/test_check_megacli.py
577index 988075a..4207953 100644
578--- a/src/tests/unit/test_check_megacli.py
579+++ b/src/tests/unit/test_check_megacli.py
580@@ -6,15 +6,17 @@ import unittest.mock as mock
581
582 import nagios_plugin3
583
584-sys.path.append('files/megacli')
585+sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
586+from samples import get_sample # noqa: E402
587+
588+sys.path.append(os.path.join(os.path.dirname(__file__), '../../files/megacli'))
589 import check_megacli # noqa: E402
590
591
592 class TestCheckMegaCLI(unittest.TestCase):
593 @mock.patch('sys.stdout', new_callable=io.StringIO)
594 def test_parse_output(self, mock_print):
595- check_megacli.INPUT_FILE = os.path.join(
596- os.getcwd(), 'tests', 'hw-health-samples', 'megacli.output.1')
597+ check_megacli.INPUT_FILE = get_sample('megacli.output.1')
598 check_megacli.parse_output()
599 actual = mock_print.getvalue()
600 expected = 'OK: Optimal, ldrives[1], pdrives[4]\n'
601@@ -22,8 +24,7 @@ class TestCheckMegaCLI(unittest.TestCase):
602
603 @mock.patch('sys.stdout', new_callable=io.StringIO)
604 def test_parse_output_critical_singledrive(self, mock_print):
605- check_megacli.INPUT_FILE = os.path.join(
606- os.getcwd(), 'tests', 'hw-health-samples', 'megacli.output.nrpe.critical.1')
607+ check_megacli.INPUT_FILE = get_sample('megacli.output.nrpe.critical.1')
608 expected = 'CRITICAL: adapter(0):ld(0):state(Degraded)'
609 with self.assertRaises(nagios_plugin3.CriticalError) as context:
610 check_megacli.parse_output()
611@@ -31,8 +32,7 @@ class TestCheckMegaCLI(unittest.TestCase):
612
613 @mock.patch('sys.stdout', new_callable=io.StringIO)
614 def test_parse_output_critical_multiple(self, mock_print):
615- check_megacli.INPUT_FILE = os.path.join(
616- os.getcwd(), 'tests', 'hw-health-samples', 'megacli.output.nrpe.critical.2')
617+ check_megacli.INPUT_FILE = get_sample('megacli.output.nrpe.critical.2')
618 expected = ('CRITICAL: adapter(0):ld(0):state(Degraded);'
619 ' adapter(0):ld(4):state(Degraded)')
620 with self.assertRaises(nagios_plugin3.CriticalError) as context:
621diff --git a/src/tests/unit/test_check_nvme.py b/src/tests/unit/test_check_nvme.py
622index 53466e4..097fd76 100644
623--- a/src/tests/unit/test_check_nvme.py
624+++ b/src/tests/unit/test_check_nvme.py
625@@ -4,7 +4,10 @@ import sys
626 import unittest
627 import unittest.mock as mock
628
629-sys.path.append('files/nvme')
630+sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
631+from samples import get_sample # noqa: E402
632+
633+sys.path.append(os.path.join(os.path.dirname(__file__), '../../files/nvme'))
634 import check_nvme # noqa: E402
635
636
637@@ -14,8 +17,7 @@ class TestCheckNvme(unittest.TestCase):
638 @mock.patch('sys.stdout', new_callable=io.StringIO)
639 def test_parse_output(self, mock_print, mock_subprocess, mock_glob):
640 mock_glob.return_value = ['/dev/nvme0']
641- input_file = os.path.join(os.getcwd(), 'tests', 'hw-health-samples',
642- 'nvme.output.1')
643+ input_file = get_sample('nvme.output.1')
644 with open(input_file, 'r') as fd:
645 mock_subprocess.return_value = fd.read().encode()
646 check_nvme.parse_output()
647diff --git a/src/tests/unit/test_check_sas2ircu.py b/src/tests/unit/test_check_sas2ircu.py
648index 1d0dc80..5464889 100644
649--- a/src/tests/unit/test_check_sas2ircu.py
650+++ b/src/tests/unit/test_check_sas2ircu.py
651@@ -4,16 +4,17 @@ import sys
652 import unittest
653 import unittest.mock as mock
654
655-sys.path.append('files/sas2ircu')
656+sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
657+from samples import get_sample # noqa: E402
658+
659+sys.path.append(os.path.join(os.path.dirname(__file__), '../../files/sas2ircu'))
660 import check_sas2ircu # noqa: E402
661
662
663 class TestCheckMegaCLI(unittest.TestCase):
664 @mock.patch('sys.stdout', new_callable=io.StringIO)
665 def test_parse_output(self, mock_print):
666- check_sas2ircu.INPUT_FILE = os.path.join(os.getcwd(), 'tests',
667- 'hw-health-samples',
668- 'sas2ircu.huawei.output.1')
669+ check_sas2ircu.INPUT_FILE = get_sample('sas2ircu.huawei.output.1')
670 check_sas2ircu.parse_output()
671 actual = mock_print.getvalue()
672 expected = 'OK: Ready[1:0,1:1,1:2,1:3,1:4,1:5,1:6,1:7]\n'
673diff --git a/src/tests/unit/test_check_sas3ircu.py b/src/tests/unit/test_check_sas3ircu.py
674index 5747de9..1379369 100644
675--- a/src/tests/unit/test_check_sas3ircu.py
676+++ b/src/tests/unit/test_check_sas3ircu.py
677@@ -4,15 +4,17 @@ import sys
678 import unittest
679 import unittest.mock as mock
680
681-sys.path.append('files/sas3ircu')
682+sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
683+from samples import get_sample # noqa: E402
684+
685+sys.path.append(os.path.join(os.path.dirname(__file__), '../../files/sas3ircu'))
686 import check_sas3ircu # noqa: E402
687
688
689 class TestCheckMegaCLI(unittest.TestCase):
690 @mock.patch('sys.stdout', new_callable=io.StringIO)
691 def test_parse_output_ok(self, mock_print):
692- _filepath = os.path.join(os.getcwd(), 'tests', 'hw-health-samples',
693- 'sas3ircu.supermicro.ok.output.1')
694+ _filepath = get_sample('sas3ircu.supermicro.ok.output.1')
695 data = check_sas3ircu.parse_output(_filepath)
696 check_sas3ircu.eval_status(data)
697 actual = mock_print.getvalue()
698diff --git a/src/tests/unit/test_cron_mdadm.py b/src/tests/unit/test_cron_mdadm.py
699index d124931..f52ea9c 100644
700--- a/src/tests/unit/test_cron_mdadm.py
701+++ b/src/tests/unit/test_cron_mdadm.py
702@@ -5,7 +5,10 @@ import sys # noqa: F401
703 import unittest
704 import unittest.mock as mock
705
706-sys.path.append('files/mdadm')
707+sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
708+from samples import get_sample # noqa: E402
709+
710+sys.path.append(os.path.join(os.path.dirname(__file__), '../../files/mdadm'))
711 import cron_mdadm # noqa: E402
712
713
714@@ -44,13 +47,11 @@ class TestCronMdadm(unittest.TestCase):
715 def test_parse_output_ok(self, mock_print, mdadm_details, devices, genout):
716 class Test_Popen(object):
717 def __init__(cls):
718- test_output = os.path.join(
719- os.getcwd(), 'tests', 'hw-health-samples', 'mdadm.output')
720+ test_output = get_sample('mdadm.output')
721 cls.stdout = io.FileIO(test_output)
722 cls.wait = lambda: 0
723
724- test_output = os.path.join(
725- os.getcwd(), 'tests', 'hw-health-samples', 'mdadm.output')
726+ test_output = get_sample('mdadm.output')
727 with open(test_output, 'r') as fd:
728 mdadm_details.return_value = ''.join(fd.readlines()).encode()
729 devices.return_value = set(['/dev/md0', '/dev/md1', '/dev/md2'])
730@@ -87,9 +88,7 @@ class TestCronMdadm(unittest.TestCase):
731 @mock.patch('cron_mdadm.get_devices')
732 @mock.patch('subprocess.check_output')
733 def test_parse_output_degraded(self, mdadm_details, devices, genout):
734- test_output = os.path.join(
735- os.getcwd(), 'tests', 'hw-health-samples',
736- 'mdadm.output.critical')
737+ test_output = get_sample('mdadm.output.critical.1')
738 with open(test_output, 'r') as fd:
739 mdadm_details.return_value = ''.join(fd.readlines()).encode()
740
741@@ -100,3 +99,31 @@ class TestCronMdadm(unittest.TestCase):
742 ' /dev/md2 ok')
743 self.assertTrue(cron_mdadm.parse_output())
744 genout.assert_called_once_with(expected)
745+
746+ @mock.patch('cron_mdadm.generate_output')
747+ @mock.patch('cron_mdadm.get_devices')
748+ @mock.patch('subprocess.check_output')
749+ def test_parse_output_removed(self, mdadm_details, devices, genout):
750+ test_output = get_sample('mdadm.output.critical.2')
751+ with open(test_output, 'r') as fd:
752+ mdadm_details.return_value = ''.join(fd.readlines()).encode()
753+
754+ devices.return_value = set(['/dev/md1'])
755+ genout.return_value = True
756+ expected = ('CRITICAL: /dev/md1 degraded, RaidDevice(s) 2 marked removed')
757+ self.assertTrue(cron_mdadm.parse_output())
758+ genout.assert_called_once_with(expected)
759+
760+ @mock.patch('cron_mdadm.generate_output')
761+ @mock.patch('cron_mdadm.get_devices')
762+ @mock.patch('subprocess.check_output')
763+ def test_parse_output_rebuilding(self, mdadm_details, devices, genout):
764+ test_output = get_sample('mdadm.output.warning')
765+ with open(test_output, 'r') as fd:
766+ mdadm_details.return_value = ''.join(fd.readlines()).encode()
767+
768+ devices.return_value = set(['/dev/md0', '/dev/md1', '/dev/md2'])
769+ genout.return_value = True
770+ expected = ('WARNING: /dev/md1 recovering, /dev/sdk2 rebuilding (13% complete)')
771+ self.assertTrue(cron_mdadm.parse_output())
772+ genout.assert_called_once_with(expected)
773diff --git a/src/tests/unit/test_hwdiscovery.py b/src/tests/unit/test_hwdiscovery.py
774index 6e5bf3d..43ea360 100644
775--- a/src/tests/unit/test_hwdiscovery.py
776+++ b/src/tests/unit/test_hwdiscovery.py
777@@ -1,11 +1,13 @@
778-import glob
779 import os # noqa: F401
780 import subprocess # noqa: F401
781 import sys
782 import unittest
783 import unittest.mock as mock
784
785-sys.path.append('lib/hwhealth')
786+sys.path.append(os.path.join(os.path.dirname(__file__), 'lib'))
787+from samples import get_sample # noqa: E402
788+
789+sys.path.append(os.path.join(os.path.dirname(__file__), '../../lib/hwhealth'))
790 import hwdiscovery # noqa: E402
791 from discovery.lshw import Hardware # noqa: E402
792
793@@ -83,8 +85,7 @@ class TestGetTools(unittest.TestCase):
794 'lshw.supermicro.sas.02.json': set(['Nvme', 'Sas3Ircu']),
795 }
796
797- for filename in glob.glob(os.path.join(
798- os.getcwd(), 'tests/hw-health-samples/lshw.*.json')):
799+ for filename in get_sample('lshw.*.json'):
800 mock_hwinfo.return_value = Hardware(filename)
801 actual = hwdiscovery._get_tools()
802 if os.path.basename(filename) in TOOLS:
803diff --git a/src/tox.ini b/src/tox.ini
804index 71aeb29..29b332a 100644
805--- a/src/tox.ini
806+++ b/src/tox.ini
807@@ -4,6 +4,7 @@ skipsdist = true
808
809 [testenv:unit]
810 basepython=python3
811+setenv = PYTHONPATH={toxinidir}/lib
812 deps=
813 charms.reactive
814 nose

Subscribers

People subscribed via source and target branches