PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
 
Loading...
Searching...
No Matches
Functions
picurv_cli.storage.catalog Namespace Reference

Functions

dict workspace_asset_references (str workspace_root)
 Count what still refers to each published workspace asset.
 
list prune_unused_workspace_assets (str workspace_root, dict profile, bool dry_run=False)
 Remove local asset objects that nothing local needs and storage has verified.
 
dict _find_reusable_archive (dict profile, dict target, str fingerprint, str compression=None)
 Find a completed archive of this artifact whose content is already current.
 
list list_remote_manifests (dict profile)
 Enumerate completed archive manifests from the remote catalog.
 
dict verify_remote_archive (dict profile, str archive_id)
 Verify the completion marker and every stored chunk checksum.
 
str resolve_workspace_archive_id (dict profile, str workspace_id)
 Find the newest complete workspace archive carrying one workspace identity.
 

Function Documentation

◆ workspace_asset_references()

dict picurv_cli.storage.catalog.workspace_asset_references ( str  workspace_root)

Count what still refers to each published workspace asset.

A local copy may be removed once nothing local needs it and a verified remote copy exists. Runs that are themselves cold still reference the asset - that is what keeps the remote copy alive - but they do not keep the local one.

Parameters
[in]workspace_rootInitialized workspace root.
Returns
Mapping of asset id to its reference counts and local object path.

Definition at line 41 of file catalog.py.

41def workspace_asset_references(workspace_root: str) -> dict:
42 """!
43 @brief Count what still refers to each published workspace asset.
44
45 @details A local copy may be removed once nothing local needs it and a verified
46 remote copy exists. Runs that are themselves cold still *reference* the
47 asset - that is what keeps the remote copy alive - but they do not keep
48 the local one.
49 @param[in] workspace_root Initialized workspace root.
50 @return Mapping of asset id to its reference counts and local object path.
51 """
52 references = {}
53 objects_root = os.path.join(workspace_root, "assets", "objects")
54 if os.path.isdir(objects_root):
55 for kind in sorted(os.listdir(objects_root)):
56 kind_root = os.path.join(objects_root, kind)
57 if not os.path.isdir(kind_root):
58 continue
59 for asset_id in sorted(os.listdir(kind_root)):
60 object_root = os.path.join(kind_root, asset_id)
61 if os.path.isdir(object_root):
62 references[asset_id] = {
63 "asset_id": asset_id, "kind": kind, "object": object_root,
64 "active_local_runs": 0, "cold_runs": 0,
65 }
66 for artifacts in ("runs", "studies"):
67 root = os.path.join(workspace_root, artifacts)
68 if not os.path.isdir(root):
69 continue
70 for lock_path in Path(root).glob("**/inputs/assets.lock.yml"):
71 try:
72 with open(lock_path, "r", encoding="utf-8") as stream:
73 lock = yaml.safe_load(stream) or {}
74 except (OSError, ValueError):
75 continue
76 run_root = lock_path.parent.parent
77 cold = is_artifact_cold(str(run_root))
78 for reference in (lock.get("assets") or {}).values():
79 entry = references.get(reference.get("asset_id"))
80 if entry is None:
81 continue
82 entry["cold_runs" if cold else "active_local_runs"] += 1
83 return references
84
85
Here is the caller graph for this function:

◆ prune_unused_workspace_assets()

list picurv_cli.storage.catalog.prune_unused_workspace_assets ( str  workspace_root,
dict  profile,
bool   dry_run = False 
)

Remove local asset objects that nothing local needs and storage has verified.

Parameters
[in]workspace_rootInitialized workspace root.
[in]profileResolved storage profile.
[in]dry_runReport the decision without removing anything.
Returns
Removal decisions, one per published asset.

Definition at line 86 of file catalog.py.

87 dry_run: bool = False) -> list:
88 """!
89 @brief Remove local asset objects that nothing local needs and storage has verified.
90 @param[in] workspace_root Initialized workspace root.
91 @param[in] profile Resolved storage profile.
92 @param[in] dry_run Report the decision without removing anything.
93 @return Removal decisions, one per published asset.
94 """
95 protected = set()
96 for manifest in list_remote_manifests(profile):
97 if manifest.get("artifact_type") != "workspace":
98 continue
99 for asset_id in manifest.get("workspace_assets") or []:
100 protected.add(asset_id)
101 decisions = []
102 for entry in workspace_asset_references(workspace_root).values():
103 verified = entry["asset_id"] in protected
104 removable = verified and entry["active_local_runs"] == 0
105 decision = {**entry, "remote_protection": "verified" if verified else "none",
106 "local_removal": "safe" if removable else "blocked"}
107 if removable and not dry_run:
108 try:
109 shutil.rmtree(entry["object"])
110 decision["removed"] = True
111 except OSError as exc:
112 # Partly deleted is still present: report it rather than count it as gone.
113 decision["removed"] = False
114 decision["error"] = str(exc)
115 decisions.append(decision)
116 return decisions
117
118
Here is the call graph for this function:

◆ _find_reusable_archive()

dict picurv_cli.storage.catalog._find_reusable_archive ( dict  profile,
dict  target,
str  fingerprint,
str   compression = None 
)
protected

Find a completed archive of this artifact whose content is already current.

