Merge ~aieri/charm-hw-health:lp1832906 into ~nagios-charmers/charm-hw-health:master
- Git
- lp:~aieri/charm-hw-health
- lp1832906
- Merge into master
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) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Stuart Bishop | Pending | ||
Drew Freiberger | Pending | ||
Xav Paice | Pending | ||
BootStack Reviewers | Pending | ||
Giuseppe Petralia | Pending | ||
Review via email:
|
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
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
🤖 Canonical IS Merge Bot (canonical-is-mergebot) wrote : Posted in a previous version of this proposal | # |
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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!
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Giuseppe Petralia (peppepetra) wrote : Posted in a previous version of this proposal | # |
All Looks good to me
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Xav Paice (xavpaice) wrote : Posted in a previous version of this proposal | # |
https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Drew Freiberger (afreiberger) wrote : Posted in a previous version of this proposal | # |
ready for review
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Drew Freiberger (afreiberger) wrote : Posted in a previous version of this proposal | # |
Green lint/unit/func tests: https:/
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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
1 | diff --git a/src/README.md b/src/README.md |
2 | index 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. |
37 | diff --git a/src/files/ipmi/cron_ipmi_sensors.py b/src/files/ipmi/cron_ipmi_sensors.py |
38 | index 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) |
88 | diff --git a/src/files/mdadm/cron_mdadm.py b/src/files/mdadm/cron_mdadm.py |
89 | index 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() |
308 | diff --git a/src/lib/hwhealth/tools.py b/src/lib/hwhealth/tools.py |
309 | index 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): |
337 | diff --git a/src/metadata.yaml b/src/metadata.yaml |
338 | index 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 |
357 | diff --git a/src/tests/download_nagios_plugin3.py b/src/tests/download_nagios_plugin3.py |
358 | index 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()) |
370 | diff --git a/src/tests/functional/test_hwhealth.py b/src/tests/functional/test_hwhealth.py |
371 | index 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 |
416 | diff --git a/src/tests/hw-health-samples/mdadm.output.critical b/src/tests/hw-health-samples/mdadm.output.critical.1 |
417 | similarity index 100% |
418 | rename from src/tests/hw-health-samples/mdadm.output.critical |
419 | rename to src/tests/hw-health-samples/mdadm.output.critical.1 |
420 | diff --git a/src/tests/hw-health-samples/mdadm.output.critical.2 b/src/tests/hw-health-samples/mdadm.output.critical.2 |
421 | new file mode 100644 |
422 | index 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 |
459 | diff --git a/src/tests/hw-health-samples/mdadm.output.warning b/src/tests/hw-health-samples/mdadm.output.warning |
460 | new file mode 100644 |
461 | index 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 |
498 | diff --git a/src/tests/unit/test_check_mdadm.py b/src/tests/unit/test_check_mdadm.py |
499 | index 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(), |
576 | diff --git a/src/tests/unit/test_check_megacli.py b/src/tests/unit/test_check_megacli.py |
577 | index 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: |
621 | diff --git a/src/tests/unit/test_check_nvme.py b/src/tests/unit/test_check_nvme.py |
622 | index 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() |
647 | diff --git a/src/tests/unit/test_check_sas2ircu.py b/src/tests/unit/test_check_sas2ircu.py |
648 | index 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' |
673 | diff --git a/src/tests/unit/test_check_sas3ircu.py b/src/tests/unit/test_check_sas3ircu.py |
674 | index 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() |
698 | diff --git a/src/tests/unit/test_cron_mdadm.py b/src/tests/unit/test_cron_mdadm.py |
699 | index 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) |
773 | diff --git a/src/tests/unit/test_hwdiscovery.py b/src/tests/unit/test_hwdiscovery.py |
774 | index 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: |
803 | diff --git a/src/tox.ini b/src/tox.ini |
804 | index 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 |
This merge proposal is being monitored by mergebot. Change the status to Approved to merge.