160 lines
5.9 KiB
YAML
160 lines
5.9 KiB
YAML
# services/veles/migration-jobs/cassandra-artifact-export.yaml
|
|
# Scale to one only during the Veles -> Cassandra artifact copy, then return to
|
|
# zero after the Cassandra import job verifies the copied content.
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: veles-artifact-export-for-cassandra
|
|
namespace: veles
|
|
spec:
|
|
selector:
|
|
app: veles-artifact-export-for-cassandra
|
|
ports:
|
|
- name: http
|
|
port: 8765
|
|
targetPort: http
|
|
---
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: veles-artifact-export-for-cassandra
|
|
namespace: veles
|
|
spec:
|
|
replicas: 0
|
|
selector:
|
|
matchLabels:
|
|
app: veles-artifact-export-for-cassandra
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: veles-artifact-export-for-cassandra
|
|
spec:
|
|
automountServiceAccountToken: false
|
|
nodeSelector:
|
|
cassandra.bstein.dev/node-pool: oceanus
|
|
tolerations:
|
|
- key: veles.bstein.dev/simulation
|
|
operator: Equal
|
|
value: "true"
|
|
effect: NoSchedule
|
|
containers:
|
|
- name: exporter
|
|
image: python:3.12-alpine
|
|
imagePullPolicy: IfNotPresent
|
|
ports:
|
|
- name: http
|
|
containerPort: 8765
|
|
env:
|
|
- name: SOURCE_ROOT
|
|
value: /source
|
|
command: ["python", "-c"]
|
|
args:
|
|
- |
|
|
from __future__ import annotations
|
|
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
from pathlib import Path
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import tarfile
|
|
|
|
ROOT = Path(os.environ.get("SOURCE_ROOT", "/source")).resolve()
|
|
EXCLUDE_TOP = {"lost+found", ".cassandra-artifact-migration"}
|
|
|
|
def iter_files():
|
|
for path in sorted(ROOT.rglob("*")):
|
|
if not path.is_file():
|
|
continue
|
|
rel = path.relative_to(ROOT).as_posix()
|
|
if rel.split("/", 1)[0] in EXCLUDE_TOP:
|
|
continue
|
|
yield path, rel
|
|
|
|
def digest_file(path: Path) -> str:
|
|
digest = hashlib.sha256()
|
|
with path.open("rb") as handle:
|
|
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
|
digest.update(chunk)
|
|
return digest.hexdigest()
|
|
|
|
def build_manifest() -> dict[str, object]:
|
|
files = []
|
|
total_bytes = 0
|
|
for path, rel in iter_files():
|
|
size = path.stat().st_size
|
|
total_bytes += size
|
|
files.append({"path": rel, "size": size, "sha256": digest_file(path)})
|
|
manifest_text = "\n".join(
|
|
f"{item['sha256']} {item['size']} {item['path']}" for item in files
|
|
)
|
|
return {
|
|
"schema": "cassandra.artifact_manifest.v1",
|
|
"source": "veles-artifacts",
|
|
"file_count": len(files),
|
|
"total_bytes": total_bytes,
|
|
"manifest_sha256": hashlib.sha256(manifest_text.encode()).hexdigest(),
|
|
"files": files,
|
|
}
|
|
|
|
MANIFEST = build_manifest()
|
|
MANIFEST_PATHS = [str(item["path"]) for item in MANIFEST["files"]]
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
def do_GET(self) -> None:
|
|
if self.path == "/healthz":
|
|
self.send_response(200)
|
|
self.end_headers()
|
|
self.wfile.write(b"ok\n")
|
|
return
|
|
if self.path == "/manifest.json":
|
|
payload = json.dumps(MANIFEST, sort_keys=True).encode()
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "application/json")
|
|
self.send_header("Content-Length", str(len(payload)))
|
|
self.end_headers()
|
|
self.wfile.write(payload)
|
|
return
|
|
if self.path == "/archive.tar":
|
|
self.send_response(200)
|
|
self.send_header("Content-Type", "application/x-tar")
|
|
self.end_headers()
|
|
with tarfile.open(fileobj=self.wfile, mode="w|") as archive:
|
|
for rel in MANIFEST_PATHS:
|
|
path = ROOT / rel
|
|
if not path.is_file():
|
|
raise FileNotFoundError(rel)
|
|
with path.open("rb") as handle:
|
|
info = archive.gettarinfo(str(path), arcname=rel)
|
|
archive.addfile(info, handle)
|
|
return
|
|
self.send_response(404)
|
|
self.end_headers()
|
|
|
|
def log_message(self, fmt: str, *args: object) -> None:
|
|
print(fmt % args, flush=True)
|
|
|
|
print(
|
|
"ready file_count={file_count} total_bytes={total_bytes} manifest={manifest_sha256}".format(
|
|
**MANIFEST
|
|
),
|
|
flush=True,
|
|
)
|
|
ThreadingHTTPServer(("0.0.0.0", 8765), Handler).serve_forever()
|
|
resources:
|
|
requests:
|
|
cpu: 250m
|
|
memory: 512Mi
|
|
limits:
|
|
cpu: "2"
|
|
memory: 2Gi
|
|
volumeMounts:
|
|
- name: artifacts
|
|
mountPath: /source
|
|
readOnly: true
|
|
volumes:
|
|
- name: artifacts
|
|
persistentVolumeClaim:
|
|
claimName: veles-artifacts
|
|
readOnly: true
|