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