Parameters
[in]profileResolved storage profile.
[in]targetLocal artifact target.
[in]fingerprintInventory fingerprint of the artifact as it stands now.
[in]compressionResolved compression the archive must have been made with; an archive at another level is not what was asked for.
Returns
Matching remote manifest, or None.

Definition at line 119 of file catalog.py.

120 compression: str = None) -> dict:
121 """!
122 @brief Find a completed archive of this artifact whose content is already current.
123 @param[in] profile Resolved storage profile.
124 @param[in] target Local artifact target.
125 @param[in] fingerprint Inventory fingerprint of the artifact as it stands now.
126 @param[in] compression Resolved compression the archive must have been made with;
127 an archive at another level is not what was asked for.
128 @return Matching remote manifest, or None.
129 """
130 if not fingerprint:
131 return None
132 identity = (target["artifact_type"], target.get("run_id"),
133 target.get("study_id"), target.get("case_id"))
134 try:
135 candidates = list_remote_manifests(profile)
136 except StorageError as exc:
137 # Reuse is an optimization. A remote that cannot be listed - not yet created,
138 # briefly unreachable - must fall through to a normal upload, never fail here.
139 print(f"[INFO] Could not check for a reusable archive ({exc}); uploading.")
140 return None
141 newest = None
142 for manifest in candidates:
143 if manifest.get("inventory_sha256") != fingerprint:
144 continue
145 if compression and manifest.get("compression") != compression:
146 continue
147 if (manifest.get("artifact_type"), manifest.get("run_id"),
148 manifest.get("study_id"), manifest.get("case_id")) != identity:
149 continue
150 if newest is None or str(manifest.get("created_at", "")) > str(newest.get("created_at", "")):
151 newest = manifest
152 return newest
153
154

◆ list_remote_manifests()

list picurv_cli.storage.catalog.list_remote_manifests ( dict  profile)

Enumerate completed archive manifests from the remote catalog.

Parameters
[in]profileValue supplied through the profile argument.
Returns
Result produced by this operation.

Definition at line 155 of file catalog.py.

155def list_remote_manifests(profile: dict) -> list:
156 """!
157 @brief Enumerate completed archive manifests from the remote catalog.
158 @param[in] profile Value supplied through the `profile` argument.
159 @return Result produced by this operation.
160 """
161 root = _remote_join(profile["remote"], REMOTE_OBJECTS_DIRECTORY)
162 result = _transport._run_rclone([
163 "lsf", root, "--recursive", "--files-only", "--include", f"*/{REMOTE_MANIFEST_FILENAME}"
164 ])
165 manifests = []
166 for relative in sorted(line.strip() for line in result.stdout.splitlines() if line.strip()):
167 archive_id = relative.split("/", 1)[0]
168 if not ARCHIVE_ID_PATTERN.fullmatch(archive_id):
169 continue
170 try:
171 manifests.append(_load_remote_manifest(profile, archive_id))
172 except StorageError:
173 continue
174 return manifests
175
176

◆ verify_remote_archive()

dict picurv_cli.storage.catalog.verify_remote_archive ( dict  profile,
str  archive_id 
)

Verify the completion marker and every stored chunk checksum.

Parameters
[in]profileValue supplied through the profile argument.
[in]archive_idValue supplied through the archive_id argument.
Returns
Result produced by this operation.

Definition at line 177 of file catalog.py.

177def verify_remote_archive(profile: dict, archive_id: str) -> dict:
178 """!
179 @brief Verify the completion marker and every stored chunk checksum.
180 @param[in] profile Value supplied through the `profile` argument.
181 @param[in] archive_id Value supplied through the `archive_id` argument.
182 @return Result produced by this operation.
183 """
184 manifest = _load_remote_manifest(profile, archive_id)
185 for chunk in manifest.get("chunks", []):
186 actual = _remote_sha256(_chunk_remote_path(profile, archive_id, chunk))
187 if actual != chunk.get("sha256"):
188 raise StorageError(
189 f"Archive {archive_id} chunk checksum mismatch: {chunk['name']} "
190 f"(expected {chunk.get('sha256')}, got {actual})."
191 )
192 return manifest
193
194

◆ resolve_workspace_archive_id()

str picurv_cli.storage.catalog.resolve_workspace_archive_id ( dict  profile,
str  workspace_id 
)

Find the newest complete workspace archive carrying one workspace identity.

Parameters
[in]profileResolved storage profile.
[in]workspace_idWorkspace identity recorded at archive time.
Returns
Archive id.
Exceptions
StorageErrorwhen no such archive exists.

Definition at line 195 of file catalog.py.

195def resolve_workspace_archive_id(profile: dict, workspace_id: str) -> str:
196 """!
197 @brief Find the newest complete workspace archive carrying one workspace identity.
198 @param[in] profile Resolved storage profile.
199 @param[in] workspace_id Workspace identity recorded at archive time.
200 @return Archive id.
201 @throws StorageError when no such archive exists.
202 """
203 matches = [
204 manifest for manifest in list_remote_manifests(profile)
205 if manifest.get("artifact_type") == "workspace"
206 and manifest.get("workspace_id") == workspace_id
207 ]
208 if not matches:
209 raise StorageError(f"No workspace archive found for identity {workspace_id!r}.")
210 return max(matches, key=lambda item: str(item.get("created_at", "")))["archive_id"]