Merge ~ltrager/maas:lp1835954_2.6 into maas:2.6

Proposed by Lee Trager
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)
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

To post a comment you must log in.
Revision history for this message
Lee Trager (ltrager) wrote :
review: Approve
Revision history for this message
MAAS Lander (maas-lander) wrote :
Revision history for this message
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://maas-ci.internal:8080/job/maas/job/branch-tester/8002/console
COMMIT: c48832ad281ae72d06db92d3197c3cd0d3e901fd

review: Needs Fixing
Revision history for this message
MAAS Lander (maas-lander) wrote :
Revision history for this message
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://maas-ci.internal:8080/job/maas/job/branch-tester/8004/console
COMMIT: ad390606298978cb1f13c2285f7b13d9713e2907

review: Needs Fixing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/maasserver/compose_preseed.py b/src/maasserver/compose_preseed.py
index 8874639..f1707fb 100644
--- a/src/maasserver/compose_preseed.py
+++ b/src/maasserver/compose_preseed.py
@@ -392,6 +392,9 @@ def get_base_preseed(node=None):
392 # jq is used during enlistment to read the JSON string containing392 # jq is used during enlistment to read the JSON string containing
393 # the system_id of the newly created machine.393 # the system_id of the newly created machine.
394 cloud_config['packages'] += ['jq']394 cloud_config['packages'] += ['jq']
395 # On disk erasing, we need nvme-cli
396 if node is not None and node.status == NODE_STATUS.DISK_ERASING:
397 cloud_config["packages"] += ["nvme-cli"]
395398
396 return cloud_config399 return cloud_config
397400
diff --git a/src/metadataserver/user_data/templates/snippets/maas_wipe.py b/src/metadataserver/user_data/templates/snippets/maas_wipe.py
398old mode 100644401old mode 100644
399new mode 100755402new mode 100755
index 9dfe96e..5e3a8f5
--- a/src/metadataserver/user_data/templates/snippets/maas_wipe.py
+++ b/src/metadataserver/user_data/templates/snippets/maas_wipe.py
@@ -30,12 +30,95 @@ def list_disks():
30 return disks30 return disks
3131
3232
33def get_disk_security_info(disk):33def get_nvme_security_info(disk):
34 """Get the disk security information.34 """Gather NVMe information from the NVMe disks using the
35 nvme-cli tool. Info from id-ctrl and id-ns is needed for
36 secure erase (nvme format) and write zeroes."""
37
38 # Grab the relevant info from nvme id-ctrl. We need to check the
39 # following bits:
40 #
41 # OACS (Optional Admin Command Support) bit 1: Format supported
42 # ONCS (Optional NVM Command Support) bit 3: Write Zeroes supported
43 # FNA (Format NVM Attributes) bit 2: Cryptographic format supported
44
45 security_info = {
46 "format_supported": False,
47 "writez_supported": False,
48 "crypto_format": False,
49 "nsze": 0,
50 "lbaf": 0,
51 "ms": 0,
52 }
53
54 try:
55 output = subprocess.check_output(["nvme", "id-ctrl", DEV_PATH % disk])
56 except subprocess.CalledProcessError as exc:
57 print_flush("Error on nvme id-ctrl (%s)" % exc.returncode)
58 return security_info
59 except OSError as exc:
60 print_flush("OS error when running nvme-cli (%s)" % exc.strerror)
61 return security_info
62
63 output = output.decode()
64
65 for line in output.split("\n"):
66 if "oacs" in line:
67 oacs = line.split(":")[1]
68 if int(oacs, 16) & 0x2:
69 security_info["format_supported"] = True
70
71 if "oncs" in line:
72 oncs = line.split(":")[1]
73 if int(oncs, 16) & 0x8:
74 security_info["writez_supported"] = True
75
76 if "fna" in line:
77 fna = line.split(":")[1]
78 if int(fna, 16) & 0x4:
79 security_info["crypto_format"] = True
80
81 # Next step: collect LBAF (LBA Format), MS (Metadata Setting) and
82 # NSZE (Namespace Size) from id-ns. According to NVMe spec, bits 0:3
83 # from FLBAS corresponds to the LBAF value, whereas bit 4 is MS.
3584
36 Uses `hdparam` to get security information about the disk. Sadly hdparam85 try:
37 doesn't provide an output that makes it easy to parse.86 output = subprocess.check_output(["nvme", "id-ns", DEV_PATH % disk])
87 except subprocess.CalledProcessError as exc:
88 print_flush("Error on nvme id-ns (%s)" % exc.returncode)
89 security_info["format_supported"] = False
90 security_info["writez_supported"] = False
91 return security_info
92 except OSError as exc:
93 print_flush("OS error when running nvme-cli (%s)" % exc.strerror)
94 security_info["format_supported"] = False
95 security_info["writez_supported"] = False
96 return security_info
97
98 output = output.decode()
99
100 for line in output.split("\n"):
101 if "nsze" in line:
102 # According to spec., this should be used as 0-based value.
103 nsze = line.split(":")[1]
104 security_info["nsze"] = int(nsze, 16) - 1
105
106 if "flbas" in line:
107 flbas = line.split(":")[1]
108 flbas = int(flbas, 16)
109 security_info["lbaf"] = flbas & 0xF
110
111 if flbas & 0x10:
112 security_info["ms"] = 1
113
114 return security_info
115
116
117def get_hdparm_security_info(disk):
118 """Get SCSI/ATA disk security info from hdparm.
119 Sadly hdparam doesn't provide an output that makes it easy to parse.
38 """120 """
121
39 # Grab the security section for hdparam.122 # Grab the security section for hdparam.
40 security_section = []123 security_section = []
41 output = subprocess.check_output(124 output = subprocess.check_output(
@@ -60,6 +143,19 @@ def get_disk_security_info(disk):
60 return security_info143 return security_info
61144
62145
146def get_disk_security_info(disk):
147 """Get the disk security information.
148
149 Uses `hdparam` to get security information about the SCSI/ATA disks.
150 If NVMe, nvme-cli is used instead.
151 """
152
153 if b"nvme" in disk:
154 return get_nvme_security_info(disk)
155
156 return get_hdparm_security_info(disk)
157
158
63def get_disk_info():159def get_disk_info():
64 """Return dictionary of wipeable disks and thier security information."""160 """Return dictionary of wipeable disks and thier security information."""
65 return {161 return {
@@ -68,46 +164,7 @@ def get_disk_info():
68 }164 }
69165
70166
71def try_secure_erase(kname, info):167def secure_erase_hdparm(kname):
72 """Try to wipe the disk with secure erase."""
73 if info[b"supported"]:
74 if info[b"frozen"]:
75 print_flush(
76 "%s: not using secure erase; "
77 "drive is currently frozen." % kname.decode('ascii'))
78 return False
79 elif info[b"locked"]:
80 print_flush(
81 "%s: not using secure erase; "
82 "drive is currently locked." % kname.decode('ascii'))
83 return False
84 elif info[b"enabled"]:
85 print_flush(
86 "%s: not using secure erase; "
87 "drive security is already enabled." % kname.decode('ascii'))
88 return False
89 else:
90 # Wiping using secure erase.
91 try:
92 secure_erase(kname)
93 except Exception as e:
94 print_flush(
95 "%s: failed to be securely erased: %s" % (
96 kname.decode('ascii'), e))
97 return False
98 else:
99 print_flush(
100 "%s: successfully securely erased." % (
101 kname.decode('ascii')))
102 return True
103 else:
104 print_flush(
105 "%s: drive does not support secure erase." % (
106 kname.decode('ascii')))
107 return False
108
109
110def secure_erase(kname):
111 """Securely wipe the device."""168 """Securely wipe the device."""
112 # First write 1 MiB of known data to the beginning of the block device.169 # First write 1 MiB of known data to the beginning of the block device.
113 # This is used to check at the end of the secure erase that it worked170 # This is used to check at the end of the secure erase that it worked
@@ -131,7 +188,7 @@ def secure_erase(kname):
131188
132 # Now that the user password is set the device should have its189 # Now that the user password is set the device should have its
133 # security mode enabled.190 # security mode enabled.
134 info = get_disk_security_info(kname)191 info = get_hdparm_security_info(kname)
135 if not info[b"enabled"]:192 if not info[b"enabled"]:
136 # If not enabled that means the password did not take, so it does not193 # If not enabled that means the password did not take, so it does not
137 # need to be cleared.194 # need to be cleared.
@@ -151,7 +208,7 @@ def secure_erase(kname):
151 failed_exc = exc208 failed_exc = exc
152209
153 # Make sure that the device is now not enabled.210 # Make sure that the device is now not enabled.
154 info = get_disk_security_info(kname)211 info = get_hdparm_security_info(kname)
155 if info[b"enabled"]:212 if info[b"enabled"]:
156 # Wipe failed since security is still enabled.213 # Wipe failed since security is still enabled.
157 subprocess.check_output([214 subprocess.check_output([
@@ -166,23 +223,195 @@ def secure_erase(kname):
166 "Secure erase was performed, but failed to actually work.")223 "Secure erase was performed, but failed to actually work.")
167224
168225
169def wipe_quickly(kname):226def try_secure_erase_hdparm(kname, info):
170 """Quickly wipe the disk by zeroing the beginning and end of the disk.227 """Try to wipe the disk with secure erase."""
228 if info[b"supported"]:
229 if info[b"frozen"]:
230 print_flush(
231 "%s: not using secure erase; "
232 "drive is currently frozen." % kname.decode("ascii")
233 )
234 return False
235 elif info[b"locked"]:
236 print_flush(
237 "%s: not using secure erase; "
238 "drive is currently locked." % kname.decode("ascii")
239 )
240 return False
241 elif info[b"enabled"]:
242 print_flush(
243 "%s: not using secure erase; "
244 "drive security is already enabled." % kname.decode("ascii")
245 )
246 return False
247 else:
248 # Wiping using secure erase.
249 try:
250 secure_erase_hdparm(kname)
251 except Exception as e:
252 print_flush(
253 "%s: failed to be securely erased: %s"
254 % (kname.decode("ascii"), e)
255 )
256 return False
257 else:
258 print_flush(
259 "%s: successfully securely erased."
260 % (kname.decode("ascii"))
261 )
262 return True
263 else:
264 print_flush(
265 "%s: drive does not support secure erase."
266 % (kname.decode("ascii"))
267 )
268 return False
269
270
271def try_secure_erase_nvme(kname, info):
272 """Perform a secure-erase on NVMe disk if that feature is
273 available. Prefer cryptographic erase, when available."""
274
275 if not info["format_supported"]:
276 print_flush(
277 "Device %s does not support formatting" % kname.decode("ascii")
278 )
279 return False
280
281 if info["crypto_format"]:
282 ses = 2
283 else:
284 ses = 1
285
286 try:
287 subprocess.check_output(
288 [
289 "nvme",
290 "format",
291 "-s",
292 str(ses),
293 "-l",
294 str(info["lbaf"]),
295 "-m",
296 str(info["ms"]),
297 DEV_PATH % kname,
298 ]
299 )
300 except subprocess.CalledProcessError as exc:
301 print_flush("Error with format command (%s)" % exc.returncode)
302 return False
303 except OSError as exc:
304 print_flush("OS error when running nvme-cli (%s)" % exc.strerror)
305 return False
306
307 print_flush(
308 "Secure erase was successful on NVMe drive %s" % kname.decode("ascii")
309 )
310 return True
171311
172 This is not a secure erase but does make it harder to get the data from312
173 the device.313def try_secure_erase(kname, info):
314 """Entry-point for secure-erase for SCSI/ATA or NVMe disks."""
315
316 if b"nvme" in kname:
317 return try_secure_erase_nvme(kname, info)
318
319 return try_secure_erase_hdparm(kname, info)
320
321
322def wipe_quickly(kname):
323 """Quickly wipe the disk by using wipefs and zeroing the beginning
324 and end of the disk. This is not a secure erase but does make it
325 harder to get the data from the device and also clears previous layouts.
174 """326 """
175 print_flush("%s: starting quick wipe." % kname.decode('ascii'))327 wipe_error = 0
176 buf = b'\0' * 1024 * 1024 * 2 # 2 MiB328 print_flush("%s: starting quick wipe." % kname.decode("ascii"))
177 with open(DEV_PATH % kname, "wb") as fp:329 try:
330 subprocess.check_output(["wipefs", "-f", "-a", DEV_PATH % kname])
331 wipe_error -= 1
332 except subprocess.CalledProcessError as exc:
333 print_flush(
334 "%s: wipefs failed (%s)" % (kname.decode("ascii"), exc.returncode)
335 )
336 wipe_error += 1
337
338 buf = b"\0" * 1024 * 1024 * 2 # 2 MiB
339 try:
340 fp = open(DEV_PATH % kname, "wb")
178 fp.write(buf)341 fp.write(buf)
179 fp.seek(-len(buf), 2)342 fp.seek(-len(buf), 2)
180 fp.write(buf)343 fp.write(buf)
181 print_flush("%s: successfully quickly wiped." % kname.decode('ascii'))344 wipe_error -= 1
345 except OSError as exc:
346 print_flush(
347 "%s: OS error while wiping beginning/end of disk (%s)"
348 % (kname.decode("ascii"), exc.strerror)
349 )
350 wipe_error += 1
351
352 if wipe_error > 0:
353 print_flush("%s: failed to be quickly wiped." % kname.decode("ascii"))
354 else:
355 print_flush("%s: successfully quickly wiped." % kname.decode("ascii"))
356
357
358def nvme_write_zeroes(kname, info):
359 """Perform a write-zeroes operation on NVMe device instead of
360 dd'ing 0 to the entire disk if secure erase is not available.
361 Write-zeroes is a faster way to clean a NVMe disk."""
362
363 fallback = False
364
365 if not info["writez_supported"]:
366 print(
367 "NVMe drive %s does not support write-zeroes"
368 % kname.decode("ascii")
369 )
370 fallback = True
371
372 if info["nsze"] <= 0:
373 print(
374 "Bad namespace information collected on NVMe drive %s"
375 % kname.decode("ascii")
376 )
377 fallback = True
378
379 if fallback:
380 print_flush("Will fallback to regular drive zeroing.")
381 return False
382
383 try:
384 subprocess.check_output(
385 [
386 "nvme",
387 "write-zeroes",
388 "-f",
389 "-s",
390 "0",
391 "-c",
392 str(hex(info["nsze"])[2:]),
393 DEV_PATH % kname,
394 ]
395 )
396 except subprocess.CalledProcessError as exc:
397 print_flush("Error with write-zeroes command (%s)" % exc.returncode)
398 return False
399 except OSError as exc:
400 print_flush("OS error when running nvme-cli (%s)" % exc.strerror)
401 return False
402
403 print_flush(
404 "%s: successfully zeroed (using write-zeroes)." % kname.decode("ascii")
405 )
406 return True
407
182408
409def zero_disk(kname, info):
410 """Zero the entire disk, trying write-zeroes first if NVMe disk."""
411 if b"nvme" in kname:
412 if nvme_write_zeroes(kname, info):
413 return
183414
184def zero_disk(kname):
185 """Zero the entire disk."""
186 # Get the total size of the device.415 # Get the total size of the device.
187 size = 0416 size = 0
188 with open(DEV_PATH % kname, "rb") as fp:417 with open(DEV_PATH % kname, "rb") as fp:
@@ -238,9 +467,12 @@ def main():
238 parser.add_argument(467 parser.add_argument(
239 "--quick-erase", action="store_true", default=False,468 "--quick-erase", action="store_true", default=False,
240 help=(469 help=(
241 "Wipe 1MiB at the start and at the end of the drive to make data "470 "Wipe 2MiB at the start and at the end of the drive to make data "
242 "recovery inconvenient and unlikely to happen by accident. This "471 "recovery inconvenient and unlikely to happen by accident. Also, "
243 "is not secure."))472 "it runs wipefs to clear known partition/layout signatures. This "
473 "is not secure."
474 ),
475 )
244 args = parser.parse_args()476 args = parser.parse_args()
245477
246 # Gather disk information.478 # Gather disk information.
@@ -257,7 +489,7 @@ def main():
257 if args.quick_erase:489 if args.quick_erase:
258 wipe_quickly(kname)490 wipe_quickly(kname)
259 else:491 else:
260 zero_disk(kname)492 zero_disk(kname, info)
261493
262 print_flush("All disks have been successfully wiped.")494 print_flush("All disks have been successfully wiped.")
263495
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
index b05c88a..f1ffb0d 100644
--- a/src/metadataserver/user_data/templates/snippets/tests/test_maas_wipe.py
+++ b/src/metadataserver/user_data/templates/snippets/tests/test_maas_wipe.py
@@ -6,6 +6,7 @@
6__all__ = []6__all__ = []
77
8import argparse8import argparse
9import builtins
9import subprocess10import subprocess
10from textwrap import dedent11from textwrap import dedent
11from unittest.mock import (12from unittest.mock import (
@@ -25,149 +26,29 @@ from snippets.maas_wipe import (
25 get_disk_info,26 get_disk_info,
26 get_disk_security_info,27 get_disk_security_info,
27 list_disks,28 list_disks,
28 secure_erase,29 nvme_write_zeroes,
30 secure_erase_hdparm,
29 try_secure_erase,31 try_secure_erase,
30 wipe_quickly,32 wipe_quickly,
31 WipeError,33 WipeError,
32 zero_disk,34 zero_disk,
33)35)
3436from snippets.tests.test_maas_wipe_defs import (
3537 HDPARM_AFTER_SECURITY,
36HDPARM_BEFORE_SECURITY = b"""\38 HDPARM_BEFORE_SECURITY,
37/dev/sda:39 HDPARM_SECURITY_ALL_TRUE,
3840 HDPARM_SECURITY_NOT_SUPPORTED,
39ATA device, with non-removable media41 HDPARM_SECURITY_SUPPORTED_ENABLED,
40 Model Number: INTEL SSDSC2CT240A442 HDPARM_SECURITY_SUPPORTED_NOT_ENABLED,
41 Serial Number: CVKI3206029X240DGN43 NVME_IDCTRL_EPILOGUE,
42 Firmware Revision: 335u44 NVME_IDCTRL_FNA_CRYPTFORMAT_SUPPORTED,
43 Transport: Serial, ATA8-AST, SATA 1.0a, SATA II Extensions45 NVME_IDCTRL_FNA_CRYPTFORMAT_UNSUPPORTED,
44Standards:46 NVME_IDCTRL_OACS_FORMAT_SUPPORTED,
45 Used: unknown (minor revision code 0xffff)47 NVME_IDCTRL_OACS_FORMAT_UNSUPPORTED,
46 Supported: 9 8 7 6 548 NVME_IDCTRL_ONCS_WRITEZ_SUPPORTED,
47 Likely used: 949 NVME_IDCTRL_ONCS_WRITEZ_UNSUPPORTED,
48Configuration:50 NVME_IDCTRL_PROLOGUE,
49 Logical max current51)
50 cylinders 16383 16383
51 heads 16 16
52 sectors/track 63 63
53 --
54 CHS current addressable sectors: 16514064
55 LBA user addressable sectors: 268435455
56 LBA48 user addressable sectors: 468862128
57 Logical Sector size: 512 bytes
58 Physical Sector size: 512 bytes
59 Logical Sector-0 offset: 0 bytes
60 device size with M = 1024*1024: 228936 MBytes
61 device size with M = 1000*1000: 240057 MBytes (240 GB)
62 cache/buffer size = unknown
63 Nominal Media Rotation Rate: Solid State Device
64Capabilities:
65 LBA, IORDY(can be disabled)
66 Queue depth: 32
67 Standby timer values: spec'd by Standard, no device specific minimum
68 R/W multiple sector transfer: Max = 16 Current = 16
69 Advanced power management level: 254
70 DMA: mdma0 mdma1 mdma2 udma0 udma1 udma2 udma3 udma4 udma5 *udma6
71 Cycle time: min=120ns recommended=120ns
72 PIO: pio0 pio1 pio2 pio3 pio4
73 Cycle time: no flow control=120ns IORDY flow control=120ns
74Commands/features:
75 Enabled Supported:
76 * SMART feature set
77 Security Mode feature set
78 * Power Management feature set
79 * Write cache
80 * Look-ahead
81 * Host Protected Area feature set
82 * WRITE_BUFFER command
83 * READ_BUFFER command
84 * NOP cmd
85 * DOWNLOAD_MICROCODE
86 * Advanced Power Management feature set
87 Power-Up In Standby feature set
88 * 48-bit Address feature set
89 * Mandatory FLUSH_CACHE
90 * FLUSH_CACHE_EXT
91 * SMART error logging
92 * SMART self-test
93 * General Purpose Logging feature set
94 * WRITE_{DMA|MULTIPLE}_FUA_EXT
95 * 64-bit World wide name
96 * IDLE_IMMEDIATE with UNLOAD
97 * WRITE_UNCORRECTABLE_EXT command
98 * {READ,WRITE}_DMA_EXT_GPL commands
99 * Segmented DOWNLOAD_MICROCODE
100 * Gen1 signaling speed (1.5Gb/s)
101 * Gen2 signaling speed (3.0Gb/s)
102 * Gen3 signaling speed (6.0Gb/s)
103 * Native Command Queueing (NCQ)
104 * Host-initiated interface power management
105 * Phy event counters
106 * DMA Setup Auto-Activate optimization
107 Device-initiated interface power management
108 * Software settings preservation
109 * SMART Command Transport (SCT) feature set
110 * SCT Data Tables (AC5)
111 * reserved 69[4]
112 * Data Set Management TRIM supported (limit 1 block)
113 * Deterministic read data after TRIM
114"""
115
116HDPARM_AFTER_SECURITY = b"""\
117Logical Unit WWN Device Identifier: 55cd2e40002643cf
118 NAA : 5
119 IEEE OUI : 5cd2e4
120 Unique ID : 0002643cf
121Checksum: correct
122"""
123
124HDPARM_SECURITY_NOT_SUPPORTED = b"""\
125Security:
126 Master password revision code = 65534
127 not supported
128 not enabled
129 not locked
130 not frozen
131 not expired: security count
132 supported: enhanced erase
133 4min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT.
134"""
135
136HDPARM_SECURITY_SUPPORTED_NOT_ENABLED = b"""\
137Security:
138 Master password revision code = 65534
139 supported
140 not enabled
141 not locked
142 not frozen
143 not expired: security count
144 supported: enhanced erase
145 4min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT.
146"""
147
148HDPARM_SECURITY_SUPPORTED_ENABLED = b"""\
149Security:
150 Master password revision code = 65534
151 supported
152 enabled
153 not locked
154 not frozen
155 not expired: security count
156 supported: enhanced erase
157 4min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT.
158"""
159
160HDPARM_SECURITY_ALL_TRUE = b"""\
161Security:
162 Master password revision code = 65534
163 supported
164 enabled
165 locked
166 frozen
167 not expired: security count
168 supported: enhanced erase
169 4min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT.
170"""
17152
17253
173class TestMAASWipe(MAASTestCase):54class TestMAASWipe(MAASTestCase):
@@ -194,15 +75,19 @@ class TestMAASWipe(MAASTestCase):
19475
195 def test_list_disks_returns_only_readwrite_disks(self):76 def test_list_disks_returns_only_readwrite_disks(self):
196 mock_check_output = self.patch(subprocess, "check_output")77 mock_check_output = self.patch(subprocess, "check_output")
197 mock_check_output.return_value = dedent("""\78 mock_check_output.return_value = dedent(
79 """\
198 sda disk 080 sda disk 0
199 sdb disk 181 sdb disk 1
200 sr0 rom 082 sr0 rom 0
201 sr1 rom 083 sr1 rom 0
202 """).encode('ascii')84 nvme0n1 disk 0
203 self.assertEqual([b'sda'], list_disks())85 nvme1n1 disk 1
86 """
87 ).encode("ascii")
88 self.assertEqual([b"sda", b"nvme0n1"], list_disks())
20489
205 def test_get_disk_security_info_missing(self):90 def test_get_disk_security_info_missing_hdparm(self):
206 hdparm_output = HDPARM_BEFORE_SECURITY + HDPARM_AFTER_SECURITY91 hdparm_output = HDPARM_BEFORE_SECURITY + HDPARM_AFTER_SECURITY
207 mock_check_output = self.patch(subprocess, "check_output")92 mock_check_output = self.patch(subprocess, "check_output")
208 mock_check_output.return_value = hdparm_output93 mock_check_output.return_value = hdparm_output
@@ -218,7 +103,7 @@ class TestMAASWipe(MAASTestCase):
218 b"frozen": False,103 b"frozen": False,
219 }, observered)104 }, observered)
220105
221 def test_get_disk_security_info_not_supported(self):106 def test_get_disk_security_info_not_supported_hdparm(self):
222 hdparm_output = (107 hdparm_output = (
223 HDPARM_BEFORE_SECURITY + HDPARM_SECURITY_NOT_SUPPORTED +108 HDPARM_BEFORE_SECURITY + HDPARM_SECURITY_NOT_SUPPORTED +
224 HDPARM_AFTER_SECURITY)109 HDPARM_AFTER_SECURITY)
@@ -236,7 +121,7 @@ class TestMAASWipe(MAASTestCase):
236 b"frozen": False,121 b"frozen": False,
237 }, observered)122 }, observered)
238123
239 def test_get_disk_security_info_supported_not_enabled(self):124 def test_get_disk_security_info_supported_not_enabled_hdparm(self):
240 hdparm_output = (125 hdparm_output = (
241 HDPARM_BEFORE_SECURITY + HDPARM_SECURITY_SUPPORTED_NOT_ENABLED +126 HDPARM_BEFORE_SECURITY + HDPARM_SECURITY_SUPPORTED_NOT_ENABLED +
242 HDPARM_AFTER_SECURITY)127 HDPARM_AFTER_SECURITY)
@@ -254,7 +139,7 @@ class TestMAASWipe(MAASTestCase):
254 b"frozen": False,139 b"frozen": False,
255 }, observered)140 }, observered)
256141
257 def test_get_disk_security_info_supported_enabled(self):142 def test_get_disk_security_info_supported_enabled_hdparm(self):
258 hdparm_output = (143 hdparm_output = (
259 HDPARM_BEFORE_SECURITY + HDPARM_SECURITY_SUPPORTED_ENABLED +144 HDPARM_BEFORE_SECURITY + HDPARM_SECURITY_SUPPORTED_ENABLED +
260 HDPARM_AFTER_SECURITY)145 HDPARM_AFTER_SECURITY)
@@ -272,7 +157,7 @@ class TestMAASWipe(MAASTestCase):
272 b"frozen": False,157 b"frozen": False,
273 }, observered)158 }, observered)
274159
275 def test_get_disk_security_info_all_true(self):160 def test_get_disk_security_info_all_true_hdparm(self):
276 hdparm_output = (161 hdparm_output = (
277 HDPARM_BEFORE_SECURITY + HDPARM_SECURITY_ALL_TRUE +162 HDPARM_BEFORE_SECURITY + HDPARM_SECURITY_ALL_TRUE +
278 HDPARM_AFTER_SECURITY)163 HDPARM_AFTER_SECURITY)
@@ -290,7 +175,7 @@ class TestMAASWipe(MAASTestCase):
290 b"frozen": True,175 b"frozen": True,
291 }, observered)176 }, observered)
292177
293 def test_get_disk_info(self):178 def test_get_disk_info_hdparm(self):
294 disk_names = [179 disk_names = [
295 factory.make_name("disk").encode("ascii")180 factory.make_name("disk").encode("ascii")
296 for _ in range(3)181 for _ in range(3)
@@ -306,15 +191,244 @@ class TestMAASWipe(MAASTestCase):
306 for _ in range(3)191 for _ in range(3)
307 ]192 ]
308 self.patch(193 self.patch(
309 maas_wipe,194 maas_wipe, "get_hdparm_security_info"
310 "get_disk_security_info").side_effect = security_info195 ).side_effect = security_info
196 observed = get_disk_info()
197 self.assertEqual(
198 {disk_names[i]: security_info[i] for i in range(3)}, observed
199 )
200
201 def test_get_disk_security_info_crypt_format_writez_nvme(self):
202 nvme_cli_output = (
203 NVME_IDCTRL_PROLOGUE +
204 NVME_IDCTRL_OACS_FORMAT_SUPPORTED +
205 NVME_IDCTRL_ONCS_WRITEZ_SUPPORTED +
206 NVME_IDCTRL_FNA_CRYPTFORMAT_SUPPORTED +
207 NVME_IDCTRL_EPILOGUE
208 )
209 mock_check_output = self.patch(subprocess, "check_output")
210 mock_check_output.return_value = nvme_cli_output
211 disk_name = factory.make_name("nvme").encode("ascii")
212 observered = get_disk_security_info(disk_name)
213 self.assertThat(
214 mock_check_output,
215 MockCallsMatch(
216 call(["nvme", "id-ctrl", maas_wipe.DEV_PATH % disk_name]),
217 call(["nvme", "id-ns", maas_wipe.DEV_PATH % disk_name]),
218 ),
219 )
220 self.assertEqual(
221 {
222 "format_supported": True,
223 "writez_supported": True,
224 "crypto_format": True,
225 "nsze": 0,
226 "lbaf": 0,
227 "ms": 0,
228 },
229 observered,
230 )
231
232 def test_get_disk_security_info_nocrypt_format_writez_nvme(self):
233 nvme_cli_output = (
234 NVME_IDCTRL_PROLOGUE +
235 NVME_IDCTRL_OACS_FORMAT_SUPPORTED +
236 NVME_IDCTRL_ONCS_WRITEZ_SUPPORTED +
237 NVME_IDCTRL_FNA_CRYPTFORMAT_UNSUPPORTED +
238 NVME_IDCTRL_EPILOGUE
239 )
240 mock_check_output = self.patch(subprocess, "check_output")
241 mock_check_output.return_value = nvme_cli_output
242 disk_name = factory.make_name("nvme").encode("ascii")
243 observered = get_disk_security_info(disk_name)
244 self.assertThat(
245 mock_check_output,
246 MockCallsMatch(
247 call(["nvme", "id-ctrl", maas_wipe.DEV_PATH % disk_name]),
248 call(["nvme", "id-ns", maas_wipe.DEV_PATH % disk_name]),
249 ),
250 )
251 self.assertEqual(
252 {
253 "format_supported": True,
254 "writez_supported": True,
255 "crypto_format": False,
256 "nsze": 0,
257 "lbaf": 0,
258 "ms": 0,
259 },
260 observered,
261 )
262
263 def test_get_disk_security_info_crypt_format_nowritez_nvme(self):
264 nvme_cli_output = (
265 NVME_IDCTRL_PROLOGUE +
266 NVME_IDCTRL_OACS_FORMAT_SUPPORTED +
267 NVME_IDCTRL_ONCS_WRITEZ_UNSUPPORTED +
268 NVME_IDCTRL_FNA_CRYPTFORMAT_SUPPORTED +
269 NVME_IDCTRL_EPILOGUE
270 )
271 mock_check_output = self.patch(subprocess, "check_output")
272 mock_check_output.return_value = nvme_cli_output
273 disk_name = factory.make_name("nvme").encode("ascii")
274 observered = get_disk_security_info(disk_name)
275 self.assertThat(
276 mock_check_output,
277 MockCallsMatch(
278 call(["nvme", "id-ctrl", maas_wipe.DEV_PATH % disk_name]),
279 call(["nvme", "id-ns", maas_wipe.DEV_PATH % disk_name]),
280 ),
281 )
282 self.assertEqual(
283 {
284 "format_supported": True,
285 "writez_supported": False,
286 "crypto_format": True,
287 "nsze": 0,
288 "lbaf": 0,
289 "ms": 0,
290 },
291 observered,
292 )
293
294 def test_get_disk_security_info_noformat_writez_nvme(self):
295 nvme_cli_output = (
296 NVME_IDCTRL_PROLOGUE +
297 NVME_IDCTRL_OACS_FORMAT_UNSUPPORTED +
298 NVME_IDCTRL_ONCS_WRITEZ_SUPPORTED +
299 NVME_IDCTRL_FNA_CRYPTFORMAT_SUPPORTED +
300 NVME_IDCTRL_EPILOGUE
301 )
302 mock_check_output = self.patch(subprocess, "check_output")
303 mock_check_output.return_value = nvme_cli_output
304 disk_name = factory.make_name("nvme").encode("ascii")
305 observered = get_disk_security_info(disk_name)
306 self.assertThat(
307 mock_check_output,
308 MockCallsMatch(
309 call(["nvme", "id-ctrl", maas_wipe.DEV_PATH % disk_name]),
310 call(["nvme", "id-ns", maas_wipe.DEV_PATH % disk_name]),
311 ),
312 )
313 self.assertEqual(
314 {
315 "format_supported": False,
316 "writez_supported": True,
317 "crypto_format": True,
318 "nsze": 0,
319 "lbaf": 0,
320 "ms": 0,
321 },
322 observered,
323 )
324
325 def test_get_disk_security_info_noformat_nowritez_nvme(self):
326 nvme_cli_output = (
327 NVME_IDCTRL_PROLOGUE +
328 NVME_IDCTRL_OACS_FORMAT_UNSUPPORTED +
329 NVME_IDCTRL_ONCS_WRITEZ_UNSUPPORTED +
330 NVME_IDCTRL_FNA_CRYPTFORMAT_UNSUPPORTED +
331 NVME_IDCTRL_EPILOGUE
332 )
333 mock_check_output = self.patch(subprocess, "check_output")
334 mock_check_output.return_value = nvme_cli_output
335 disk_name = factory.make_name("nvme").encode("ascii")
336 observered = get_disk_security_info(disk_name)
337 self.assertThat(
338 mock_check_output,
339 MockCallsMatch(
340 call(["nvme", "id-ctrl", maas_wipe.DEV_PATH % disk_name]),
341 call(["nvme", "id-ns", maas_wipe.DEV_PATH % disk_name]),
342 ),
343 )
344 self.assertEqual(
345 {
346 "format_supported": False,
347 "writez_supported": False,
348 "crypto_format": False,
349 "nsze": 0,
350 "lbaf": 0,
351 "ms": 0,
352 },
353 observered,
354 )
355
356 def test_get_disk_security_info_failed_cmd_nvme(self):
357 mock_check_output = self.patch(subprocess, "check_output")
358 mock_check_output.side_effect = subprocess.CalledProcessError(
359 1, "nvme id-ctrl"
360 )
361 disk_name = factory.make_name("nvme").encode("ascii")
362 observered = get_disk_security_info(disk_name)
363
364 self.assertThat(
365 self.print_flush,
366 MockCalledOnceWith("Error on nvme id-ctrl (%s)" % "1"),
367 )
368 self.assertEqual(
369 {
370 "format_supported": False,
371 "writez_supported": False,
372 "crypto_format": False,
373 "nsze": 0,
374 "lbaf": 0,
375 "ms": 0,
376 },
377 observered,
378 )
379
380 def test_get_disk_security_info_failed_os_nvme(self):
381 mock_check_output = self.patch(subprocess, "check_output")
382 mock_check_output.side_effect = OSError(
383 -2, "No such file or directory"
384 )
385 disk_name = factory.make_name("nvme").encode("ascii")
386 observered = get_disk_security_info(disk_name)
387
388 self.assertThat(
389 self.print_flush,
390 MockCalledOnceWith(
391 "OS error when running nvme-cli (No such file or directory)"
392 ),
393 )
394 self.assertEqual(
395 {
396 "format_supported": False,
397 "writez_supported": False,
398 "crypto_format": False,
399 "nsze": 0,
400 "lbaf": 0,
401 "ms": 0,
402 },
403 observered,
404 )
405
406 def test_get_disk_info_nvme(self):
407 disk_names = [
408 factory.make_name("nvme").encode("ascii") for _ in range(3)
409 ]
410 self.patch(maas_wipe, "list_disks").return_value = disk_names
411 security_info = [
412 {
413 "format_supported": True,
414 "writez_supported": True,
415 "crypto_format": True,
416 "nsze": 0,
417 "lbaf": 0,
418 "ms": 0,
419 }
420 for _ in range(3)
421 ]
422 self.patch(
423 maas_wipe, "get_nvme_security_info"
424 ).side_effect = security_info
311 observed = get_disk_info()425 observed = get_disk_info()
312 self.assertEqual({426 self.assertEqual({
313 disk_names[i]: security_info[i]427 disk_names[i]: security_info[i]
314 for i in range(3)428 for i in range(3)
315 }, observed)429 }, observed)
316430
317 def test_try_secure_erase_not_supported(self):431 def test_try_secure_erase_not_supported_hdparm(self):
318 disk_name = factory.make_name("disk").encode("ascii")432 disk_name = factory.make_name("disk").encode("ascii")
319 disk_info = {433 disk_info = {
320 b"supported": False,434 b"supported": False,
@@ -329,7 +443,7 @@ class TestMAASWipe(MAASTestCase):
329 "%s: drive does not support secure erase." % (443 "%s: drive does not support secure erase." % (
330 disk_name.decode("ascii"))))444 disk_name.decode("ascii"))))
331445
332 def test_try_secure_erase_frozen(self):446 def test_try_secure_erase_frozen_hdparm(self):
333 disk_name = factory.make_name("disk").encode("ascii")447 disk_name = factory.make_name("disk").encode("ascii")
334 disk_info = {448 disk_info = {
335 b"supported": True,449 b"supported": True,
@@ -344,7 +458,7 @@ class TestMAASWipe(MAASTestCase):
344 "%s: not using secure erase; drive is currently frozen." % (458 "%s: not using secure erase; drive is currently frozen." % (
345 disk_name.decode("ascii"))))459 disk_name.decode("ascii"))))
346460
347 def test_try_secure_erase_locked(self):461 def test_try_secure_erase_locked_hdparm(self):
348 disk_name = factory.make_name("disk").encode("ascii")462 disk_name = factory.make_name("disk").encode("ascii")
349 disk_info = {463 disk_info = {
350 b"supported": True,464 b"supported": True,
@@ -359,7 +473,7 @@ class TestMAASWipe(MAASTestCase):
359 "%s: not using secure erase; drive is currently locked." % (473 "%s: not using secure erase; drive is currently locked." % (
360 disk_name.decode("ascii"))))474 disk_name.decode("ascii"))))
361475
362 def test_try_secure_erase_enabled(self):476 def test_try_secure_erase_enabled_hdparm(self):
363 disk_name = factory.make_name("disk").encode("ascii")477 disk_name = factory.make_name("disk").encode("ascii")
364 disk_info = {478 disk_info = {
365 b"supported": True,479 b"supported": True,
@@ -375,7 +489,7 @@ class TestMAASWipe(MAASTestCase):
375 "is already enabled." % (489 "is already enabled." % (
376 disk_name.decode("ascii"))))490 disk_name.decode("ascii"))))
377491
378 def test_try_secure_erase_failed_erase(self):492 def test_try_secure_erase_failed_erase_hdparm(self):
379 disk_name = factory.make_name("disk").encode("ascii")493 disk_name = factory.make_name("disk").encode("ascii")
380 disk_info = {494 disk_info = {
381 b"supported": True,495 b"supported": True,
@@ -384,7 +498,7 @@ class TestMAASWipe(MAASTestCase):
384 b"frozen": False,498 b"frozen": False,
385 }499 }
386 exception = factory.make_exception()500 exception = factory.make_exception()
387 self.patch(maas_wipe, "secure_erase").side_effect = exception501 self.patch(maas_wipe, "secure_erase_hdparm").side_effect = exception
388 self.assertFalse(try_secure_erase(disk_name, disk_info))502 self.assertFalse(try_secure_erase(disk_name, disk_info))
389 self.assertThat(503 self.assertThat(
390 self.print_flush,504 self.print_flush,
@@ -392,7 +506,7 @@ class TestMAASWipe(MAASTestCase):
392 "%s: failed to be securely erased: %s" % (506 "%s: failed to be securely erased: %s" % (
393 disk_name.decode("ascii"), exception)))507 disk_name.decode("ascii"), exception)))
394508
395 def test_try_secure_erase_successful_erase(self):509 def test_try_secure_erase_successful_erase_hdparm(self):
396 disk_name = factory.make_name("disk").encode("ascii")510 disk_name = factory.make_name("disk").encode("ascii")
397 disk_info = {511 disk_info = {
398 b"supported": True,512 b"supported": True,
@@ -400,7 +514,7 @@ class TestMAASWipe(MAASTestCase):
400 b"locked": False,514 b"locked": False,
401 b"frozen": False,515 b"frozen": False,
402 }516 }
403 self.patch(maas_wipe, "secure_erase")517 self.patch(maas_wipe, "secure_erase_hdparm")
404 self.assertTrue(try_secure_erase(disk_name, disk_info))518 self.assertTrue(try_secure_erase(disk_name, disk_info))
405 self.assertThat(519 self.assertThat(
406 self.print_flush,520 self.print_flush,
@@ -408,7 +522,223 @@ class TestMAASWipe(MAASTestCase):
408 "%s: successfully securely erased." % (522 "%s: successfully securely erased." % (
409 disk_name.decode("ascii"))))523 disk_name.decode("ascii"))))
410524
411 def test_secure_erase_writes_known_data(self):525 def test_try_secure_erase_not_supported_nvme(self):
526 disk_name = factory.make_name("nvme").encode("ascii")
527 sec_info = {
528 "format_supported": False,
529 "writez_supported": True,
530 "crypto_format": True,
531 "nsze": 0,
532 "lbaf": 0,
533 "ms": 0,
534 }
535 self.assertFalse(try_secure_erase(disk_name, sec_info))
536 self.assertThat(
537 self.print_flush,
538 MockCalledOnceWith(
539 "Device %s does not support formatting"
540 % disk_name.decode("ascii")
541 ),
542 )
543
544 def test_try_secure_erase_successful_cryto_nvme(self):
545 disk_name = factory.make_name("nvme").encode("ascii")
546 sec_info = {
547 "format_supported": True,
548 "writez_supported": True,
549 "crypto_format": True,
550 "nsze": 0,
551 "lbaf": 0,
552 "ms": 0,
553 }
554 mock_check_output = self.patch(subprocess, "check_output")
555 self.assertTrue(try_secure_erase(disk_name, sec_info))
556 self.assertThat(
557 mock_check_output,
558 MockCalledOnceWith(
559 [
560 "nvme",
561 "format",
562 "-s",
563 "2",
564 "-l",
565 "0",
566 "-m",
567 "0",
568 maas_wipe.DEV_PATH % disk_name,
569 ]
570 ),
571 )
572 self.assertThat(
573 self.print_flush,
574 MockCalledOnceWith(
575 "Secure erase was successful on NVMe drive %s"
576 % disk_name.decode("ascii")
577 ),
578 )
579
580 def test_try_secure_erase_successful_nocryto_nvme(self):
581 disk_name = factory.make_name("nvme").encode("ascii")
582 sec_info = {
583 "format_supported": True,
584 "writez_supported": True,
585 "crypto_format": False,
586 "nsze": 0,
587 "lbaf": 0,
588 "ms": 0,
589 }
590 mock_check_output = self.patch(subprocess, "check_output")
591 self.assertTrue(try_secure_erase(disk_name, sec_info))
592 self.assertThat(
593 mock_check_output,
594 MockCalledOnceWith(
595 [
596 "nvme",
597 "format",
598 "-s",
599 "1",
600 "-l",
601 "0",
602 "-m",
603 "0",
604 maas_wipe.DEV_PATH % disk_name,
605 ]
606 ),
607 )
608 self.assertThat(
609 self.print_flush,
610 MockCalledOnceWith(
611 "Secure erase was successful on NVMe drive %s"
612 % disk_name.decode("ascii")
613 ),
614 )
615
616 def test_try_secure_erase_failed_nvme(self):
617 disk_name = factory.make_name("nvme").encode("ascii")
618 sec_info = {
619 "format_supported": True,
620 "writez_supported": True,
621 "crypto_format": True,
622 "nsze": 0,
623 "lbaf": 0,
624 "ms": 0,
625 }
626 mock_check_output = self.patch(subprocess, "check_output")
627 mock_check_output.side_effect = subprocess.CalledProcessError(
628 1, "nvme format"
629 )
630
631 self.assertFalse(try_secure_erase(disk_name, sec_info))
632 self.assertThat(
633 self.print_flush,
634 MockCalledOnceWith("Error with format command (%s)" % "1"),
635 )
636
637 def test_try_write_zeroes_not_supported_nvme(self):
638 disk_name = factory.make_name("nvme").encode("ascii")
639 sec_info = {
640 "format_supported": False,
641 "writez_supported": False,
642 "crypto_format": False,
643 "nsze": 1,
644 "lbaf": 0,
645 "ms": 0,
646 }
647 mock_print = self.patch(builtins, "print")
648 self.assertFalse(nvme_write_zeroes(disk_name, sec_info))
649 self.assertThat(
650 mock_print,
651 MockCalledOnceWith(
652 "NVMe drive %s does not support write-zeroes"
653 % disk_name.decode("ascii")
654 ),
655 )
656 self.assertThat(
657 self.print_flush,
658 MockCalledOnceWith("Will fallback to regular drive zeroing."),
659 )
660
661 def test_try_write_zeroes_supported_invalid_nsze_nvme(self):
662 disk_name = factory.make_name("nvme").encode("ascii")
663 sec_info = {
664 "format_supported": False,
665 "writez_supported": True,
666 "crypto_format": False,
667 "nsze": 0,
668 "lbaf": 0,
669 "ms": 0,
670 }
671 mock_print = self.patch(builtins, "print")
672 self.assertFalse(nvme_write_zeroes(disk_name, sec_info))
673 self.assertThat(
674 mock_print,
675 MockCalledOnceWith(
676 "Bad namespace information collected on NVMe drive %s"
677 % disk_name.decode("ascii")
678 ),
679 )
680 self.assertThat(
681 self.print_flush,
682 MockCalledOnceWith("Will fallback to regular drive zeroing."),
683 )
684
685 def test_try_write_zeroes_successful_nvme(self):
686 disk_name = factory.make_name("nvme").encode("ascii")
687 sec_info = {
688 "format_supported": False,
689 "writez_supported": True,
690 "crypto_format": False,
691 "nsze": 0x100A,
692 "lbaf": 0,
693 "ms": 0,
694 }
695 mock_check_output = self.patch(subprocess, "check_output")
696 self.assertTrue(nvme_write_zeroes(disk_name, sec_info))
697 self.assertThat(
698 mock_check_output,
699 MockCalledOnceWith(
700 [
701 "nvme",
702 "write-zeroes",
703 "-f",
704 "-s",
705 "0",
706 "-c",
707 "100a",
708 maas_wipe.DEV_PATH % disk_name,
709 ]
710 ),
711 )
712 self.assertThat(
713 self.print_flush,
714 MockCalledOnceWith(
715 "%s: successfully zeroed (using write-zeroes)."
716 % disk_name.decode("ascii")
717 ),
718 )
719
720 def test_try_write_zeroes_failed_nvme(self):
721 disk_name = factory.make_name("nvme").encode("ascii")
722 sec_info = {
723 "format_supported": False,
724 "writez_supported": True,
725 "crypto_format": False,
726 "nsze": 100,
727 "lbaf": 0,
728 "ms": 0,
729 }
730 mock_check_output = self.patch(subprocess, "check_output")
731 mock_check_output.side_effect = subprocess.CalledProcessError(
732 1, "nvme write-zeroes"
733 )
734
735 self.assertFalse(nvme_write_zeroes(disk_name, sec_info))
736 self.assertThat(
737 self.print_flush,
738 MockCalledOnceWith("Error with write-zeroes command (%s)" % "1"),
739 )
740
741 def test_secure_erase_writes_known_data_hdparm(self):
412 tmp_dir = self.make_dir()742 tmp_dir = self.make_dir()
413 dev_path = (tmp_dir + "/%s").encode("ascii")743 dev_path = (tmp_dir + "/%s").encode("ascii")
414 self.patch(maas_wipe, "DEV_PATH", dev_path)744 self.patch(maas_wipe, "DEV_PATH", dev_path)
@@ -420,14 +750,14 @@ class TestMAASWipe(MAASTestCase):
420 mock_check_output = self.patch(subprocess, "check_output")750 mock_check_output = self.patch(subprocess, "check_output")
421 mock_check_output.side_effect = factory.make_exception()751 mock_check_output.side_effect = factory.make_exception()
422752
423 self.assertRaises(WipeError, secure_erase, dev_name)753 self.assertRaises(WipeError, secure_erase_hdparm, dev_name)
424 expected_buf = b'M' * 1024 * 1024754 expected_buf = b"M" * 1024 * 1024
425 with open(file_path, "rb") as fp:755 with open(file_path, "rb") as fp:
426 read_buf = fp.read(len(expected_buf))756 read_buf = fp.read(len(expected_buf))
427 self.assertEqual(757 self.assertEqual(
428 expected_buf, read_buf, "First 1 MiB of file was not written.")758 expected_buf, read_buf, "First 1 MiB of file was not written.")
429759
430 def test_secure_erase_sets_security_password(self):760 def test_secure_erase_sets_security_password_hdparm(self):
431 tmp_dir = self.make_dir()761 tmp_dir = self.make_dir()
432 dev_path = (tmp_dir + "/%s").encode("ascii")762 dev_path = (tmp_dir + "/%s").encode("ascii")
433 self.patch(maas_wipe, "DEV_PATH", dev_path)763 self.patch(maas_wipe, "DEV_PATH", dev_path)
@@ -440,17 +770,17 @@ class TestMAASWipe(MAASTestCase):
440 # Fail to get disk info just to exit early.770 # Fail to get disk info just to exit early.
441 exception_type = factory.make_exception_type()771 exception_type = factory.make_exception_type()
442 self.patch(772 self.patch(
443 maas_wipe,773 maas_wipe, "get_hdparm_security_info"
444 "get_disk_security_info").side_effect = exception_type()774 ).side_effect = exception_type()
445775
446 self.assertRaises(exception_type, secure_erase, dev_name)776 self.assertRaises(exception_type, secure_erase_hdparm, dev_name)
447 self.assertThat(777 self.assertThat(
448 mock_check_output,778 mock_check_output,
449 MockCalledOnceWith([779 MockCalledOnceWith([
450 b'hdparm', b'--user-master', b'u',780 b'hdparm', b'--user-master', b'u',
451 b'--security-set-pass', b'maas', file_path]))781 b'--security-set-pass', b'maas', file_path]))
452782
453 def test_secure_erase_fails_if_not_enabled(self):783 def test_secure_erase_fails_if_not_enabled_hdparm(self):
454 tmp_dir = self.make_dir()784 tmp_dir = self.make_dir()
455 dev_path = (tmp_dir + "/%s").encode("ascii")785 dev_path = (tmp_dir + "/%s").encode("ascii")
456 self.patch(maas_wipe, "DEV_PATH", dev_path)786 self.patch(maas_wipe, "DEV_PATH", dev_path)
@@ -459,16 +789,16 @@ class TestMAASWipe(MAASTestCase):
459 self.make_empty_file(file_path)789 self.make_empty_file(file_path)
460790
461 self.patch(subprocess, "check_output")791 self.patch(subprocess, "check_output")
462 self.patch(maas_wipe, "get_disk_security_info").return_value = {792 self.patch(maas_wipe, "get_hdparm_security_info").return_value = {
463 b"enabled": False,793 b"enabled": False
464 }794 }
465795
466 error = self.assertRaises(WipeError, secure_erase, dev_name)796 error = self.assertRaises(WipeError, secure_erase_hdparm, dev_name)
467 self.assertEqual(797 self.assertEqual(
468 "Failed to enable security to perform secure erase.",798 "Failed to enable security to perform secure erase.",
469 str(error))799 str(error))
470800
471 def test_secure_erase_fails_when_still_enabled(self):801 def test_secure_erase_fails_when_still_enabled_hdparm(self):
472 tmp_dir = self.make_dir()802 tmp_dir = self.make_dir()
473 dev_path = (tmp_dir + "/%s").encode("ascii")803 dev_path = (tmp_dir + "/%s").encode("ascii")
474 self.patch(maas_wipe, "DEV_PATH", dev_path)804 self.patch(maas_wipe, "DEV_PATH", dev_path)
@@ -477,30 +807,47 @@ class TestMAASWipe(MAASTestCase):
477 self.make_empty_file(file_path)807 self.make_empty_file(file_path)
478808
479 mock_check_output = self.patch(subprocess, "check_output")809 mock_check_output = self.patch(subprocess, "check_output")
480 self.patch(maas_wipe, "get_disk_security_info").return_value = {810 self.patch(maas_wipe, "get_hdparm_security_info").return_value = {
481 b"enabled": True,811 b"enabled": True
482 }812 }
483 exception = factory.make_exception()813 exception = factory.make_exception()
484 mock_check_call = self.patch(subprocess, "check_call")814 mock_check_call = self.patch(subprocess, "check_call")
485 mock_check_call.side_effect = exception815 mock_check_call.side_effect = exception
486816
487 error = self.assertRaises(WipeError, secure_erase, dev_name)817 error = self.assertRaises(WipeError, secure_erase_hdparm, dev_name)
488 self.assertThat(mock_check_call, MockCalledOnceWith([818 self.assertThat(
489 b'hdparm', b'--user-master', b'u',819 mock_check_call,
490 b'--security-erase', b'maas', file_path]))820 MockCalledOnceWith(
491 self.assertThat(mock_check_output, MockCallsMatch(821 [
492 call([822 b"hdparm",
493 b'hdparm', b'--user-master', b'u',823 b"--user-master",
494 b'--security-set-pass', b'maas', file_path]),824 b"u",
495 call([825 b"--security-erase",
496 b'hdparm', b'--security-disable', b'maas', file_path])826 b"maas",
497 ))827 file_path,
498 self.assertEqual(828 ]
499 "Failed to securely erase.",829 ),
500 str(error))830 )
831 self.assertThat(
832 mock_check_output,
833 MockCallsMatch(
834 call(
835 [
836 b"hdparm",
837 b"--user-master",
838 b"u",
839 b"--security-set-pass",
840 b"maas",
841 file_path,
842 ]
843 ),
844 call([b"hdparm", b"--security-disable", b"maas", file_path]),
845 ),
846 )
847 self.assertEqual("Failed to securely erase.", str(error))
501 self.assertEqual(exception, error.__cause__)848 self.assertEqual(exception, error.__cause__)
502849
503 def test_secure_erase_fails_when_buffer_not_different(self):850 def test_secure_erase_fails_when_buffer_not_different_hdparm(self):
504 tmp_dir = self.make_dir()851 tmp_dir = self.make_dir()
505 dev_path = (tmp_dir + "/%s").encode("ascii")852 dev_path = (tmp_dir + "/%s").encode("ascii")
506 self.patch(maas_wipe, "DEV_PATH", dev_path)853 self.patch(maas_wipe, "DEV_PATH", dev_path)
@@ -509,25 +856,31 @@ class TestMAASWipe(MAASTestCase):
509 self.make_empty_file(file_path)856 self.make_empty_file(file_path)
510857
511 self.patch(subprocess, "check_output")858 self.patch(subprocess, "check_output")
512 self.patch(maas_wipe, "get_disk_security_info").side_effect = [859 self.patch(maas_wipe, "get_hdparm_security_info").side_effect = [
513 {860 {b"enabled": True},
514 b"enabled": True,861 {b"enabled": False},
515 },
516 {
517 b"enabled": False,
518 },
519 ]862 ]
520 mock_check_call = self.patch(subprocess, "check_call")863 mock_check_call = self.patch(subprocess, "check_call")
521864
522 error = self.assertRaises(WipeError, secure_erase, dev_name)865 error = self.assertRaises(WipeError, secure_erase_hdparm, dev_name)
523 self.assertThat(mock_check_call, MockCalledOnceWith([866 self.assertThat(
524 b'hdparm', b'--user-master', b'u',867 mock_check_call,
525 b'--security-erase', b'maas', file_path]))868 MockCalledOnceWith(
869 [
870 b"hdparm",
871 b"--user-master",
872 b"u",
873 b"--security-erase",
874 b"maas",
875 file_path,
876 ]
877 ),
878 )
526 self.assertEqual(879 self.assertEqual(
527 "Secure erase was performed, but failed to actually work.",880 "Secure erase was performed, but failed to actually work.",
528 str(error))881 str(error))
529882
530 def test_secure_erase_fails_success(self):883 def test_secure_erase_fails_success_hdparm(self):
531 tmp_dir = self.make_dir()884 tmp_dir = self.make_dir()
532 dev_path = (tmp_dir + "/%s").encode("ascii")885 dev_path = (tmp_dir + "/%s").encode("ascii")
533 self.patch(maas_wipe, "DEV_PATH", dev_path)886 self.patch(maas_wipe, "DEV_PATH", dev_path)
@@ -536,13 +889,9 @@ class TestMAASWipe(MAASTestCase):
536 self.make_empty_file(file_path)889 self.make_empty_file(file_path)
537890
538 self.patch(subprocess, "check_output")891 self.patch(subprocess, "check_output")
539 self.patch(maas_wipe, "get_disk_security_info").side_effect = [892 self.patch(maas_wipe, "get_hdparm_security_info").side_effect = [
540 {893 {b"enabled": True},
541 b"enabled": True,894 {b"enabled": False},
542 },
543 {
544 b"enabled": False,
545 },
546 ]895 ]
547896
548 def wipe_buffer(*args, **kwargs):897 def wipe_buffer(*args, **kwargs):
@@ -556,9 +905,9 @@ class TestMAASWipe(MAASTestCase):
556 mock_check_call.side_effect = wipe_buffer905 mock_check_call.side_effect = wipe_buffer
557906
558 # No error should be raised.907 # No error should be raised.
559 secure_erase(dev_name)908 secure_erase_hdparm(dev_name)
560909
561 def test_wipe_quickly(self):910 def test_wipe_quickly_successful(self):
562 tmp_dir = self.make_dir()911 tmp_dir = self.make_dir()
563 dev_path = (tmp_dir + "/%s").encode("ascii")912 dev_path = (tmp_dir + "/%s").encode("ascii")
564 self.patch(maas_wipe, "DEV_PATH", dev_path)913 self.patch(maas_wipe, "DEV_PATH", dev_path)
@@ -566,7 +915,14 @@ class TestMAASWipe(MAASTestCase):
566 file_path = dev_path % dev_name915 file_path = dev_path % dev_name
567 self.make_empty_file(file_path, content=b'T')916 self.make_empty_file(file_path, content=b'T')
568917
918 mock_check_output = self.patch(subprocess, "check_output")
569 wipe_quickly(dev_name)919 wipe_quickly(dev_name)
920 self.assertThat(
921 mock_check_output,
922 MockCalledOnceWith(
923 ["wipefs", "-f", "-a", maas_wipe.DEV_PATH % dev_name]
924 ),
925 )
570926
571 buf_size = 1024 * 1024927 buf_size = 1024 * 1024
572 with open(file_path, "rb") as fp:928 with open(file_path, "rb") as fp:
@@ -577,8 +933,18 @@ class TestMAASWipe(MAASTestCase):
577 zero_buf = b'\0' * 1024 * 1024933 zero_buf = b'\0' * 1024 * 1024
578 self.assertEqual(zero_buf, first_buf, "First 1 MiB was not wiped.")934 self.assertEqual(zero_buf, first_buf, "First 1 MiB was not wiped.")
579 self.assertEqual(zero_buf, last_buf, "Last 1 MiB was not wiped.")935 self.assertEqual(zero_buf, last_buf, "Last 1 MiB was not wiped.")
580936 self.assertThat(
581 def test_zero_disk(self):937 self.print_flush,
938 MockCallsMatch(
939 call("%s: starting quick wipe." % dev_name.decode("ascii")),
940 call(
941 "%s: successfully quickly wiped."
942 % dev_name.decode("ascii")
943 ),
944 ),
945 )
946
947 def test_wipe_quickly_successful_but_wipefs_failed(self):
582 tmp_dir = self.make_dir()948 tmp_dir = self.make_dir()
583 dev_path = (tmp_dir + "/%s").encode("ascii")949 dev_path = (tmp_dir + "/%s").encode("ascii")
584 self.patch(maas_wipe, "DEV_PATH", dev_path)950 self.patch(maas_wipe, "DEV_PATH", dev_path)
@@ -586,13 +952,83 @@ class TestMAASWipe(MAASTestCase):
586 file_path = dev_path % dev_name952 file_path = dev_path % dev_name
587 self.make_empty_file(file_path, content=b'T')953 self.make_empty_file(file_path, content=b'T')
588954
955 mock_check_output = self.patch(subprocess, "check_output")
956 mock_check_output.side_effect = subprocess.CalledProcessError(
957 1, "wipefs"
958 )
959 wipe_quickly(dev_name)
960
961 buf_size = 1024 * 1024
962 with open(file_path, "rb") as fp:
963 first_buf = fp.read(buf_size)
964 fp.seek(-buf_size, 2)
965 last_buf = fp.read(buf_size)
966
967 zero_buf = b"\0" * 1024 * 1024
968 self.assertEqual(zero_buf, first_buf, "First 1 MiB was not wiped.")
969 self.assertEqual(zero_buf, last_buf, "Last 1 MiB was not wiped.")
970 self.assertThat(
971 self.print_flush,
972 MockCallsMatch(
973 call("%s: starting quick wipe." % dev_name.decode("ascii")),
974 call("%s: wipefs failed (1)" % dev_name.decode("ascii")),
975 call(
976 "%s: successfully quickly wiped."
977 % dev_name.decode("ascii")
978 ),
979 ),
980 )
981
982 def test_wipe_quickly_failed(self):
983 dev_name = factory.make_name("disk").encode("ascii")
984
985 mock_check_output = self.patch(subprocess, "check_output")
986 mock_check_output.side_effect = subprocess.CalledProcessError(
987 1, "wipefs"
988 )
989
990 mock_os_open = self.patch(builtins, "open")
991 mock_os_open.side_effect = OSError(-2, "No such file or directory")
992
993 wipe_quickly(dev_name)
994
995 self.assertThat(
996 self.print_flush,
997 MockCallsMatch(
998 call("%s: starting quick wipe." % dev_name.decode("ascii")),
999 call("%s: wipefs failed (1)" % dev_name.decode("ascii")),
1000 call(
1001 "%s: OS error while wiping beginning/end of disk "
1002 "(No such file or directory)" % dev_name.decode("ascii")
1003 ),
1004 call(
1005 "%s: failed to be quickly wiped."
1006 % dev_name.decode("ascii")
1007 ),
1008 ),
1009 )
1010
1011 def test_zero_disk_hdd(self):
1012 tmp_dir = self.make_dir()
1013 dev_path = (tmp_dir + "/%s").encode("ascii")
1014 self.patch(maas_wipe, "DEV_PATH", dev_path)
1015 dev_name = factory.make_name("disk").encode("ascii")
1016 file_path = dev_path % dev_name
1017 self.make_empty_file(file_path, content=b"T")
1018 disk_info = {
1019 b"supported": True,
1020 b"enabled": False,
1021 b"locked": False,
1022 b"frozen": False,
1023 }
1024
589 # Add a little size to the file making it not evenly1025 # Add a little size to the file making it not evenly
590 # divisable by 1 MiB.1026 # divisable by 1 MiB.
591 extra_end = 5121027 extra_end = 512
592 with open(file_path, "a+b") as fp:1028 with open(file_path, "a+b") as fp:
593 fp.write(b'T' * extra_end)1029 fp.write(b'T' * extra_end)
5941030
595 zero_disk(dev_name)1031 zero_disk(dev_name, disk_info)
5961032
597 zero_buf = b'\0' * 1024 * 10241033 zero_buf = b'\0' * 1024 * 1024
598 with open(file_path, "rb") as fp:1034 with open(file_path, "rb") as fp:
@@ -617,7 +1053,7 @@ class TestMAASWipe(MAASTestCase):
617 parser.parse_args.return_value = args1053 parser.parse_args.return_value = args
618 self.patch(argparse, "ArgumentParser").return_value = parser1054 self.patch(argparse, "ArgumentParser").return_value = parser
6191055
620 def test_main_calls_try_secure_erase_for_all_disks(self):1056 def test_main_calls_try_secure_erase_for_all_hdd(self):
621 self.patch_args(True, False)1057 self.patch_args(True, False)
622 disks = {1058 disks = {
623 factory.make_name("disk").encode("ascii"): {}1059 factory.make_name("disk").encode("ascii"): {}
@@ -637,7 +1073,7 @@ class TestMAASWipe(MAASTestCase):
637 self.assertThat(mock_try, MockCallsMatch(*calls))1073 self.assertThat(mock_try, MockCallsMatch(*calls))
638 self.assertThat(mock_zero, MockNotCalled())1074 self.assertThat(mock_zero, MockNotCalled())
6391075
640 def test_main_calls_zero_disk_if_no_secure_erase(self):1076 def test_main_calls_zero_disk_if_no_secure_erase_hdd(self):
641 self.patch_args(True, False)1077 self.patch_args(True, False)
642 disks = {1078 disks = {
643 factory.make_name("disk").encode("ascii"): {}1079 factory.make_name("disk").encode("ascii"): {}
@@ -650,18 +1086,11 @@ class TestMAASWipe(MAASTestCase):
650 mock_try.return_value = False1086 mock_try.return_value = False
651 maas_wipe.main()1087 maas_wipe.main()
6521088
653 try_calls = [1089 try_calls = [call(disk, info) for disk, info in disks.items()]
654 call(disk, info)
655 for disk, info in disks.items()
656 ]
657 wipe_calls = [
658 call(disk)
659 for disk in disks.keys()
660 ]
661 self.assertThat(mock_try, MockCallsMatch(*try_calls))1090 self.assertThat(mock_try, MockCallsMatch(*try_calls))
662 self.assertThat(mock_zero, MockCallsMatch(*wipe_calls))1091 self.assertThat(mock_zero, MockCallsMatch(*try_calls))
6631092
664 def test_main_calls_wipe_quickly_if_no_secure_erase(self):1093 def test_main_calls_wipe_quickly_if_no_secure_erase_hdd(self):
665 self.patch_args(True, True)1094 self.patch_args(True, True)
666 disks = {1095 disks = {
667 factory.make_name("disk").encode("ascii"): {}1096 factory.make_name("disk").encode("ascii"): {}
@@ -718,9 +1147,6 @@ class TestMAASWipe(MAASTestCase):
718 mock_try.return_value = False1147 mock_try.return_value = False
719 maas_wipe.main()1148 maas_wipe.main()
7201149
721 wipe_calls = [1150 wipe_calls = [call(disk, info) for disk, info in disks.items()]
722 call(disk)
723 for disk in disks.keys()
724 ]
725 self.assertThat(mock_try, MockNotCalled())1151 self.assertThat(mock_try, MockNotCalled())
726 self.assertThat(zero_disk, MockCallsMatch(*wipe_calls))1152 self.assertThat(zero_disk, MockCallsMatch(*wipe_calls))
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
727new file mode 1006441153new file mode 100644
index 0000000..f9bb440
--- /dev/null
+++ b/src/metadataserver/user_data/templates/snippets/tests/test_maas_wipe_defs.py
@@ -0,0 +1,232 @@
1#!/usr/bin/python3
2# Copyright 2020 Canonical Ltd. This software is licensed under the
3# GNU Affero General Public License version 3 (see the file LICENSE).
4#
5# hdparm / nvme-cli outputs used on maas_wipe testing
6
7HDPARM_BEFORE_SECURITY = b"""\
8/dev/sda:
9
10ATA device, with non-removable media
11 Model Number: INTEL SSDSC2CT240A4
12 Serial Number: CVKI3206029X240DGN
13 Firmware Revision: 335u
14 Transport: Serial, ATA8-AST, SATA 1.0a, SATA II Extensions
15Standards:
16 Used: unknown (minor revision code 0xffff)
17 Supported: 9 8 7 6 5
18 Likely used: 9
19Configuration:
20 Logical max current
21 cylinders 16383 16383
22 heads 16 16
23 sectors/track 63 63
24 --
25 CHS current addressable sectors: 16514064
26 LBA user addressable sectors: 268435455
27 LBA48 user addressable sectors: 468862128
28 Logical Sector size: 512 bytes
29 Physical Sector size: 512 bytes
30 Logical Sector-0 offset: 0 bytes
31 device size with M = 1024*1024: 228936 MBytes
32 device size with M = 1000*1000: 240057 MBytes (240 GB)
33 cache/buffer size = unknown
34 Nominal Media Rotation Rate: Solid State Device
35Capabilities:
36 LBA, IORDY(can be disabled)
37 Queue depth: 32
38 Standby timer values: spec'd by Standard, no device specific minimum
39 R/W multiple sector transfer: Max = 16 Current = 16
40 Advanced power management level: 254
41 DMA: mdma0 mdma1 mdma2 udma0 udma1 udma2 udma3 udma4 udma5 *udma6
42 Cycle time: min=120ns recommended=120ns
43 PIO: pio0 pio1 pio2 pio3 pio4
44 Cycle time: no flow control=120ns IORDY flow control=120ns
45Commands/features:
46 Enabled Supported:
47 * SMART feature set
48 Security Mode feature set
49 * Power Management feature set
50 * Write cache
51 * Look-ahead
52 * Host Protected Area feature set
53 * WRITE_BUFFER command
54 * READ_BUFFER command
55 * NOP cmd
56 * DOWNLOAD_MICROCODE
57 * Advanced Power Management feature set
58 Power-Up In Standby feature set
59 * 48-bit Address feature set
60 * Mandatory FLUSH_CACHE
61 * FLUSH_CACHE_EXT
62 * SMART error logging
63 * SMART self-test
64 * General Purpose Logging feature set
65 * WRITE_{DMA|MULTIPLE}_FUA_EXT
66 * 64-bit World wide name
67 * IDLE_IMMEDIATE with UNLOAD
68 * WRITE_UNCORRECTABLE_EXT command
69 * {READ,WRITE}_DMA_EXT_GPL commands
70 * Segmented DOWNLOAD_MICROCODE
71 * Gen1 signaling speed (1.5Gb/s)
72 * Gen2 signaling speed (3.0Gb/s)
73 * Gen3 signaling speed (6.0Gb/s)
74 * Native Command Queueing (NCQ)
75 * Host-initiated interface power management
76 * Phy event counters
77 * DMA Setup Auto-Activate optimization
78 Device-initiated interface power management
79 * Software settings preservation
80 * SMART Command Transport (SCT) feature set
81 * SCT Data Tables (AC5)
82 * reserved 69[4]
83 * Data Set Management TRIM supported (limit 1 block)
84 * Deterministic read data after TRIM
85"""
86
87HDPARM_AFTER_SECURITY = b"""\
88Logical Unit WWN Device Identifier: 55cd2e40002643cf
89 NAA : 5
90 IEEE OUI : 5cd2e4
91 Unique ID : 0002643cf
92Checksum: correct
93"""
94
95HDPARM_SECURITY_NOT_SUPPORTED = b"""\
96Security:
97 Master password revision code = 65534
98 not supported
99 not enabled
100 not locked
101 not frozen
102 not expired: security count
103 supported: enhanced erase
104 4min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT.
105"""
106
107HDPARM_SECURITY_SUPPORTED_NOT_ENABLED = b"""\
108Security:
109 Master password revision code = 65534
110 supported
111 not enabled
112 not locked
113 not frozen
114 not expired: security count
115 supported: enhanced erase
116 4min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT.
117"""
118
119HDPARM_SECURITY_SUPPORTED_ENABLED = b"""\
120Security:
121 Master password revision code = 65534
122 supported
123 enabled
124 not locked
125 not frozen
126 not expired: security count
127 supported: enhanced erase
128 4min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT.
129"""
130
131HDPARM_SECURITY_ALL_TRUE = b"""\
132Security:
133 Master password revision code = 65534
134 supported
135 enabled
136 locked
137 frozen
138 not expired: security count
139 supported: enhanced erase
140 4min for SECURITY ERASE UNIT. 2min for ENHANCED SECURITY ERASE UNIT.
141"""
142
143NVME_IDCTRL_PROLOGUE = b"""\
144NVME Identify Controller:
145vid : 0x8086
146ssvid : 0x8086
147sn : CVMD5066002T400AGN
148mn : INTEL SSDPEDME400G4
149fr : 8DV10131
150rab : 0
151ieee : 5cd2e4
152cmic : 0
153mdts : 5
154cntlid : 0
155ver : 0
156rtd3r : 0
157rtd3e : 0
158oaes : 0
159ctratt : 0
160acl : 3
161aerl : 3
162frmw : 0x2
163lpa : 0
164elpe : 63
165npss : 0
166avscc : 0
167apsta : 0
168wctemp : 0
169cctemp : 0
170mtfa : 0
171hmpre : 0
172hmmin : 0
173tnvmcap : 0
174unvmcap : 0
175rpmbs : 0
176edstt : 0
177dsto : 0
178fwug : 0
179kas : 0
180hctma : 0
181mntmt : 0
182mxtmt : 0
183sanicap : 0
184hmminds : 0
185hmmaxd : 0
186sqes : 0x66
187cqes : 0x44
188maxcmd : 0
189nn : 1
190fuses : 0
191"""
192
193NVME_IDCTRL_OACS_FORMAT_SUPPORTED = b"""\
194oacs : 0x6
195"""
196
197NVME_IDCTRL_OACS_FORMAT_UNSUPPORTED = b"""\
198oacs : 0x4
199"""
200
201NVME_IDCTRL_ONCS_WRITEZ_SUPPORTED = b"""\
202oncs : 0xe
203"""
204
205NVME_IDCTRL_ONCS_WRITEZ_UNSUPPORTED = b"""\
206oncs : 0x6
207"""
208
209NVME_IDCTRL_FNA_CRYPTFORMAT_SUPPORTED = b"""\
210fna : 0x7
211"""
212
213NVME_IDCTRL_FNA_CRYPTFORMAT_UNSUPPORTED = b"""\
214fna : 0x3
215"""
216
217NVME_IDCTRL_EPILOGUE = b"""\
218vwc : 0
219awun : 0
220awupf : 0
221nvscc : 0
222acwu : 0
223sgls : 0
224subnqn :
225ioccsz : 0
226iorcsz : 0
227icdoff : 0
228ctrattr : 0
229msdbd : 0
230ps 0 : mp:25.00W operational enlat:0 exlat:0 rrt:0 rrl:0
231 rwt:0 rwl:0 idle_power:- active_power:-
232"""

Subscribers

People subscribed via source and target branches