Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.

Commit 29d5005

Browse files
mangelajogithub-actions[bot]
authored andcommitted
Implement more robust flash pipeline
(cherry picked from commit 2b30889)
1 parent 3c18f07 commit 29d5005

1 file changed

Lines changed: 60 additions & 19 deletions

File tree

  • packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers

packages/jumpstarter-driver-flashers/jumpstarter_driver_flashers/client.py

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -309,43 +309,84 @@ def _flash_with_progress(
309309
self._check_url_access(console, prompt, image_url, tls_args, header_args)
310310

311311
# Flash the image, we run curl -> decompress -> dd in the background, so we can monitor dd's progress
312+
# Use pipefail to ensure the pipeline fails if any command in the pipe fails
312313
flash_cmd = (
313-
f'( curl -fsSL {tls_args} {header_args} "{image_url}" | '
314+
f'( set -o pipefail; curl -fsSL {tls_args} {header_args} "{image_url}" | '
314315
f"{decompress_cmd} "
315-
f"dd of={target_path} bs=64k iflag=fullblock oflag=direct) &"
316+
f'dd of={target_path} bs=64k iflag=fullblock oflag=direct ' +
317+
'&& echo "FLASH_COMPLETE" || echo "FLASH_FAILED" ) &'
316318
)
317319
console.sendline(flash_cmd)
318320
console.expect(prompt, timeout=EXPECT_TIMEOUT_DEFAULT * 2)
319321

320-
# monitor the dd process to understand flashing progrses
322+
# Monitor flash progress by accumulating console output and looking for completion markers
321323
console.sendline("pidof dd")
322324
console.expect(prompt, timeout=EXPECT_TIMEOUT_DEFAULT)
323325
dd_pid = console.before.decode(errors="ignore").splitlines()[1].strip()
324326

325327
# Initialize progress tracking variables
326328
last_pos = 0
327329
last_time = time.time()
330+
flash_start_time = time.time()
331+
accumulated_output = ""
332+
dd_finished_time = None
328333

329334
while True:
335+
# Check if dd process is still running for progress monitoring
330336
console.sendline(f"cat /proc/{dd_pid}/fdinfo/1")
331337
console.expect(prompt, timeout=EXPECT_TIMEOUT_DEFAULT)
332338
if "No such file or directory" in console.before.decode(errors="ignore"):
333-
break
334-
data = console.before.decode(errors="ignore")
335-
match = re.search(r"pos:\s+(\d+)", data)
336-
if match:
337-
current_bytes = int(match.group(1))
338-
current_time = time.time()
339-
elapsed = current_time - last_time
340-
341-
if elapsed >= 5.0: # Update speed every 5 seconds
342-
bytes_diff = current_bytes - last_pos
343-
speed_mb = (bytes_diff / (1024 * 1024)) / elapsed
344-
total_mb = current_bytes / (1024 * 1024)
345-
self.logger.info(f"Flash progress: {total_mb:.2f} MB, Speed: {speed_mb:.2f} MB/s")
346-
347-
last_pos = current_bytes
348-
last_time = current_time
339+
# dd process finished, check for completion markers in accumulated output
340+
if "FLASH_COMPLETE" in accumulated_output:
341+
self.logger.info("Flash operation completed successfully")
342+
break
343+
elif "FLASH_FAILED" in accumulated_output:
344+
raise RuntimeError("Flash operation failed - curl or pipeline failed")
345+
else:
346+
# dd finished but no completion marker found yet - wait a bit for echo to execute
347+
if dd_finished_time is None:
348+
dd_finished_time = time.time()
349+
elif time.time() - dd_finished_time > 5: # Wait up to 5 seconds for echo
350+
raise RuntimeError("Flash operation completed without success/failure marker")
351+
# Continue checking for a few more iterations
352+
time.sleep(1)
353+
continue
354+
else:
355+
# dd is still running, check for progress and accumulate output
356+
data = console.before.decode(errors="ignore")
357+
# Keep only the last 128 bytes to prevent memory growth
358+
if len(accumulated_output) > 128:
359+
accumulated_output = accumulated_output[-128:]
360+
361+
accumulated_output += data
362+
363+
# Check for completion markers in the accumulated output
364+
if "FLASH_COMPLETE" in accumulated_output:
365+
self.logger.info("Flash operation completed successfully")
366+
break
367+
elif "FLASH_FAILED" in accumulated_output:
368+
raise RuntimeError("Flash operation failed - curl or pipeline failed")
369+
370+
# Monitor dd progress
371+
match = re.search(r"pos:\s+(\d+)", data)
372+
if match:
373+
current_bytes = int(match.group(1))
374+
current_time = time.time()
375+
elapsed = current_time - last_time
376+
377+
if elapsed >= 5.0: # Update speed every 5 seconds
378+
bytes_diff = current_bytes - last_pos
379+
speed_mb = (bytes_diff / (1024 * 1024)) / elapsed
380+
total_mb = current_bytes / (1024 * 1024)
381+
self.logger.info(f"Flash progress: {total_mb:.2f} MB, Speed: {speed_mb:.2f} MB/s")
382+
383+
last_pos = current_bytes
384+
last_time = current_time
385+
386+
# Check for timeout (prevent infinite loops)
387+
if time.time() - flash_start_time > EXPECT_TIMEOUT_SYNC:
388+
raise RuntimeError("Flash operation timed out")
389+
349390
time.sleep(1)
350391

351392
self.logger.info("Flushing buffers")

0 commit comments

Comments
 (0)