Add tus upload backend via bfabricpy - #25
Conversation
|
Thanks for putting this together. I agree with the central separation: completing a tus transfer is not sufficient to delete the instrument copy, and checking the individual B-Fabric resource status at deletion time is the right foundation. FCC processing does not need an acquisition/workunit-level “done” state; the relevant invariant here is strictly per source file/resource because BioBeamer owns source cleanup. I do not think the current implementation is safe to merge for production deletion yet:
The repeated Suggested state shape: Deletion would require: unchanged local identity, every required destination complete, tus resource Verification on this branch with bfabricpy 1.22.0: all 58 tus-specific tests pass. The full suite produced 99 passed, 1 skipped, and one environmental SFTP integration failure because no SSH server was running on localhost. There are currently no GitHub checks/workflows on the PR, so I would also add CI before enabling the backend on instrument hosts. |
What this adds
tool="tus"as a fourth transfer backend alongsiderobocopy,scpandsftp. It uploads overHTTP(S) to a tus endpoint via bfabricpy, which means nothing has to be mounted (no UNC mapping,
no SSH keys) and the workunit is created at upload time, so data lands in B-Fabric registered
rather than needing a separate import.
Additive and opt-in per host: existing robocopy/scp/sftp hosts are untouched.
Design decisions
upload_filesis batch (one call = one workunit), so tusbranches above the per-file copy loop in
copy_files_with_tool; routing it through would havecreated one workunit per file. Bruker
.d/ Waters.PRObundles are uploaded as a singledirectory entry so nested structure survives as resource names (
sub/deep.bin).application_idcomes from<b-fabric><applicationID>, which has been in the XSD since thebeginning and was never read by any code — the parser only walked
i.attrib, never children.container_idis parsed from the file path, with no fallback. One instrument writes data formany projects, so the container cannot be a per-host constant. Filing data under the wrong project
is worse than a failed run, so an unrecognisable path is a hard error naming both the path and the
accepted formats; nothing is uploaded. The legal formats are an operational contract, documented in
the README in wording identical to the runtime error.
BFABRIC_CLIENT_SECRETonly, so itcannot leak into logs or
/proc. This also fixes a pre-existing leak where--passwordwas writtenverbatim into the launcher's INFO log.
Verified end to end
Against a live local stack (tusd + bfabric-tus-storage-service + B-Fabric):
sub/deep.binprefix intactpendingresources; the retry uploads them rather thanfailing to link (needs the bfabricpy change above)
Unit tests: 29 new in
tests/test_tusupload.py, 71 passing overall (42 before this branch).Storage is verified before a source file is deleted
A completed tus transfer is not confirmed storage. The storage service runs its virus scan,
checksum verification and disk checks in a post-finish hook — after the transfer is already
complete — and that hook reports to B-Fabric rather than to BioBeamer, so it cannot fail the
transfer that produced it. A file bfabricpy reports in
summary.uploadscan therefore end up withits resource marked
failed, holding no usable bytes.That matters here because
max_time_deletedeletes source files once they are old enough andrecorded as copied. Trusting the ledger alone would eventually delete the only copy of data
B-Fabric rejected. So for
tool="tus":tus_resources.jsonbeside the ledgeravailablefor ever
Only
availableauthorises a delete. Everything else is withheld —pending(the server has notruled yet), a status that could not be read, and any file not accounted for. A failure to check
is never mistaken for confirmation, and equally never triggers a mass re-upload.
The check is at deletion time rather than just after upload because verification runs on the
server's schedule: measured against the live stack, resources read
pending~1s after upload andavailable~25s later. Checking immediately would tell us nothing, and the state that matters isthe one when the delete decision is made.
This is tus-specific on purpose. For robocopy/scp/sftp the target is a staging area and a separate
downstream process moves and registers the files, so a completed transfer is the whole job and
the existing destination comparison already covers it. tus collapses transfer and registration into
one step, which is what creates the unobservable window.
Verified against the live stack
tests/integration/tus_verification_walkthrough.shwalks it through and prints B-Fabric's verdictper file at each step. Using an EICAR test file — which transfers normally and is then rejected by
ClamAV in post-finish — the run shows the upload reporting success, the resource going
failed, thelocal copy correctly kept, and the ledger repaired on the following run.
Known gap, for discussion: #26. A file that will never pass verification currently retries
indefinitely, creating a new workunit and resource each run. No data is at risk (the local copy is
always kept) but nothing escalates it to a human, and a transient rejection is indistinguishable
from a deterministic one. Options are laid out in that issue.
Files
src/biobeamer/tusupload.pysrc/biobeamer/cli.pytusin the destination-comparison dispatch; non-secret bfabric flags; deletion gated on verified storage; ledger repairsrc/biobeamer/tusregistry.pytests/test_tusregistry.pytests/integration/tus_verification_walkthrough.shsrc/biobeamer/parser.py<b-fabric><applicationID>; per-instanceparameterscopysrc/biobeamer/configs/BioBeamer2.xsdtus_*attributes,xs:anyAttribute,applicationID→xs:intpyproject.toml[tus]extra;requires-pythonraised to a truthful 3.9README.mdtool="tus", thetus_*attributes, the legal source path formats, and the storage-verification contractRollout note
xs:anyAttributeis the load-bearing part of the XSD change. The config XML is shared by the wholefleet while the XSD ships per BioBeamer version, so without it any new attribute breaks every host
still pinned to an older version — including robocopy hosts unrelated to tus. Verified empirically:
a
tus_endpointattribute fails validation against the old XSD and passes with the one-lineanyAttributeaddition. Back-port that line and roll it out before the config change lands.The
[tus]extra carries apython_version >= '3.11'marker because bfabric requires 3.11+ whileBioBeamer must stay installable on the 3.8/3.9 instrument PCs. On those hosts the extra resolves to
nothing and
tool="tus"fails at startup with a message saying so.