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