demo: read the recovered content instead of a path
zerto_recover_file no longer writes the file to this host and hands back a path, because a path here means nothing to a caller elsewhere and letting the caller choose it was an arbitrary write. Both drivers still read rec["path"], so they broke. They now use the returned content. A small recovered_bytes() helper in each driver decodes the text or base64 form, so the Windows copy-back keeps shipping exact bytes rather than letting PowerShell rewrite line endings, which is the bug that put a stray CR in an earlier take. The Linux driver writes the bytes to a local file before scp, since scp needs something on disk to send. Verified against a live recovery: the helper returns 164 bytes whose sha256 matches the one the server reported, and the keys the on-screen show() filter uses are all still present. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_016yVfC5nvZowoLFnEGWhLGn
This commit is contained in:
+17
-4
@@ -16,6 +16,17 @@ CYAN, GREEN, RED, YEL, MAG = "\033[36m", "\033[32m", "\033[31m", "\033[33m", "\0
|
|||||||
SPEED = float(os.environ.get("DEMO_SPEED", "1.0"))
|
SPEED = float(os.environ.get("DEMO_SPEED", "1.0"))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def recovered_bytes(rec: dict) -> bytes:
|
||||||
|
"""zerto_recover_file returns the content now, not a path on the MCP host.
|
||||||
|
|
||||||
|
The server may be on another machine, so a path there is of no use here.
|
||||||
|
"""
|
||||||
|
import base64
|
||||||
|
if rec.get("encoding") == "base64":
|
||||||
|
return base64.b64decode(rec["content"])
|
||||||
|
return rec["content"].encode()
|
||||||
|
|
||||||
def w(s=""):
|
def w(s=""):
|
||||||
sys.stdout.write(s + "\n"); sys.stdout.flush()
|
sys.stdout.write(s + "\n"); sys.stdout.flush()
|
||||||
|
|
||||||
@@ -212,8 +223,8 @@ async def main():
|
|||||||
if not rec.get("ok"):
|
if not rec.get("ok"):
|
||||||
w(f"{RED} FLR failed{RST}"); return 1
|
w(f"{RED} FLR failed{RST}"); return 1
|
||||||
beat(1.5)
|
beat(1.5)
|
||||||
w(f"{YEL} $ cat {rec['path']}{RST}")
|
w(f"{YEL} recovered {rec['bytes']} bytes, sha256 {rec['sha256'][:16]}...{RST}")
|
||||||
body = Path(rec["path"]).read_text()
|
body = recovered_bytes(rec).decode()
|
||||||
for line in body.splitlines():
|
for line in body.splitlines():
|
||||||
w(f"{GREEN}{BOLD} | {line}{RST}")
|
w(f"{GREEN}{BOLD} | {line}{RST}")
|
||||||
beat(2.5)
|
beat(2.5)
|
||||||
@@ -222,11 +233,13 @@ async def main():
|
|||||||
step("Copy it back to the guest")
|
step("Copy it back to the guest")
|
||||||
note("This part is scp, not Zerto. Zerto got the bytes back.")
|
note("This part is scp, not Zerto. Zerto got the bytes back.")
|
||||||
target = f"{CFG['user']}@{CFG['host']}"
|
target = f"{CFG['user']}@{CFG['host']}"
|
||||||
|
local_copy = SP / rec["name"]
|
||||||
|
local_copy.write_bytes(recovered_bytes(rec))
|
||||||
scp = (["sshpass", "-p", CFG["password"]] if CFG.get("password") else []) + \
|
scp = (["sshpass", "-p", CFG["password"]] if CFG.get("password") else []) + \
|
||||||
["scp", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null",
|
["scp", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null",
|
||||||
"-o", "LogLevel=ERROR"] + (["-i", CFG["key"]] if CFG.get("key") else []) + \
|
"-o", "LogLevel=ERROR"] + (["-i", CFG["key"]] if CFG.get("key") else []) + \
|
||||||
[rec["path"], f"{target}:{CFG['guest_path']}"]
|
[str(local_copy), f"{target}:{CFG['guest_path']}"]
|
||||||
w(f"{YEL} $ scp {Path(rec['path']).name} {target}:{CFG['guest_path']}{RST}")
|
w(f"{YEL} $ scp {rec['name']} {target}:{CFG['guest_path']}{RST}")
|
||||||
subprocess.run(scp, capture_output=True, text=True, timeout=60)
|
subprocess.run(scp, capture_output=True, text=True, timeout=60)
|
||||||
guest(f"cat {CFG['guest_path']}")
|
guest(f"cat {CFG['guest_path']}")
|
||||||
beat(1.5)
|
beat(1.5)
|
||||||
|
|||||||
+13
-2
@@ -22,6 +22,17 @@ T0 = time.time()
|
|||||||
STEP = [0]
|
STEP = [0]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def recovered_bytes(rec: dict) -> bytes:
|
||||||
|
"""zerto_recover_file returns the content now, not a path on the MCP host.
|
||||||
|
|
||||||
|
The server may be on another machine, so a path there is of no use here.
|
||||||
|
"""
|
||||||
|
import base64
|
||||||
|
if rec.get("encoding") == "base64":
|
||||||
|
return base64.b64decode(rec["content"])
|
||||||
|
return rec["content"].encode()
|
||||||
|
|
||||||
def w(s=""):
|
def w(s=""):
|
||||||
sys.stdout.write(s + "\n"); sys.stdout.flush()
|
sys.stdout.write(s + "\n"); sys.stdout.flush()
|
||||||
|
|
||||||
@@ -155,7 +166,7 @@ async def main():
|
|||||||
show(rec, keep=["ok", "bytes", "flr_path", "unmount"])
|
show(rec, keep=["ok", "bytes", "flr_path", "unmount"])
|
||||||
if not rec.get("ok"):
|
if not rec.get("ok"):
|
||||||
w(f"{RED} FLR failed{RST}"); return 1
|
w(f"{RED} FLR failed{RST}"); return 1
|
||||||
body = Path(rec["path"]).read_text()
|
body = recovered_bytes(rec).decode()
|
||||||
w(f"{YEL} recovered file:{RST}")
|
w(f"{YEL} recovered file:{RST}")
|
||||||
for line in body.splitlines():
|
for line in body.splitlines():
|
||||||
w(f"{GREEN}{BOLD} | {line}{RST}")
|
w(f"{GREEN}{BOLD} | {line}{RST}")
|
||||||
@@ -166,7 +177,7 @@ async def main():
|
|||||||
# endings and appends one, which makes the restored file differ
|
# endings and appends one, which makes the restored file differ
|
||||||
# from what Zerto handed back. WriteAllBytes does not.
|
# from what Zerto handed back. WriteAllBytes does not.
|
||||||
import base64
|
import base64
|
||||||
blob = Path(rec["path"]).read_bytes()
|
blob = recovered_bytes(rec)
|
||||||
b64 = base64.b64encode(blob).decode()
|
b64 = base64.b64encode(blob).decode()
|
||||||
guest_ps(
|
guest_ps(
|
||||||
f"[IO.File]::WriteAllBytes('{CFG['guest_path']}',"
|
f"[IO.File]::WriteAllBytes('{CFG['guest_path']}',"
|
||||||
|
|||||||
Reference in New Issue
Block a user