Merge ~ltrager/maas:lp1835954_2.8 into maas:2.8
- Git
- lp:~ltrager/maas
- lp1835954_2.8
- Merge into 2.8
Proposed by
Lee Trager
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Lee Trager | ||||
Approved revision: | ddf53cddd6079dd0b1fb4a128cb134f8c4f88d40 | ||||
Merge reported by: | MAAS Lander | ||||
Merged at revision: | not available | ||||
Proposed branch: | ~ltrager/maas:lp1835954_2.8 | ||||
Merge into: | maas:2.8 | ||||
Diff against target: |
1741 lines (+1118/-242) 4 files modified
src/maasserver/compose_preseed.py (+3/-0) src/metadataserver/user_data/templates/snippets/maas_wipe.py (+289/-63) src/metadataserver/user_data/templates/snippets/tests/test_maas_wipe.py (+594/-179) src/metadataserver/user_data/templates/snippets/tests/test_maas_wipe_defs.py (+232/-0) |
||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
MAAS Lander | Approve | ||
Lee Trager (community) | Approve | ||
Review via email: mp+387870@code.launchpad.net |
Commit message
This is the NVMe secure erase implementation (+ one more patch, to fix/improve quick erase).
I tried to explain/detail everything in the commit messages; also, code/tests were validated against Flake8 to prevent style issues.
Backport of 9633810
Description of the change
To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b lp1835954_2.8 lp:~ltrager/maas/+git/maas into -b 2.8 lp:~maas-committers/maas
STATUS: SUCCESS
COMMIT: ddf53cddd6079dd
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/src/maasserver/compose_preseed.py b/src/maasserver/compose_preseed.py |
2 | index 8acf2f5..4baa65a 100644 |
3 | --- a/src/maasserver/compose_preseed.py |
4 | +++ b/src/maasserver/compose_preseed.py |
5 | @@ -391,6 +391,9 @@ def get_base_preseed(node=None): |
6 | # jq is used during enlistment to read the JSON string containing |
7 | # the system_id of the newly created machine. |
8 | cloud_config["packages"] += ["jq"] |
9 | + # On disk erasing, we need nvme-cli |
10 | + if node is not None and node.status == NODE_STATUS.DISK_ERASING: |
11 | + cloud_config["packages"] += ["nvme-cli"] |
12 | |
13 | return cloud_config |
14 | |
15 | diff --git a/src/metadataserver/user_data/templates/snippets/maas_wipe.py b/src/metadataserver/user_data/templates/snippets/maas_wipe.py |
16 | old mode 100644 |
17 | new mode 100755 |
18 | index 317c62b..f7e611a |
19 | --- a/src/metadataserver/user_data/templates/snippets/maas_wipe.py |
20 | +++ b/src/metadataserver/user_data/templates/snippets/maas_wipe.py |
21 | @@ -29,12 +29,95 @@ def list_disks(): |
22 | return disks |
23 | |
24 | |
25 | -def get_disk_security_info(disk): |
26 | - """Get the disk security information. |
27 | +def get_nvme_security_info(disk): |
28 | + """Gather NVMe information from the NVMe disks using the |
29 | + nvme-cli tool. Info from id-ctrl and id-ns is needed for |
30 | + secure erase (nvme format) and write zeroes.""" |
31 | + |
32 | + # Grab the relevant info from nvme id-ctrl. We need to check the |
33 | + # following bits: |
34 | + # |
35 | + # OACS (Optional Admin Command Support) bit 1: Format supported |
36 | + # ONCS (Optional NVM Command Support) bit 3: Write Zeroes supported |
37 | + # FNA (Format NVM Attributes) bit 2: Cryptographic format supported |
38 | + |
39 | + security_info = { |
40 | + "format_supported": False, |
41 | + "writez_supported": False, |
42 | + "crypto_format": False, |
43 | + "nsze": 0, |
44 | + "lbaf": 0, |
45 | + "ms": 0, |
46 | + } |
47 | + |
48 | + try: |
49 | + output = subprocess.check_output(["nvme", "id-ctrl", DEV_PATH % disk]) |
50 | + except subprocess.CalledProcessError as exc: |
51 | + print_flush("Error on nvme id-ctrl (%s)" % exc.returncode) |
52 | + return security_info |
53 | + except OSError as exc: |
54 | + print_flush("OS error when running nvme-cli (%s)" % exc.strerror) |
55 | + return security_info |
56 | + |
57 | + output = output.decode() |
58 | + |
59 | + for line in output.split("\n"): |
60 | + if "oacs" in line: |
61 | + oacs = line.split(":")[1] |
62 | + if int(oacs, 16) & 0x2: |
63 | + security_info["format_supported"] = True |
64 | + |
65 | + if "oncs" in line: |
66 | + oncs = line.split(":")[1] |
67 | + if int(oncs, 16) & 0x8: |
68 | + security_info["writez_supported"] = True |
69 | + |
70 | + if "fna" in line: |
71 | + fna = line.split(":")[1] |
72 | + if int(fna, 16) & 0x4: |
73 | + security_info["crypto_format"] = True |
74 | + |
75 | + # Next step: collect LBAF (LBA Format), MS (Metadata Setting) and |
76 | + # NSZE (Namespace Size) from id-ns. According to NVMe spec, bits 0:3 |
77 | + # from FLBAS corresponds to the LBAF value, whereas bit 4 is MS. |
78 | + |
79 | + try: |
80 | + output = subprocess.check_output(["nvme", "id-ns", DEV_PATH % disk]) |
81 | + except subprocess.CalledProcessError as exc: |
82 | + print_flush("Error on nvme id-ns (%s)" % exc.returncode) |
83 | + security_info["format_supported"] = False |
84 | + security_info["writez_supported"] = False |
85 | + return security_info |
86 | + except OSError as exc: |
87 | + print_flush("OS error when running nvme-cli (%s)" % exc.strerror) |
88 | + security_info["format_supported"] = False |
89 | + security_info["writez_supported"] = False |
90 | + return security_info |
91 | + |
92 | + output = output.decode() |
93 | + |
94 | + for line in output.split("\n"): |
95 | + if "nsze" in line: |
96 | + # According to spec., this should be used as 0-based value. |
97 | + nsze = line.split(":")[1] |
98 | + security_info["nsze"] = int(nsze, 16) - 1 |
99 | + |
100 | + if "flbas" in line: |
101 | + flbas = line.split(":")[1] |
102 | + flbas = int(flbas, 16) |
103 | + security_info["lbaf"] = flbas & 0xF |
104 | + |
105 | + if flbas & 0x10: |
106 | + security_info["ms"] = 1 |
107 | + |
108 | + return security_info |
109 | |
110 | - Uses `hdparam` to get security information about the disk. Sadly hdparam |
111 | - doesn't provide an output that makes it easy to parse. |
112 | + |
113 | +def get_hdparm_security_info(disk): |
114 | + """Get SCSI/ATA disk security info from hdparm. |
115 | + Sadly hdparam doesn't provide an output that makes it easy to parse. |
116 | """ |
117 | + |
118 | # Grab the security section for hdparam. |
119 | security_section = [] |
120 | output = subprocess.check_output([b"hdparm", b"-I", DEV_PATH % disk]) |
121 | @@ -62,57 +145,25 @@ def get_disk_security_info(disk): |
122 | return security_info |
123 | |
124 | |
125 | +def get_disk_security_info(disk): |
126 | + """Get the disk security information. |
127 | + |
128 | + Uses `hdparam` to get security information about the SCSI/ATA disks. |
129 | + If NVMe, nvme-cli is used instead. |
130 | + """ |
131 | + |
132 | + if b"nvme" in disk: |
133 | + return get_nvme_security_info(disk) |
134 | + |
135 | + return get_hdparm_security_info(disk) |
136 | + |
137 | + |
138 | def get_disk_info(): |
139 | """Return dictionary of wipeable disks and thier security information.""" |
140 | return {kname: get_disk_security_info(kname) for kname in list_disks()} |
141 | |
142 | |
143 | -def try_secure_erase(kname, info): |
144 | - """Try to wipe the disk with secure erase.""" |
145 | - if info[b"supported"]: |
146 | - if info[b"frozen"]: |
147 | - print_flush( |
148 | - "%s: not using secure erase; " |
149 | - "drive is currently frozen." % kname.decode("ascii") |
150 | - ) |
151 | - return False |
152 | - elif info[b"locked"]: |
153 | - print_flush( |
154 | - "%s: not using secure erase; " |
155 | - "drive is currently locked." % kname.decode("ascii") |
156 | - ) |
157 | - return False |
158 | - elif info[b"enabled"]: |
159 | - print_flush( |
160 | - "%s: not using secure erase; " |
161 | - "drive security is already enabled." % kname.decode("ascii") |
162 | - ) |
163 | - return False |
164 | - else: |
165 | - # Wiping using secure erase. |
166 | - try: |
167 | - secure_erase(kname) |
168 | - except Exception as e: |
169 | - print_flush( |
170 | - "%s: failed to be securely erased: %s" |
171 | - % (kname.decode("ascii"), e) |
172 | - ) |
173 | - return False |
174 | - else: |
175 | - print_flush( |
176 | - "%s: successfully securely erased." |
177 | - % (kname.decode("ascii")) |
178 | - ) |
179 | - return True |
180 | - else: |
181 | - print_flush( |
182 | - "%s: drive does not support secure erase." |
183 | - % (kname.decode("ascii")) |
184 | - ) |
185 | - return False |
186 | - |
187 | - |
188 | -def secure_erase(kname): |
189 | +def secure_erase_hdparm(kname): |
190 | """Securely wipe the device.""" |
191 | # First write 1 MiB of known data to the beginning of the block device. |
192 | # This is used to check at the end of the secure erase that it worked |
193 | @@ -142,7 +193,7 @@ def secure_erase(kname): |
194 | |
195 | # Now that the user password is set the device should have its |
196 | # security mode enabled. |
197 | - info = get_disk_security_info(kname) |
198 | + info = get_hdparm_security_info(kname) |
199 | if not info[b"enabled"]: |
200 | # If not enabled that means the password did not take, so it does not |
201 | # need to be cleared. |
202 | @@ -167,7 +218,7 @@ def secure_erase(kname): |
203 | failed_exc = exc |
204 | |
205 | # Make sure that the device is now not enabled. |
206 | - info = get_disk_security_info(kname) |
207 | + info = get_hdparm_security_info(kname) |
208 | if info[b"enabled"]: |
209 | # Wipe failed since security is still enabled. |
210 | subprocess.check_output( |
211 | @@ -184,23 +235,197 @@ def secure_erase(kname): |
212 | ) |
213 | |
214 | |
215 | -def wipe_quickly(kname): |
216 | - """Quickly wipe the disk by zeroing the beginning and end of the disk. |
217 | +def try_secure_erase_hdparm(kname, info): |
218 | + """Try to wipe the disk with secure erase.""" |
219 | + if info[b"supported"]: |
220 | + if info[b"frozen"]: |
221 | + print_flush( |
222 | + "%s: not using secure erase; " |
223 | + "drive is currently frozen." % kname.decode("ascii") |
224 | + ) |
225 | + return False |
226 | + elif info[b"locked"]: |
227 | + print_flush( |
228 | + "%s: not using secure erase; " |
229 | + "drive is currently locked." % kname.decode("ascii") |
230 | + ) |
231 | + return False |
232 | + elif info[b"enabled"]: |
233 | + print_flush( |
234 | + "%s: not using secure erase; " |
235 | + "drive security is already enabled." % kname.decode("ascii") |
236 | + ) |
237 | + return False |
238 | + else: |
239 | + # Wiping using secure erase. |
240 | + try: |
241 | + secure_erase_hdparm(kname) |
242 | + except Exception as e: |
243 | + print_flush( |
244 | + "%s: failed to be securely erased: %s" |
245 | + % (kname.decode("ascii"), e) |
246 | + ) |
247 | + return False |
248 | + else: |
249 | + print_flush( |
250 | + "%s: successfully securely erased." |
251 | + % (kname.decode("ascii")) |
252 | + ) |
253 | + return True |
254 | + else: |
255 | + print_flush( |
256 | + "%s: drive does not support secure erase." |
257 | + % (kname.decode("ascii")) |
258 | + ) |
259 | + return False |
260 | + |
261 | + |
262 | +def try_secure_erase_nvme(kname, info): |
263 | + """Perform a secure-erase on NVMe disk if that feature is |
264 | + available. Prefer cryptographic erase, when available.""" |
265 | + |
266 | + if not info["format_supported"]: |
267 | + print_flush( |
268 | + "Device %s does not support formatting" % kname.decode("ascii") |
269 | + ) |
270 | + return False |
271 | + |
272 | + if info["crypto_format"]: |
273 | + ses = 2 |
274 | + else: |
275 | + ses = 1 |
276 | + |
277 | + try: |
278 | + subprocess.check_output( |
279 | + [ |
280 | + "nvme", |
281 | + "format", |
282 | + "-s", |
283 | + str(ses), |
284 | + "-l", |
285 | + str(info["lbaf"]), |
286 | + "-m", |
287 | + str(info["ms"]), |
288 | + DEV_PATH % kname, |
289 | + ] |
290 | + ) |
291 | + except subprocess.CalledProcessError as exc: |
292 | + print_flush("Error with format command (%s)" % exc.returncode) |
293 | + return False |
294 | + except OSError as exc: |
295 | + print_flush("OS error when running nvme-cli (%s)" % exc.strerror) |
296 | + return False |
297 | + |
298 | + print_flush( |
299 | + "Secure erase was successful on NVMe drive %s" % kname.decode("ascii") |
300 | + ) |
301 | + return True |
302 | + |
303 | + |
304 | +def try_secure_erase(kname, info): |
305 | + """Entry-point for secure-erase for SCSI/ATA or NVMe disks.""" |
306 | + |
307 | + if b"nvme" in kname: |
308 | + return try_secure_erase_nvme(kname, info) |
309 | + |
310 | + return try_secure_erase_hdparm(kname, info) |
311 | |
312 | - This is not a secure erase but does make it harder to get the data from |
313 | - the device. |
314 | + |
315 | +def wipe_quickly(kname): |
316 | + """Quickly wipe the disk by using wipefs and zeroing the beginning |
317 | + and end of the disk. This is not a secure erase but does make it |
318 | + harder to get the data from the device and also clears previous layouts. |
319 | """ |
320 | + |
321 | + wipe_error = 0 |
322 | print_flush("%s: starting quick wipe." % kname.decode("ascii")) |
323 | + try: |
324 | + subprocess.check_output(["wipefs", "-f", "-a", DEV_PATH % kname]) |
325 | + wipe_error -= 1 |
326 | + except subprocess.CalledProcessError as exc: |
327 | + print_flush( |
328 | + "%s: wipefs failed (%s)" % (kname.decode("ascii"), exc.returncode) |
329 | + ) |
330 | + wipe_error += 1 |
331 | + |
332 | buf = b"\0" * 1024 * 1024 * 2 # 2 MiB |
333 | - with open(DEV_PATH % kname, "wb") as fp: |
334 | + try: |
335 | + fp = open(DEV_PATH % kname, "wb") |
336 | fp.write(buf) |
337 | fp.seek(-len(buf), 2) |
338 | fp.write(buf) |
339 | - print_flush("%s: successfully quickly wiped." % kname.decode("ascii")) |
340 | + wipe_error -= 1 |
341 | + except OSError as exc: |
342 | + print_flush( |
343 | + "%s: OS error while wiping beginning/end of disk (%s)" |
344 | + % (kname.decode("ascii"), exc.strerror) |
345 | + ) |
346 | + wipe_error += 1 |
347 | + |
348 | + if wipe_error > 0: |
349 | + print_flush("%s: failed to be quickly wiped." % kname.decode("ascii")) |
350 | + else: |
351 | + print_flush("%s: successfully quickly wiped." % kname.decode("ascii")) |
352 | + |
353 | + |
354 | +def nvme_write_zeroes(kname, info): |
355 | + """Perform a write-zeroes operation on NVMe device instead of |
356 | + dd'ing 0 to the entire disk if secure erase is not available. |
357 | + Write-zeroes is a faster way to clean a NVMe disk.""" |
358 | + |
359 | + fallback = False |
360 | + |
361 | + if not info["writez_supported"]: |
362 | + print( |
363 | + "NVMe drive %s does not support write-zeroes" |
364 | + % kname.decode("ascii") |
365 | + ) |
366 | + fallback = True |
367 | + |
368 | + if info["nsze"] <= 0: |
369 | + print( |
370 | + "Bad namespace information collected on NVMe drive %s" |
371 | + % kname.decode("ascii") |
372 | + ) |
373 | + fallback = True |
374 | + |
375 | + if fallback: |
376 | + print_flush("Will fallback to regular drive zeroing.") |
377 | + return False |
378 | + |
379 | + try: |
380 | + subprocess.check_output( |
381 | + [ |
382 | + "nvme", |
383 | + "write-zeroes", |
384 | + "-f", |
385 | + "-s", |
386 | + "0", |
387 | + "-c", |
388 | + str(hex(info["nsze"])[2:]), |
389 | + DEV_PATH % kname, |
390 | + ] |
391 | + ) |
392 | + except subprocess.CalledProcessError as exc: |
393 | + print_flush("Error with write-zeroes command (%s)" % exc.returncode) |
394 | + return False |
395 | + except OSError as exc: |
396 | + print_flush("OS error when running nvme-cli (%s)" % exc.strerror) |
397 | + return False |
398 | + |
399 | + print_flush( |
400 | + "%s: successfully zeroed (using write-zeroes)." % kname.decode("ascii") |
401 | + ) |
402 | + return True |
403 | + |
404 | + |
405 | +def zero_disk(kname, info): |
406 | + """Zero the entire disk, trying write-zeroes first if NVMe disk.""" |
407 | |
408 | + if b"nvme" in kname: |
409 | + if nvme_write_zeroes(kname, info): |
410 | + return |
411 | |
412 | -def zero_disk(kname): |
413 | - """Zero the entire disk.""" |
414 | # Get the total size of the device. |
415 | size = 0 |
416 | with open(DEV_PATH % kname, "rb") as fp: |
417 | @@ -266,8 +491,9 @@ def main(): |
418 | action="store_true", |
419 | default=False, |
420 | help=( |
421 | - "Wipe 1MiB at the start and at the end of the drive to make data " |
422 | - "recovery inconvenient and unlikely to happen by accident. This " |
423 | + "Wipe 2MiB at the start and at the end of the drive to make data " |
424 | + "recovery inconvenient and unlikely to happen by accident. Also, " |
425 | + "it runs wipefs to clear known partition/layout signatures. This " |
426 | "is not secure." |
427 | ), |
428 | ) |
429 | @@ -288,7 +514,7 @@ def main(): |
430 | if args.quick_erase: |
431 | wipe_quickly(kname) |
432 | else: |
433 | - zero_disk(kname) |
434 | + zero_disk(kname, info) |
435 | |
436 | print_flush("All disks have been successfully wiped.") |
437 | |
438 | diff --git a/src/metadataserver/user_data/templates/snippets/tests/test_maas_wipe.py b/src/metadataserver/user_data/templates/snippets/tests/test_maas_wipe.py |
439 | index 995c0b5..191cd02 100644 |
440 | --- a/src/metadataserver/user_data/templates/snippets/tests/test_maas_wipe.py |
441 | +++ b/src/metadataserver/user_data/templates/snippets/tests/test_maas_wipe.py |
442 | @@ -6,6 +6,7 @@ |
443 | __all__ = [] |
444 | |
445 | import argparse |
446 | +import builtins |
447 | import subprocess |
448 | from textwrap import dedent |
449 | from unittest.mock import call, MagicMock |
450 | @@ -22,148 +23,29 @@ from snippets.maas_wipe import ( |
451 | get_disk_info, |
452 | get_disk_security_info, |
453 | list_disks, |
454 | - secure_erase, |
455 | + nvme_write_zeroes, |
456 | + secure_erase_hdparm, |
457 | try_secure_erase, |
458 | wipe_quickly, |
459 | WipeError, |
460 | zero_disk, |
461 | ) |
462 | - |
463 | -HDPARM_BEFORE_SECURITY = b"""\ |
464 | -/dev/sda: |
465 | - |
466 | -ATA device, with non-removable media |
467 | - Model Number: INTEL SSDSC2CT240A4 |
468 | - Serial Number: CVKI3206029X240DGN |
469 | - Firmware Revision: 335u |
470 | - Transport: Serial, ATA8-AST, SATA 1.0a, SATA II Extensions |
471 | -Standards: |
472 | - Used: unknown (minor revision code 0xffff) |
473 | - Supported: 9 8 7 6 5 |
474 | - Likely used: 9 |
475 | -Configuration: |
476 | - Logical max current |
477 | - cylinders 16383 16383 |
478 | - heads 16 16 |
479 | - sectors/track 63 63 |
480 | - -- |
481 | - CHS current addressable sectors: 16514064 |
482 | - LBA user addressable sectors: 268435455 |
483 | - LBA48 user addressable sectors: 468862128 |
484 | - Logical Sector size: 512 bytes |
485 | - Physical Sector size: 512 bytes |
486 | - Logical Sector-0 offset: 0 bytes |
487 | - device size with M = 1024*1024: 228936 MBytes |
488 | - device size with M = 1000*1000: 240057 MBytes (240 GB) |
489 | - cache/buffer size = unknown |
490 | - Nominal Media Rotation Rate: Solid State Device |
491 | -Capabilities: |
492 | - LBA, IORDY(can be disabled) |
493 | - Queue depth: 32 |
494 | - Standby timer values: spec'd by Standard, no device specific minimum |
495 | - R/W multiple sector transfer: Max = 16 Current = 16 |
496 | - Advanced power management level: 254 |
497 | - DMA: mdma0 mdma1 mdma2 udma0 udma1 udma2 udma3 udma4 udma5 *udma6 |
498 | - Cycle time: min=120ns recommended=120ns |
499 | - PIO: pio0 pio1 pio2 pio3 pio4 |
500 | - Cycle time: no flow control=120ns IORDY flow control=120ns |
501 | -Commands/features: |
502 | - Enabled Supported: |
503 | - * SMART feature set |
504 | - Security Mode feature set |
505 | - * Power Management feature set |
506 | - * Write cache |
507 | - * Look-ahead |
508 | - * Host Protected Area feature set |
509 | - * WRITE_BUFFER command |
510 | - * READ_BUFFER command |
511 | - * NOP cmd |
512 | - * DOWNLOAD_MICROCODE |
513 | - * Advanced Power Management feature set |
514 | - Power-Up In Standby feature set |
515 | - * 48-bit Address feature set |
516 | - * Mandatory FLUSH_CACHE |
517 | - * FLUSH_CACHE_EXT |
518 | - * SMART error logging |
519 | - * SMART self-test |
520 | - * General Purpose Logging feature set |
521 | - * WRITE_{DMA|MULTIPLE}_FUA_EXT |
522 | - * 64-bit World wide name |
523 | - * IDLE_IMMEDIATE with UNLOAD |
524 | - * WRITE_UNCORRECTABLE_EXT command |
525 | - * {READ,WRITE}_DMA_EXT_GPL commands |
526 | - * Segmented DOWNLOAD_MICROCODE |
527 | - * Gen1 signaling speed (1.5Gb/s) |
528 | - * Gen2 signaling speed (3.0Gb/s) |
529 | - * Gen3 signaling speed (6.0Gb/s) |
530 | - * Native Command Queueing (NCQ) |
531 | - * Host-initiated interface power management |
532 | - * Phy event counters |
533 | - * DMA Setup Auto-Activate optimization |
534 | - Device-initiated interface power management |
535 | - * Software settings preservation |
536 | - * SMART Command Transport (SCT) feature set |
537 | - * SCT Data Tables (AC5) |
538 | - * reserved 69[4] |
539 | - * Data Set Management TRIM supported (limit 1 block) |
540 | - * Deterministic read data after TRIM |
541 | -""" |
542 | - |
543 | -HDPARM_AFTER_SECURITY = b"""\ |
544 | -Logical Unit WWN Device Identifier: 55cd2e40002643cf |
545 | - NAA : 5 |
546 | - IEEE OUI : 5cd2e4 |
547 | - Unique ID : 0002643cf |
548 | -Checksum: correct |
549 | -""" |
550 | - |
551 | -HDPARM_SECURITY_NOT_SUPPORTED = b"""\ |
552 | -Security: |
553 | - Master password revision code = 65534 |
554 | - not supported |
555 | - not enabled |
556 | - not locked |
557 | - not frozen |
558 | - not expired: security count |
559 | - supported: enhanced erase |
560 | - 4min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT. |
561 | -""" |
562 | - |
563 | -HDPARM_SECURITY_SUPPORTED_NOT_ENABLED = b"""\ |
564 | -Security: |
565 | - Master password revision code = 65534 |
566 | - supported |
567 | - not enabled |
568 | - not locked |
569 | - not frozen |
570 | - not expired: security count |
571 | - supported: enhanced erase |
572 | - 4min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT. |
573 | -""" |
574 | - |
575 | -HDPARM_SECURITY_SUPPORTED_ENABLED = b"""\ |
576 | -Security: |
577 | - Master password revision code = 65534 |
578 | - supported |
579 | - enabled |
580 | - not locked |
581 | - not frozen |
582 | - not expired: security count |
583 | - supported: enhanced erase |
584 | - 4min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT. |
585 | -""" |
586 | - |
587 | -HDPARM_SECURITY_ALL_TRUE = b"""\ |
588 | -Security: |
589 | - Master password revision code = 65534 |
590 | - supported |
591 | - enabled |
592 | - locked |
593 | - frozen |
594 | - not expired: security count |
595 | - supported: enhanced erase |
596 | - 4min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT. |
597 | -""" |
598 | +from snippets.tests.test_maas_wipe_defs import ( |
599 | + HDPARM_AFTER_SECURITY, |
600 | + HDPARM_BEFORE_SECURITY, |
601 | + HDPARM_SECURITY_ALL_TRUE, |
602 | + HDPARM_SECURITY_NOT_SUPPORTED, |
603 | + HDPARM_SECURITY_SUPPORTED_ENABLED, |
604 | + HDPARM_SECURITY_SUPPORTED_NOT_ENABLED, |
605 | + NVME_IDCTRL_EPILOGUE, |
606 | + NVME_IDCTRL_FNA_CRYPTFORMAT_SUPPORTED, |
607 | + NVME_IDCTRL_FNA_CRYPTFORMAT_UNSUPPORTED, |
608 | + NVME_IDCTRL_OACS_FORMAT_SUPPORTED, |
609 | + NVME_IDCTRL_OACS_FORMAT_UNSUPPORTED, |
610 | + NVME_IDCTRL_ONCS_WRITEZ_SUPPORTED, |
611 | + NVME_IDCTRL_ONCS_WRITEZ_UNSUPPORTED, |
612 | + NVME_IDCTRL_PROLOGUE, |
613 | +) |
614 | |
615 | |
616 | class TestMAASWipe(MAASTestCase): |
617 | @@ -196,11 +78,13 @@ class TestMAASWipe(MAASTestCase): |
618 | sdb disk 1 |
619 | sr0 rom 0 |
620 | sr1 rom 0 |
621 | + nvme0n1 disk 0 |
622 | + nvme1n1 disk 1 |
623 | """ |
624 | ).encode("ascii") |
625 | - self.assertEqual([b"sda"], list_disks()) |
626 | + self.assertEqual([b"sda", b"nvme0n1"], list_disks()) |
627 | |
628 | - def test_get_disk_security_info_missing(self): |
629 | + def test_get_disk_security_info_missing_hdparm(self): |
630 | hdparm_output = HDPARM_BEFORE_SECURITY + HDPARM_AFTER_SECURITY |
631 | mock_check_output = self.patch(subprocess, "check_output") |
632 | mock_check_output.return_value = hdparm_output |
633 | @@ -220,7 +104,7 @@ class TestMAASWipe(MAASTestCase): |
634 | observered, |
635 | ) |
636 | |
637 | - def test_get_disk_security_info_not_supported(self): |
638 | + def test_get_disk_security_info_not_supported_hdparm(self): |
639 | hdparm_output = ( |
640 | HDPARM_BEFORE_SECURITY |
641 | + HDPARM_SECURITY_NOT_SUPPORTED |
642 | @@ -244,7 +128,7 @@ class TestMAASWipe(MAASTestCase): |
643 | observered, |
644 | ) |
645 | |
646 | - def test_get_disk_security_info_supported_not_enabled(self): |
647 | + def test_get_disk_security_info_supported_not_enabled_hdparm(self): |
648 | hdparm_output = ( |
649 | HDPARM_BEFORE_SECURITY |
650 | + HDPARM_SECURITY_SUPPORTED_NOT_ENABLED |
651 | @@ -268,7 +152,7 @@ class TestMAASWipe(MAASTestCase): |
652 | observered, |
653 | ) |
654 | |
655 | - def test_get_disk_security_info_supported_enabled(self): |
656 | + def test_get_disk_security_info_supported_enabled_hdparm(self): |
657 | hdparm_output = ( |
658 | HDPARM_BEFORE_SECURITY |
659 | + HDPARM_SECURITY_SUPPORTED_ENABLED |
660 | @@ -292,7 +176,7 @@ class TestMAASWipe(MAASTestCase): |
661 | observered, |
662 | ) |
663 | |
664 | - def test_get_disk_security_info_all_true(self): |
665 | + def test_get_disk_security_info_all_true_hdparm(self): |
666 | hdparm_output = ( |
667 | HDPARM_BEFORE_SECURITY |
668 | + HDPARM_SECURITY_ALL_TRUE |
669 | @@ -316,7 +200,7 @@ class TestMAASWipe(MAASTestCase): |
670 | observered, |
671 | ) |
672 | |
673 | - def test_get_disk_info(self): |
674 | + def test_get_disk_info_hdparm(self): |
675 | disk_names = [ |
676 | factory.make_name("disk").encode("ascii") for _ in range(3) |
677 | ] |
678 | @@ -331,14 +215,243 @@ class TestMAASWipe(MAASTestCase): |
679 | for _ in range(3) |
680 | ] |
681 | self.patch( |
682 | - maas_wipe, "get_disk_security_info" |
683 | + maas_wipe, "get_hdparm_security_info" |
684 | + ).side_effect = security_info |
685 | + observed = get_disk_info() |
686 | + self.assertEqual( |
687 | + {disk_names[i]: security_info[i] for i in range(3)}, observed |
688 | + ) |
689 | + |
690 | + def test_get_disk_security_info_crypt_format_writez_nvme(self): |
691 | + nvme_cli_output = ( |
692 | + NVME_IDCTRL_PROLOGUE |
693 | + + NVME_IDCTRL_OACS_FORMAT_SUPPORTED |
694 | + + NVME_IDCTRL_ONCS_WRITEZ_SUPPORTED |
695 | + + NVME_IDCTRL_FNA_CRYPTFORMAT_SUPPORTED |
696 | + + NVME_IDCTRL_EPILOGUE |
697 | + ) |
698 | + mock_check_output = self.patch(subprocess, "check_output") |
699 | + mock_check_output.return_value = nvme_cli_output |
700 | + disk_name = factory.make_name("nvme").encode("ascii") |
701 | + observered = get_disk_security_info(disk_name) |
702 | + self.assertThat( |
703 | + mock_check_output, |
704 | + MockCallsMatch( |
705 | + call(["nvme", "id-ctrl", maas_wipe.DEV_PATH % disk_name]), |
706 | + call(["nvme", "id-ns", maas_wipe.DEV_PATH % disk_name]), |
707 | + ), |
708 | + ) |
709 | + self.assertEqual( |
710 | + { |
711 | + "format_supported": True, |
712 | + "writez_supported": True, |
713 | + "crypto_format": True, |
714 | + "nsze": 0, |
715 | + "lbaf": 0, |
716 | + "ms": 0, |
717 | + }, |
718 | + observered, |
719 | + ) |
720 | + |
721 | + def test_get_disk_security_info_nocrypt_format_writez_nvme(self): |
722 | + nvme_cli_output = ( |
723 | + NVME_IDCTRL_PROLOGUE |
724 | + + NVME_IDCTRL_OACS_FORMAT_SUPPORTED |
725 | + + NVME_IDCTRL_ONCS_WRITEZ_SUPPORTED |
726 | + + NVME_IDCTRL_FNA_CRYPTFORMAT_UNSUPPORTED |
727 | + + NVME_IDCTRL_EPILOGUE |
728 | + ) |
729 | + mock_check_output = self.patch(subprocess, "check_output") |
730 | + mock_check_output.return_value = nvme_cli_output |
731 | + disk_name = factory.make_name("nvme").encode("ascii") |
732 | + observered = get_disk_security_info(disk_name) |
733 | + self.assertThat( |
734 | + mock_check_output, |
735 | + MockCallsMatch( |
736 | + call(["nvme", "id-ctrl", maas_wipe.DEV_PATH % disk_name]), |
737 | + call(["nvme", "id-ns", maas_wipe.DEV_PATH % disk_name]), |
738 | + ), |
739 | + ) |
740 | + self.assertEqual( |
741 | + { |
742 | + "format_supported": True, |
743 | + "writez_supported": True, |
744 | + "crypto_format": False, |
745 | + "nsze": 0, |
746 | + "lbaf": 0, |
747 | + "ms": 0, |
748 | + }, |
749 | + observered, |
750 | + ) |
751 | + |
752 | + def test_get_disk_security_info_crypt_format_nowritez_nvme(self): |
753 | + nvme_cli_output = ( |
754 | + NVME_IDCTRL_PROLOGUE |
755 | + + NVME_IDCTRL_OACS_FORMAT_SUPPORTED |
756 | + + NVME_IDCTRL_ONCS_WRITEZ_UNSUPPORTED |
757 | + + NVME_IDCTRL_FNA_CRYPTFORMAT_SUPPORTED |
758 | + + NVME_IDCTRL_EPILOGUE |
759 | + ) |
760 | + mock_check_output = self.patch(subprocess, "check_output") |
761 | + mock_check_output.return_value = nvme_cli_output |
762 | + disk_name = factory.make_name("nvme").encode("ascii") |
763 | + observered = get_disk_security_info(disk_name) |
764 | + self.assertThat( |
765 | + mock_check_output, |
766 | + MockCallsMatch( |
767 | + call(["nvme", "id-ctrl", maas_wipe.DEV_PATH % disk_name]), |
768 | + call(["nvme", "id-ns", maas_wipe.DEV_PATH % disk_name]), |
769 | + ), |
770 | + ) |
771 | + self.assertEqual( |
772 | + { |
773 | + "format_supported": True, |
774 | + "writez_supported": False, |
775 | + "crypto_format": True, |
776 | + "nsze": 0, |
777 | + "lbaf": 0, |
778 | + "ms": 0, |
779 | + }, |
780 | + observered, |
781 | + ) |
782 | + |
783 | + def test_get_disk_security_info_noformat_writez_nvme(self): |
784 | + nvme_cli_output = ( |
785 | + NVME_IDCTRL_PROLOGUE |
786 | + + NVME_IDCTRL_OACS_FORMAT_UNSUPPORTED |
787 | + + NVME_IDCTRL_ONCS_WRITEZ_SUPPORTED |
788 | + + NVME_IDCTRL_FNA_CRYPTFORMAT_SUPPORTED |
789 | + + NVME_IDCTRL_EPILOGUE |
790 | + ) |
791 | + mock_check_output = self.patch(subprocess, "check_output") |
792 | + mock_check_output.return_value = nvme_cli_output |
793 | + disk_name = factory.make_name("nvme").encode("ascii") |
794 | + observered = get_disk_security_info(disk_name) |
795 | + self.assertThat( |
796 | + mock_check_output, |
797 | + MockCallsMatch( |
798 | + call(["nvme", "id-ctrl", maas_wipe.DEV_PATH % disk_name]), |
799 | + call(["nvme", "id-ns", maas_wipe.DEV_PATH % disk_name]), |
800 | + ), |
801 | + ) |
802 | + self.assertEqual( |
803 | + { |
804 | + "format_supported": False, |
805 | + "writez_supported": True, |
806 | + "crypto_format": True, |
807 | + "nsze": 0, |
808 | + "lbaf": 0, |
809 | + "ms": 0, |
810 | + }, |
811 | + observered, |
812 | + ) |
813 | + |
814 | + def test_get_disk_security_info_noformat_nowritez_nvme(self): |
815 | + nvme_cli_output = ( |
816 | + NVME_IDCTRL_PROLOGUE |
817 | + + NVME_IDCTRL_OACS_FORMAT_UNSUPPORTED |
818 | + + NVME_IDCTRL_ONCS_WRITEZ_UNSUPPORTED |
819 | + + NVME_IDCTRL_FNA_CRYPTFORMAT_UNSUPPORTED |
820 | + + NVME_IDCTRL_EPILOGUE |
821 | + ) |
822 | + mock_check_output = self.patch(subprocess, "check_output") |
823 | + mock_check_output.return_value = nvme_cli_output |
824 | + disk_name = factory.make_name("nvme").encode("ascii") |
825 | + observered = get_disk_security_info(disk_name) |
826 | + self.assertThat( |
827 | + mock_check_output, |
828 | + MockCallsMatch( |
829 | + call(["nvme", "id-ctrl", maas_wipe.DEV_PATH % disk_name]), |
830 | + call(["nvme", "id-ns", maas_wipe.DEV_PATH % disk_name]), |
831 | + ), |
832 | + ) |
833 | + self.assertEqual( |
834 | + { |
835 | + "format_supported": False, |
836 | + "writez_supported": False, |
837 | + "crypto_format": False, |
838 | + "nsze": 0, |
839 | + "lbaf": 0, |
840 | + "ms": 0, |
841 | + }, |
842 | + observered, |
843 | + ) |
844 | + |
845 | + def test_get_disk_security_info_failed_cmd_nvme(self): |
846 | + mock_check_output = self.patch(subprocess, "check_output") |
847 | + mock_check_output.side_effect = subprocess.CalledProcessError( |
848 | + 1, "nvme id-ctrl" |
849 | + ) |
850 | + disk_name = factory.make_name("nvme").encode("ascii") |
851 | + observered = get_disk_security_info(disk_name) |
852 | + |
853 | + self.assertThat( |
854 | + self.print_flush, |
855 | + MockCalledOnceWith("Error on nvme id-ctrl (%s)" % "1"), |
856 | + ) |
857 | + self.assertEqual( |
858 | + { |
859 | + "format_supported": False, |
860 | + "writez_supported": False, |
861 | + "crypto_format": False, |
862 | + "nsze": 0, |
863 | + "lbaf": 0, |
864 | + "ms": 0, |
865 | + }, |
866 | + observered, |
867 | + ) |
868 | + |
869 | + def test_get_disk_security_info_failed_os_nvme(self): |
870 | + mock_check_output = self.patch(subprocess, "check_output") |
871 | + mock_check_output.side_effect = OSError( |
872 | + -2, "No such file or directory" |
873 | + ) |
874 | + disk_name = factory.make_name("nvme").encode("ascii") |
875 | + observered = get_disk_security_info(disk_name) |
876 | + |
877 | + self.assertThat( |
878 | + self.print_flush, |
879 | + MockCalledOnceWith( |
880 | + "OS error when running nvme-cli (No such file or directory)" |
881 | + ), |
882 | + ) |
883 | + self.assertEqual( |
884 | + { |
885 | + "format_supported": False, |
886 | + "writez_supported": False, |
887 | + "crypto_format": False, |
888 | + "nsze": 0, |
889 | + "lbaf": 0, |
890 | + "ms": 0, |
891 | + }, |
892 | + observered, |
893 | + ) |
894 | + |
895 | + def test_get_disk_info_nvme(self): |
896 | + disk_names = [ |
897 | + factory.make_name("nvme").encode("ascii") for _ in range(3) |
898 | + ] |
899 | + self.patch(maas_wipe, "list_disks").return_value = disk_names |
900 | + security_info = [ |
901 | + { |
902 | + "format_supported": True, |
903 | + "writez_supported": True, |
904 | + "crypto_format": True, |
905 | + "nsze": 0, |
906 | + "lbaf": 0, |
907 | + "ms": 0, |
908 | + } |
909 | + for _ in range(3) |
910 | + ] |
911 | + self.patch( |
912 | + maas_wipe, "get_nvme_security_info" |
913 | ).side_effect = security_info |
914 | observed = get_disk_info() |
915 | self.assertEqual( |
916 | {disk_names[i]: security_info[i] for i in range(3)}, observed |
917 | ) |
918 | |
919 | - def test_try_secure_erase_not_supported(self): |
920 | + def test_try_secure_erase_not_supported_hdparm(self): |
921 | disk_name = factory.make_name("disk").encode("ascii") |
922 | disk_info = { |
923 | b"supported": False, |
924 | @@ -355,7 +468,7 @@ class TestMAASWipe(MAASTestCase): |
925 | ), |
926 | ) |
927 | |
928 | - def test_try_secure_erase_frozen(self): |
929 | + def test_try_secure_erase_frozen_hdparm(self): |
930 | disk_name = factory.make_name("disk").encode("ascii") |
931 | disk_info = { |
932 | b"supported": True, |
933 | @@ -372,7 +485,7 @@ class TestMAASWipe(MAASTestCase): |
934 | ), |
935 | ) |
936 | |
937 | - def test_try_secure_erase_locked(self): |
938 | + def test_try_secure_erase_locked_hdparm(self): |
939 | disk_name = factory.make_name("disk").encode("ascii") |
940 | disk_info = { |
941 | b"supported": True, |
942 | @@ -389,7 +502,7 @@ class TestMAASWipe(MAASTestCase): |
943 | ), |
944 | ) |
945 | |
946 | - def test_try_secure_erase_enabled(self): |
947 | + def test_try_secure_erase_enabled_hdparm(self): |
948 | disk_name = factory.make_name("disk").encode("ascii") |
949 | disk_info = { |
950 | b"supported": True, |
951 | @@ -406,7 +519,7 @@ class TestMAASWipe(MAASTestCase): |
952 | ), |
953 | ) |
954 | |
955 | - def test_try_secure_erase_failed_erase(self): |
956 | + def test_try_secure_erase_failed_erase_hdparm(self): |
957 | disk_name = factory.make_name("disk").encode("ascii") |
958 | disk_info = { |
959 | b"supported": True, |
960 | @@ -415,7 +528,7 @@ class TestMAASWipe(MAASTestCase): |
961 | b"frozen": False, |
962 | } |
963 | exception = factory.make_exception() |
964 | - self.patch(maas_wipe, "secure_erase").side_effect = exception |
965 | + self.patch(maas_wipe, "secure_erase_hdparm").side_effect = exception |
966 | self.assertFalse(try_secure_erase(disk_name, disk_info)) |
967 | self.assertThat( |
968 | self.print_flush, |
969 | @@ -425,7 +538,7 @@ class TestMAASWipe(MAASTestCase): |
970 | ), |
971 | ) |
972 | |
973 | - def test_try_secure_erase_successful_erase(self): |
974 | + def test_try_secure_erase_successful_erase_hdparm(self): |
975 | disk_name = factory.make_name("disk").encode("ascii") |
976 | disk_info = { |
977 | b"supported": True, |
978 | @@ -433,7 +546,7 @@ class TestMAASWipe(MAASTestCase): |
979 | b"locked": False, |
980 | b"frozen": False, |
981 | } |
982 | - self.patch(maas_wipe, "secure_erase") |
983 | + self.patch(maas_wipe, "secure_erase_hdparm") |
984 | self.assertTrue(try_secure_erase(disk_name, disk_info)) |
985 | self.assertThat( |
986 | self.print_flush, |
987 | @@ -443,7 +556,223 @@ class TestMAASWipe(MAASTestCase): |
988 | ), |
989 | ) |
990 | |
991 | - def test_secure_erase_writes_known_data(self): |
992 | + def test_try_secure_erase_not_supported_nvme(self): |
993 | + disk_name = factory.make_name("nvme").encode("ascii") |
994 | + sec_info = { |
995 | + "format_supported": False, |
996 | + "writez_supported": True, |
997 | + "crypto_format": True, |
998 | + "nsze": 0, |
999 | + "lbaf": 0, |
1000 | + "ms": 0, |
1001 | + } |
1002 | + self.assertFalse(try_secure_erase(disk_name, sec_info)) |
1003 | + self.assertThat( |
1004 | + self.print_flush, |
1005 | + MockCalledOnceWith( |
1006 | + "Device %s does not support formatting" |
1007 | + % disk_name.decode("ascii") |
1008 | + ), |
1009 | + ) |
1010 | + |
1011 | + def test_try_secure_erase_successful_cryto_nvme(self): |
1012 | + disk_name = factory.make_name("nvme").encode("ascii") |
1013 | + sec_info = { |
1014 | + "format_supported": True, |
1015 | + "writez_supported": True, |
1016 | + "crypto_format": True, |
1017 | + "nsze": 0, |
1018 | + "lbaf": 0, |
1019 | + "ms": 0, |
1020 | + } |
1021 | + mock_check_output = self.patch(subprocess, "check_output") |
1022 | + self.assertTrue(try_secure_erase(disk_name, sec_info)) |
1023 | + self.assertThat( |
1024 | + mock_check_output, |
1025 | + MockCalledOnceWith( |
1026 | + [ |
1027 | + "nvme", |
1028 | + "format", |
1029 | + "-s", |
1030 | + "2", |
1031 | + "-l", |
1032 | + "0", |
1033 | + "-m", |
1034 | + "0", |
1035 | + maas_wipe.DEV_PATH % disk_name, |
1036 | + ] |
1037 | + ), |
1038 | + ) |
1039 | + self.assertThat( |
1040 | + self.print_flush, |
1041 | + MockCalledOnceWith( |
1042 | + "Secure erase was successful on NVMe drive %s" |
1043 | + % disk_name.decode("ascii") |
1044 | + ), |
1045 | + ) |
1046 | + |
1047 | + def test_try_secure_erase_successful_nocryto_nvme(self): |
1048 | + disk_name = factory.make_name("nvme").encode("ascii") |
1049 | + sec_info = { |
1050 | + "format_supported": True, |
1051 | + "writez_supported": True, |
1052 | + "crypto_format": False, |
1053 | + "nsze": 0, |
1054 | + "lbaf": 0, |
1055 | + "ms": 0, |
1056 | + } |
1057 | + mock_check_output = self.patch(subprocess, "check_output") |
1058 | + self.assertTrue(try_secure_erase(disk_name, sec_info)) |
1059 | + self.assertThat( |
1060 | + mock_check_output, |
1061 | + MockCalledOnceWith( |
1062 | + [ |
1063 | + "nvme", |
1064 | + "format", |
1065 | + "-s", |
1066 | + "1", |
1067 | + "-l", |
1068 | + "0", |
1069 | + "-m", |
1070 | + "0", |
1071 | + maas_wipe.DEV_PATH % disk_name, |
1072 | + ] |
1073 | + ), |
1074 | + ) |
1075 | + self.assertThat( |
1076 | + self.print_flush, |
1077 | + MockCalledOnceWith( |
1078 | + "Secure erase was successful on NVMe drive %s" |
1079 | + % disk_name.decode("ascii") |
1080 | + ), |
1081 | + ) |
1082 | + |
1083 | + def test_try_secure_erase_failed_nvme(self): |
1084 | + disk_name = factory.make_name("nvme").encode("ascii") |
1085 | + sec_info = { |
1086 | + "format_supported": True, |
1087 | + "writez_supported": True, |
1088 | + "crypto_format": True, |
1089 | + "nsze": 0, |
1090 | + "lbaf": 0, |
1091 | + "ms": 0, |
1092 | + } |
1093 | + mock_check_output = self.patch(subprocess, "check_output") |
1094 | + mock_check_output.side_effect = subprocess.CalledProcessError( |
1095 | + 1, "nvme format" |
1096 | + ) |
1097 | + |
1098 | + self.assertFalse(try_secure_erase(disk_name, sec_info)) |
1099 | + self.assertThat( |
1100 | + self.print_flush, |
1101 | + MockCalledOnceWith("Error with format command (%s)" % "1"), |
1102 | + ) |
1103 | + |
1104 | + def test_try_write_zeroes_not_supported_nvme(self): |
1105 | + disk_name = factory.make_name("nvme").encode("ascii") |
1106 | + sec_info = { |
1107 | + "format_supported": False, |
1108 | + "writez_supported": False, |
1109 | + "crypto_format": False, |
1110 | + "nsze": 1, |
1111 | + "lbaf": 0, |
1112 | + "ms": 0, |
1113 | + } |
1114 | + mock_print = self.patch(builtins, "print") |
1115 | + self.assertFalse(nvme_write_zeroes(disk_name, sec_info)) |
1116 | + self.assertThat( |
1117 | + mock_print, |
1118 | + MockCalledOnceWith( |
1119 | + "NVMe drive %s does not support write-zeroes" |
1120 | + % disk_name.decode("ascii") |
1121 | + ), |
1122 | + ) |
1123 | + self.assertThat( |
1124 | + self.print_flush, |
1125 | + MockCalledOnceWith("Will fallback to regular drive zeroing."), |
1126 | + ) |
1127 | + |
1128 | + def test_try_write_zeroes_supported_invalid_nsze_nvme(self): |
1129 | + disk_name = factory.make_name("nvme").encode("ascii") |
1130 | + sec_info = { |
1131 | + "format_supported": False, |
1132 | + "writez_supported": True, |
1133 | + "crypto_format": False, |
1134 | + "nsze": 0, |
1135 | + "lbaf": 0, |
1136 | + "ms": 0, |
1137 | + } |
1138 | + mock_print = self.patch(builtins, "print") |
1139 | + self.assertFalse(nvme_write_zeroes(disk_name, sec_info)) |
1140 | + self.assertThat( |
1141 | + mock_print, |
1142 | + MockCalledOnceWith( |
1143 | + "Bad namespace information collected on NVMe drive %s" |
1144 | + % disk_name.decode("ascii") |
1145 | + ), |
1146 | + ) |
1147 | + self.assertThat( |
1148 | + self.print_flush, |
1149 | + MockCalledOnceWith("Will fallback to regular drive zeroing."), |
1150 | + ) |
1151 | + |
1152 | + def test_try_write_zeroes_successful_nvme(self): |
1153 | + disk_name = factory.make_name("nvme").encode("ascii") |
1154 | + sec_info = { |
1155 | + "format_supported": False, |
1156 | + "writez_supported": True, |
1157 | + "crypto_format": False, |
1158 | + "nsze": 0x100A, |
1159 | + "lbaf": 0, |
1160 | + "ms": 0, |
1161 | + } |
1162 | + mock_check_output = self.patch(subprocess, "check_output") |
1163 | + self.assertTrue(nvme_write_zeroes(disk_name, sec_info)) |
1164 | + self.assertThat( |
1165 | + mock_check_output, |
1166 | + MockCalledOnceWith( |
1167 | + [ |
1168 | + "nvme", |
1169 | + "write-zeroes", |
1170 | + "-f", |
1171 | + "-s", |
1172 | + "0", |
1173 | + "-c", |
1174 | + "100a", |
1175 | + maas_wipe.DEV_PATH % disk_name, |
1176 | + ] |
1177 | + ), |
1178 | + ) |
1179 | + self.assertThat( |
1180 | + self.print_flush, |
1181 | + MockCalledOnceWith( |
1182 | + "%s: successfully zeroed (using write-zeroes)." |
1183 | + % disk_name.decode("ascii") |
1184 | + ), |
1185 | + ) |
1186 | + |
1187 | + def test_try_write_zeroes_failed_nvme(self): |
1188 | + disk_name = factory.make_name("nvme").encode("ascii") |
1189 | + sec_info = { |
1190 | + "format_supported": False, |
1191 | + "writez_supported": True, |
1192 | + "crypto_format": False, |
1193 | + "nsze": 100, |
1194 | + "lbaf": 0, |
1195 | + "ms": 0, |
1196 | + } |
1197 | + mock_check_output = self.patch(subprocess, "check_output") |
1198 | + mock_check_output.side_effect = subprocess.CalledProcessError( |
1199 | + 1, "nvme write-zeroes" |
1200 | + ) |
1201 | + |
1202 | + self.assertFalse(nvme_write_zeroes(disk_name, sec_info)) |
1203 | + self.assertThat( |
1204 | + self.print_flush, |
1205 | + MockCalledOnceWith("Error with write-zeroes command (%s)" % "1"), |
1206 | + ) |
1207 | + |
1208 | + def test_secure_erase_writes_known_data_hdparm(self): |
1209 | tmp_dir = self.make_dir() |
1210 | dev_path = (tmp_dir + "/%s").encode("ascii") |
1211 | self.patch(maas_wipe, "DEV_PATH", dev_path) |
1212 | @@ -455,7 +784,7 @@ class TestMAASWipe(MAASTestCase): |
1213 | mock_check_output = self.patch(subprocess, "check_output") |
1214 | mock_check_output.side_effect = factory.make_exception() |
1215 | |
1216 | - self.assertRaises(WipeError, secure_erase, dev_name) |
1217 | + self.assertRaises(WipeError, secure_erase_hdparm, dev_name) |
1218 | expected_buf = b"M" * 1024 * 1024 |
1219 | with open(file_path, "rb") as fp: |
1220 | read_buf = fp.read(len(expected_buf)) |
1221 | @@ -463,7 +792,7 @@ class TestMAASWipe(MAASTestCase): |
1222 | expected_buf, read_buf, "First 1 MiB of file was not written." |
1223 | ) |
1224 | |
1225 | - def test_secure_erase_sets_security_password(self): |
1226 | + def test_secure_erase_sets_security_password_hdparm(self): |
1227 | tmp_dir = self.make_dir() |
1228 | dev_path = (tmp_dir + "/%s").encode("ascii") |
1229 | self.patch(maas_wipe, "DEV_PATH", dev_path) |
1230 | @@ -476,10 +805,10 @@ class TestMAASWipe(MAASTestCase): |
1231 | # Fail to get disk info just to exit early. |
1232 | exception_type = factory.make_exception_type() |
1233 | self.patch( |
1234 | - maas_wipe, "get_disk_security_info" |
1235 | + maas_wipe, "get_hdparm_security_info" |
1236 | ).side_effect = exception_type() |
1237 | |
1238 | - self.assertRaises(exception_type, secure_erase, dev_name) |
1239 | + self.assertRaises(exception_type, secure_erase_hdparm, dev_name) |
1240 | self.assertThat( |
1241 | mock_check_output, |
1242 | MockCalledOnceWith( |
1243 | @@ -494,7 +823,7 @@ class TestMAASWipe(MAASTestCase): |
1244 | ), |
1245 | ) |
1246 | |
1247 | - def test_secure_erase_fails_if_not_enabled(self): |
1248 | + def test_secure_erase_fails_if_not_enabled_hdparm(self): |
1249 | tmp_dir = self.make_dir() |
1250 | dev_path = (tmp_dir + "/%s").encode("ascii") |
1251 | self.patch(maas_wipe, "DEV_PATH", dev_path) |
1252 | @@ -503,16 +832,16 @@ class TestMAASWipe(MAASTestCase): |
1253 | self.make_empty_file(file_path) |
1254 | |
1255 | self.patch(subprocess, "check_output") |
1256 | - self.patch(maas_wipe, "get_disk_security_info").return_value = { |
1257 | + self.patch(maas_wipe, "get_hdparm_security_info").return_value = { |
1258 | b"enabled": False |
1259 | } |
1260 | |
1261 | - error = self.assertRaises(WipeError, secure_erase, dev_name) |
1262 | + error = self.assertRaises(WipeError, secure_erase_hdparm, dev_name) |
1263 | self.assertEqual( |
1264 | "Failed to enable security to perform secure erase.", str(error) |
1265 | ) |
1266 | |
1267 | - def test_secure_erase_fails_when_still_enabled(self): |
1268 | + def test_secure_erase_fails_when_still_enabled_hdparm(self): |
1269 | tmp_dir = self.make_dir() |
1270 | dev_path = (tmp_dir + "/%s").encode("ascii") |
1271 | self.patch(maas_wipe, "DEV_PATH", dev_path) |
1272 | @@ -521,14 +850,14 @@ class TestMAASWipe(MAASTestCase): |
1273 | self.make_empty_file(file_path) |
1274 | |
1275 | mock_check_output = self.patch(subprocess, "check_output") |
1276 | - self.patch(maas_wipe, "get_disk_security_info").return_value = { |
1277 | + self.patch(maas_wipe, "get_hdparm_security_info").return_value = { |
1278 | b"enabled": True |
1279 | } |
1280 | exception = factory.make_exception() |
1281 | mock_check_call = self.patch(subprocess, "check_call") |
1282 | mock_check_call.side_effect = exception |
1283 | |
1284 | - error = self.assertRaises(WipeError, secure_erase, dev_name) |
1285 | + error = self.assertRaises(WipeError, secure_erase_hdparm, dev_name) |
1286 | self.assertThat( |
1287 | mock_check_call, |
1288 | MockCalledOnceWith( |
1289 | @@ -561,7 +890,7 @@ class TestMAASWipe(MAASTestCase): |
1290 | self.assertEqual("Failed to securely erase.", str(error)) |
1291 | self.assertEqual(exception, error.__cause__) |
1292 | |
1293 | - def test_secure_erase_fails_when_buffer_not_different(self): |
1294 | + def test_secure_erase_fails_when_buffer_not_different_hdparm(self): |
1295 | tmp_dir = self.make_dir() |
1296 | dev_path = (tmp_dir + "/%s").encode("ascii") |
1297 | self.patch(maas_wipe, "DEV_PATH", dev_path) |
1298 | @@ -570,13 +899,13 @@ class TestMAASWipe(MAASTestCase): |
1299 | self.make_empty_file(file_path) |
1300 | |
1301 | self.patch(subprocess, "check_output") |
1302 | - self.patch(maas_wipe, "get_disk_security_info").side_effect = [ |
1303 | + self.patch(maas_wipe, "get_hdparm_security_info").side_effect = [ |
1304 | {b"enabled": True}, |
1305 | {b"enabled": False}, |
1306 | ] |
1307 | mock_check_call = self.patch(subprocess, "check_call") |
1308 | |
1309 | - error = self.assertRaises(WipeError, secure_erase, dev_name) |
1310 | + error = self.assertRaises(WipeError, secure_erase_hdparm, dev_name) |
1311 | self.assertThat( |
1312 | mock_check_call, |
1313 | MockCalledOnceWith( |
1314 | @@ -595,7 +924,7 @@ class TestMAASWipe(MAASTestCase): |
1315 | str(error), |
1316 | ) |
1317 | |
1318 | - def test_secure_erase_fails_success(self): |
1319 | + def test_secure_erase_fails_success_hdparm(self): |
1320 | tmp_dir = self.make_dir() |
1321 | dev_path = (tmp_dir + "/%s").encode("ascii") |
1322 | self.patch(maas_wipe, "DEV_PATH", dev_path) |
1323 | @@ -604,7 +933,7 @@ class TestMAASWipe(MAASTestCase): |
1324 | self.make_empty_file(file_path) |
1325 | |
1326 | self.patch(subprocess, "check_output") |
1327 | - self.patch(maas_wipe, "get_disk_security_info").side_effect = [ |
1328 | + self.patch(maas_wipe, "get_hdparm_security_info").side_effect = [ |
1329 | {b"enabled": True}, |
1330 | {b"enabled": False}, |
1331 | ] |
1332 | @@ -620,9 +949,9 @@ class TestMAASWipe(MAASTestCase): |
1333 | mock_check_call.side_effect = wipe_buffer |
1334 | |
1335 | # No error should be raised. |
1336 | - secure_erase(dev_name) |
1337 | + secure_erase_hdparm(dev_name) |
1338 | |
1339 | - def test_wipe_quickly(self): |
1340 | + def test_wipe_quickly_successful(self): |
1341 | tmp_dir = self.make_dir() |
1342 | dev_path = (tmp_dir + "/%s").encode("ascii") |
1343 | self.patch(maas_wipe, "DEV_PATH", dev_path) |
1344 | @@ -630,7 +959,14 @@ class TestMAASWipe(MAASTestCase): |
1345 | file_path = dev_path % dev_name |
1346 | self.make_empty_file(file_path, content=b"T") |
1347 | |
1348 | + mock_check_output = self.patch(subprocess, "check_output") |
1349 | wipe_quickly(dev_name) |
1350 | + self.assertThat( |
1351 | + mock_check_output, |
1352 | + MockCalledOnceWith( |
1353 | + ["wipefs", "-f", "-a", maas_wipe.DEV_PATH % dev_name] |
1354 | + ), |
1355 | + ) |
1356 | |
1357 | buf_size = 1024 * 1024 |
1358 | with open(file_path, "rb") as fp: |
1359 | @@ -641,8 +977,18 @@ class TestMAASWipe(MAASTestCase): |
1360 | zero_buf = b"\0" * 1024 * 1024 |
1361 | self.assertEqual(zero_buf, first_buf, "First 1 MiB was not wiped.") |
1362 | self.assertEqual(zero_buf, last_buf, "Last 1 MiB was not wiped.") |
1363 | + self.assertThat( |
1364 | + self.print_flush, |
1365 | + MockCallsMatch( |
1366 | + call("%s: starting quick wipe." % dev_name.decode("ascii")), |
1367 | + call( |
1368 | + "%s: successfully quickly wiped." |
1369 | + % dev_name.decode("ascii") |
1370 | + ), |
1371 | + ), |
1372 | + ) |
1373 | |
1374 | - def test_zero_disk(self): |
1375 | + def test_wipe_quickly_successful_but_wipefs_failed(self): |
1376 | tmp_dir = self.make_dir() |
1377 | dev_path = (tmp_dir + "/%s").encode("ascii") |
1378 | self.patch(maas_wipe, "DEV_PATH", dev_path) |
1379 | @@ -650,13 +996,83 @@ class TestMAASWipe(MAASTestCase): |
1380 | file_path = dev_path % dev_name |
1381 | self.make_empty_file(file_path, content=b"T") |
1382 | |
1383 | + mock_check_output = self.patch(subprocess, "check_output") |
1384 | + mock_check_output.side_effect = subprocess.CalledProcessError( |
1385 | + 1, "wipefs" |
1386 | + ) |
1387 | + wipe_quickly(dev_name) |
1388 | + |
1389 | + buf_size = 1024 * 1024 |
1390 | + with open(file_path, "rb") as fp: |
1391 | + first_buf = fp.read(buf_size) |
1392 | + fp.seek(-buf_size, 2) |
1393 | + last_buf = fp.read(buf_size) |
1394 | + |
1395 | + zero_buf = b"\0" * 1024 * 1024 |
1396 | + self.assertEqual(zero_buf, first_buf, "First 1 MiB was not wiped.") |
1397 | + self.assertEqual(zero_buf, last_buf, "Last 1 MiB was not wiped.") |
1398 | + self.assertThat( |
1399 | + self.print_flush, |
1400 | + MockCallsMatch( |
1401 | + call("%s: starting quick wipe." % dev_name.decode("ascii")), |
1402 | + call("%s: wipefs failed (1)" % dev_name.decode("ascii")), |
1403 | + call( |
1404 | + "%s: successfully quickly wiped." |
1405 | + % dev_name.decode("ascii") |
1406 | + ), |
1407 | + ), |
1408 | + ) |
1409 | + |
1410 | + def test_wipe_quickly_failed(self): |
1411 | + dev_name = factory.make_name("disk").encode("ascii") |
1412 | + |
1413 | + mock_check_output = self.patch(subprocess, "check_output") |
1414 | + mock_check_output.side_effect = subprocess.CalledProcessError( |
1415 | + 1, "wipefs" |
1416 | + ) |
1417 | + |
1418 | + mock_os_open = self.patch(builtins, "open") |
1419 | + mock_os_open.side_effect = OSError(-2, "No such file or directory") |
1420 | + |
1421 | + wipe_quickly(dev_name) |
1422 | + |
1423 | + self.assertThat( |
1424 | + self.print_flush, |
1425 | + MockCallsMatch( |
1426 | + call("%s: starting quick wipe." % dev_name.decode("ascii")), |
1427 | + call("%s: wipefs failed (1)" % dev_name.decode("ascii")), |
1428 | + call( |
1429 | + "%s: OS error while wiping beginning/end of disk (No such file or directory)" |
1430 | + % dev_name.decode("ascii") |
1431 | + ), |
1432 | + call( |
1433 | + "%s: failed to be quickly wiped." |
1434 | + % dev_name.decode("ascii") |
1435 | + ), |
1436 | + ), |
1437 | + ) |
1438 | + |
1439 | + def test_zero_disk_hdd(self): |
1440 | + tmp_dir = self.make_dir() |
1441 | + dev_path = (tmp_dir + "/%s").encode("ascii") |
1442 | + self.patch(maas_wipe, "DEV_PATH", dev_path) |
1443 | + dev_name = factory.make_name("disk").encode("ascii") |
1444 | + file_path = dev_path % dev_name |
1445 | + self.make_empty_file(file_path, content=b"T") |
1446 | + disk_info = { |
1447 | + b"supported": True, |
1448 | + b"enabled": False, |
1449 | + b"locked": False, |
1450 | + b"frozen": False, |
1451 | + } |
1452 | + |
1453 | # Add a little size to the file making it not evenly |
1454 | # divisable by 1 MiB. |
1455 | extra_end = 512 |
1456 | with open(file_path, "a+b") as fp: |
1457 | fp.write(b"T" * extra_end) |
1458 | |
1459 | - zero_disk(dev_name) |
1460 | + zero_disk(dev_name, disk_info) |
1461 | |
1462 | zero_buf = b"\0" * 1024 * 1024 |
1463 | with open(file_path, "rb") as fp: |
1464 | @@ -682,7 +1098,7 @@ class TestMAASWipe(MAASTestCase): |
1465 | parser.parse_args.return_value = args |
1466 | self.patch(argparse, "ArgumentParser").return_value = parser |
1467 | |
1468 | - def test_main_calls_try_secure_erase_for_all_disks(self): |
1469 | + def test_main_calls_try_secure_erase_for_all_hdd(self): |
1470 | self.patch_args(True, False) |
1471 | disks = { |
1472 | factory.make_name("disk").encode("ascii"): {} for _ in range(3) |
1473 | @@ -698,7 +1114,7 @@ class TestMAASWipe(MAASTestCase): |
1474 | self.assertThat(mock_try, MockCallsMatch(*calls)) |
1475 | self.assertThat(mock_zero, MockNotCalled()) |
1476 | |
1477 | - def test_main_calls_zero_disk_if_no_secure_erase(self): |
1478 | + def test_main_calls_zero_disk_if_no_secure_erase_hdd(self): |
1479 | self.patch_args(True, False) |
1480 | disks = { |
1481 | factory.make_name("disk").encode("ascii"): {} for _ in range(3) |
1482 | @@ -711,11 +1127,10 @@ class TestMAASWipe(MAASTestCase): |
1483 | maas_wipe.main() |
1484 | |
1485 | try_calls = [call(disk, info) for disk, info in disks.items()] |
1486 | - wipe_calls = [call(disk) for disk in disks.keys()] |
1487 | self.assertThat(mock_try, MockCallsMatch(*try_calls)) |
1488 | - self.assertThat(mock_zero, MockCallsMatch(*wipe_calls)) |
1489 | + self.assertThat(mock_zero, MockCallsMatch(*try_calls)) |
1490 | |
1491 | - def test_main_calls_wipe_quickly_if_no_secure_erase(self): |
1492 | + def test_main_calls_wipe_quickly_if_no_secure_erase_hdd(self): |
1493 | self.patch_args(True, True) |
1494 | disks = { |
1495 | factory.make_name("disk").encode("ascii"): {} for _ in range(3) |
1496 | @@ -760,6 +1175,6 @@ class TestMAASWipe(MAASTestCase): |
1497 | mock_try.return_value = False |
1498 | maas_wipe.main() |
1499 | |
1500 | - wipe_calls = [call(disk) for disk in disks.keys()] |
1501 | + wipe_calls = [call(disk, info) for disk, info in disks.items()] |
1502 | self.assertThat(mock_try, MockNotCalled()) |
1503 | self.assertThat(zero_disk, MockCallsMatch(*wipe_calls)) |
1504 | diff --git a/src/metadataserver/user_data/templates/snippets/tests/test_maas_wipe_defs.py b/src/metadataserver/user_data/templates/snippets/tests/test_maas_wipe_defs.py |
1505 | new file mode 100644 |
1506 | index 0000000..f9bb440 |
1507 | --- /dev/null |
1508 | +++ b/src/metadataserver/user_data/templates/snippets/tests/test_maas_wipe_defs.py |
1509 | @@ -0,0 +1,232 @@ |
1510 | +#!/usr/bin/python3 |
1511 | +# Copyright 2020 Canonical Ltd. This software is licensed under the |
1512 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
1513 | +# |
1514 | +# hdparm / nvme-cli outputs used on maas_wipe testing |
1515 | + |
1516 | +HDPARM_BEFORE_SECURITY = b"""\ |
1517 | +/dev/sda: |
1518 | + |
1519 | +ATA device, with non-removable media |
1520 | + Model Number: INTEL SSDSC2CT240A4 |
1521 | + Serial Number: CVKI3206029X240DGN |
1522 | + Firmware Revision: 335u |
1523 | + Transport: Serial, ATA8-AST, SATA 1.0a, SATA II Extensions |
1524 | +Standards: |
1525 | + Used: unknown (minor revision code 0xffff) |
1526 | + Supported: 9 8 7 6 5 |
1527 | + Likely used: 9 |
1528 | +Configuration: |
1529 | + Logical max current |
1530 | + cylinders 16383 16383 |
1531 | + heads 16 16 |
1532 | + sectors/track 63 63 |
1533 | + -- |
1534 | + CHS current addressable sectors: 16514064 |
1535 | + LBA user addressable sectors: 268435455 |
1536 | + LBA48 user addressable sectors: 468862128 |
1537 | + Logical Sector size: 512 bytes |
1538 | + Physical Sector size: 512 bytes |
1539 | + Logical Sector-0 offset: 0 bytes |
1540 | + device size with M = 1024*1024: 228936 MBytes |
1541 | + device size with M = 1000*1000: 240057 MBytes (240 GB) |
1542 | + cache/buffer size = unknown |
1543 | + Nominal Media Rotation Rate: Solid State Device |
1544 | +Capabilities: |
1545 | + LBA, IORDY(can be disabled) |
1546 | + Queue depth: 32 |
1547 | + Standby timer values: spec'd by Standard, no device specific minimum |
1548 | + R/W multiple sector transfer: Max = 16 Current = 16 |
1549 | + Advanced power management level: 254 |
1550 | + DMA: mdma0 mdma1 mdma2 udma0 udma1 udma2 udma3 udma4 udma5 *udma6 |
1551 | + Cycle time: min=120ns recommended=120ns |
1552 | + PIO: pio0 pio1 pio2 pio3 pio4 |
1553 | + Cycle time: no flow control=120ns IORDY flow control=120ns |
1554 | +Commands/features: |
1555 | + Enabled Supported: |
1556 | + * SMART feature set |
1557 | + Security Mode feature set |
1558 | + * Power Management feature set |
1559 | + * Write cache |
1560 | + * Look-ahead |
1561 | + * Host Protected Area feature set |
1562 | + * WRITE_BUFFER command |
1563 | + * READ_BUFFER command |
1564 | + * NOP cmd |
1565 | + * DOWNLOAD_MICROCODE |
1566 | + * Advanced Power Management feature set |
1567 | + Power-Up In Standby feature set |
1568 | + * 48-bit Address feature set |
1569 | + * Mandatory FLUSH_CACHE |
1570 | + * FLUSH_CACHE_EXT |
1571 | + * SMART error logging |
1572 | + * SMART self-test |
1573 | + * General Purpose Logging feature set |
1574 | + * WRITE_{DMA|MULTIPLE}_FUA_EXT |
1575 | + * 64-bit World wide name |
1576 | + * IDLE_IMMEDIATE with UNLOAD |
1577 | + * WRITE_UNCORRECTABLE_EXT command |
1578 | + * {READ,WRITE}_DMA_EXT_GPL commands |
1579 | + * Segmented DOWNLOAD_MICROCODE |
1580 | + * Gen1 signaling speed (1.5Gb/s) |
1581 | + * Gen2 signaling speed (3.0Gb/s) |
1582 | + * Gen3 signaling speed (6.0Gb/s) |
1583 | + * Native Command Queueing (NCQ) |
1584 | + * Host-initiated interface power management |
1585 | + * Phy event counters |
1586 | + * DMA Setup Auto-Activate optimization |
1587 | + Device-initiated interface power management |
1588 | + * Software settings preservation |
1589 | + * SMART Command Transport (SCT) feature set |
1590 | + * SCT Data Tables (AC5) |
1591 | + * reserved 69[4] |
1592 | + * Data Set Management TRIM supported (limit 1 block) |
1593 | + * Deterministic read data after TRIM |
1594 | +""" |
1595 | + |
1596 | +HDPARM_AFTER_SECURITY = b"""\ |
1597 | +Logical Unit WWN Device Identifier: 55cd2e40002643cf |
1598 | + NAA : 5 |
1599 | + IEEE OUI : 5cd2e4 |
1600 | + Unique ID : 0002643cf |
1601 | +Checksum: correct |
1602 | +""" |
1603 | + |
1604 | +HDPARM_SECURITY_NOT_SUPPORTED = b"""\ |
1605 | +Security: |
1606 | + Master password revision code = 65534 |
1607 | + not supported |
1608 | + not enabled |
1609 | + not locked |
1610 | + not frozen |
1611 | + not expired: security count |
1612 | + supported: enhanced erase |
1613 | + 4min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT. |
1614 | +""" |
1615 | + |
1616 | +HDPARM_SECURITY_SUPPORTED_NOT_ENABLED = b"""\ |
1617 | +Security: |
1618 | + Master password revision code = 65534 |
1619 | + supported |
1620 | + not enabled |
1621 | + not locked |
1622 | + not frozen |
1623 | + not expired: security count |
1624 | + supported: enhanced erase |
1625 | + 4min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT. |
1626 | +""" |
1627 | + |
1628 | +HDPARM_SECURITY_SUPPORTED_ENABLED = b"""\ |
1629 | +Security: |
1630 | + Master password revision code = 65534 |
1631 | + supported |
1632 | + enabled |
1633 | + not locked |
1634 | + not frozen |
1635 | + not expired: security count |
1636 | + supported: enhanced erase |
1637 | + 4min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT. |
1638 | +""" |
1639 | + |
1640 | +HDPARM_SECURITY_ALL_TRUE = b"""\ |
1641 | +Security: |
1642 | + Master password revision code = 65534 |
1643 | + supported |
1644 | + enabled |
1645 | + locked |
1646 | + frozen |
1647 | + not expired: security count |
1648 | + supported: enhanced erase |
1649 | + 4min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT. |
1650 | +""" |
1651 | + |
1652 | +NVME_IDCTRL_PROLOGUE = b"""\ |
1653 | +NVME Identify Controller: |
1654 | +vid : 0x8086 |
1655 | +ssvid : 0x8086 |
1656 | +sn : CVMD5066002T400AGN |
1657 | +mn : INTEL SSDPEDME400G4 |
1658 | +fr : 8DV10131 |
1659 | +rab : 0 |
1660 | +ieee : 5cd2e4 |
1661 | +cmic : 0 |
1662 | +mdts : 5 |
1663 | +cntlid : 0 |
1664 | +ver : 0 |
1665 | +rtd3r : 0 |
1666 | +rtd3e : 0 |
1667 | +oaes : 0 |
1668 | +ctratt : 0 |
1669 | +acl : 3 |
1670 | +aerl : 3 |
1671 | +frmw : 0x2 |
1672 | +lpa : 0 |
1673 | +elpe : 63 |
1674 | +npss : 0 |
1675 | +avscc : 0 |
1676 | +apsta : 0 |
1677 | +wctemp : 0 |
1678 | +cctemp : 0 |
1679 | +mtfa : 0 |
1680 | +hmpre : 0 |
1681 | +hmmin : 0 |
1682 | +tnvmcap : 0 |
1683 | +unvmcap : 0 |
1684 | +rpmbs : 0 |
1685 | +edstt : 0 |
1686 | +dsto : 0 |
1687 | +fwug : 0 |
1688 | +kas : 0 |
1689 | +hctma : 0 |
1690 | +mntmt : 0 |
1691 | +mxtmt : 0 |
1692 | +sanicap : 0 |
1693 | +hmminds : 0 |
1694 | +hmmaxd : 0 |
1695 | +sqes : 0x66 |
1696 | +cqes : 0x44 |
1697 | +maxcmd : 0 |
1698 | +nn : 1 |
1699 | +fuses : 0 |
1700 | +""" |
1701 | + |
1702 | +NVME_IDCTRL_OACS_FORMAT_SUPPORTED = b"""\ |
1703 | +oacs : 0x6 |
1704 | +""" |
1705 | + |
1706 | +NVME_IDCTRL_OACS_FORMAT_UNSUPPORTED = b"""\ |
1707 | +oacs : 0x4 |
1708 | +""" |
1709 | + |
1710 | +NVME_IDCTRL_ONCS_WRITEZ_SUPPORTED = b"""\ |
1711 | +oncs : 0xe |
1712 | +""" |
1713 | + |
1714 | +NVME_IDCTRL_ONCS_WRITEZ_UNSUPPORTED = b"""\ |
1715 | +oncs : 0x6 |
1716 | +""" |
1717 | + |
1718 | +NVME_IDCTRL_FNA_CRYPTFORMAT_SUPPORTED = b"""\ |
1719 | +fna : 0x7 |
1720 | +""" |
1721 | + |
1722 | +NVME_IDCTRL_FNA_CRYPTFORMAT_UNSUPPORTED = b"""\ |
1723 | +fna : 0x3 |
1724 | +""" |
1725 | + |
1726 | +NVME_IDCTRL_EPILOGUE = b"""\ |
1727 | +vwc : 0 |
1728 | +awun : 0 |
1729 | +awupf : 0 |
1730 | +nvscc : 0 |
1731 | +acwu : 0 |
1732 | +sgls : 0 |
1733 | +subnqn : |
1734 | +ioccsz : 0 |
1735 | +iorcsz : 0 |
1736 | +icdoff : 0 |
1737 | +ctrattr : 0 |
1738 | +msdbd : 0 |
1739 | +ps 0 : mp:25.00W operational enlat:0 exlat:0 rrt:0 rrl:0 |
1740 | + rwt:0 rwl:0 idle_power:- active_power:- |
1741 | +""" |
Reviewed in https:/ /code.launchpad .net/~gpiccoli/ maas/+git/ maas/+merge/ 387316