chore: record pre-existing uncommitted repository state
Snapshot of the in-flight state that already existed, identically, in both this worktree and the main checkout before this session began: the initial HTTP Client platform implementation (previously untracked), the redis-lab removal, and the JPA / object-storage / notification integration work. Kept separate from this session's HTTP Client review response, which lands in the following commit, so the two bodies of work stay reviewable apart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1a3b560678
commit
5f10b791d3
@@ -0,0 +1,127 @@
|
||||
# Storage certification job.
|
||||
#
|
||||
# A PersistentVolumeClaim is not a filesystem contract. Whether an atomic rename, a same-file-store
|
||||
# guarantee, or symlink refusal actually holds depends on the CSI driver, the StorageClass, the
|
||||
# access mode, the backend, and the mount options — so this job records all five alongside the probe
|
||||
# result. A certification without that tuple is not transferable to another cluster.
|
||||
#
|
||||
# The job writes a machine-readable result to the claim itself so the evidence lives with the volume
|
||||
# it describes.
|
||||
#
|
||||
# kubectl apply -f infra/fileserver/kubernetes/pvc-certification-job.yaml
|
||||
# kubectl logs job/fileserver-pvc-certification
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: fileserver-certification
|
||||
labels:
|
||||
app.kubernetes.io/name: fileserver
|
||||
app.kubernetes.io/component: certification
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
# Left unset on purpose: the certification is only meaningful for the class it actually ran on,
|
||||
# so the operator names it explicitly rather than inheriting a cluster default.
|
||||
storageClassName: ""
|
||||
---
|
||||
apiVersion: batch/v1
|
||||
kind: Job
|
||||
metadata:
|
||||
name: fileserver-pvc-certification
|
||||
labels:
|
||||
app.kubernetes.io/name: fileserver
|
||||
app.kubernetes.io/component: certification
|
||||
spec:
|
||||
backoffLimit: 0
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: fileserver
|
||||
app.kubernetes.io/component: certification
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
runAsUser: 10001
|
||||
fsGroup: 10001
|
||||
containers:
|
||||
- name: certify
|
||||
image: eclipse-temurin:21-jdk
|
||||
env:
|
||||
- name: FILESERVER_STORAGE_ROOT
|
||||
value: /var/lib/backend/files
|
||||
- name: KUBERNETES_VERSION
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.annotations['certification.fileserver/kubernetes-version']
|
||||
- name: CSI_DRIVER
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.annotations['certification.fileserver/csi-driver']
|
||||
- name: STORAGE_CLASS
|
||||
valueFrom:
|
||||
fieldRef:
|
||||
fieldPath: metadata.annotations['certification.fileserver/storage-class']
|
||||
- name: ACCESS_MODE
|
||||
value: ReadWriteOnce
|
||||
command:
|
||||
- /bin/bash
|
||||
- -c
|
||||
- |
|
||||
set -euo pipefail
|
||||
ROOT="${FILESERVER_STORAGE_ROOT}"
|
||||
mkdir -p "${ROOT}/staging" "${ROOT}/content"
|
||||
|
||||
# Atomic rename within one file store is the property the publish path depends on.
|
||||
echo probe > "${ROOT}/staging/probe"
|
||||
if mv "${ROOT}/staging/probe" "${ROOT}/content/probe" 2>/dev/null; then
|
||||
ATOMIC_MOVE=true
|
||||
else
|
||||
ATOMIC_MOVE=false
|
||||
fi
|
||||
|
||||
# Same device means a rename is a metadata operation rather than a copy.
|
||||
STAGING_DEV=$(stat -c %d "${ROOT}/staging")
|
||||
CONTENT_DEV=$(stat -c %d "${ROOT}/content")
|
||||
[ "${STAGING_DEV}" = "${CONTENT_DEV}" ] && SAME_STORE=true || SAME_STORE=false
|
||||
|
||||
# O_EXCL create is what makes a publish create-only rather than an overwrite.
|
||||
if (set -o noclobber; echo x > "${ROOT}/content/excl") 2>/dev/null; then
|
||||
ATOMIC_CREATE=true
|
||||
else
|
||||
ATOMIC_CREATE=false
|
||||
fi
|
||||
|
||||
cat > "${ROOT}/certification-result.json" <<RESULT
|
||||
{
|
||||
"kubernetesVersion": "${KUBERNETES_VERSION:-unknown}",
|
||||
"csiDriver": "${CSI_DRIVER:-unknown}",
|
||||
"storageClass": "${STORAGE_CLASS:-unknown}",
|
||||
"accessMode": "${ACCESS_MODE}",
|
||||
"backend": "$(stat -f -c %T "${ROOT}")",
|
||||
"mountOptions": "$(findmnt -no OPTIONS --target "${ROOT}" || echo unknown)",
|
||||
"atomicMove": ${ATOMIC_MOVE},
|
||||
"sameFileStore": ${SAME_STORE},
|
||||
"atomicCreate": ${ATOMIC_CREATE}
|
||||
}
|
||||
RESULT
|
||||
cat "${ROOT}/certification-result.json"
|
||||
|
||||
# Fail closed: a volume that cannot publish atomically must not be certified silently.
|
||||
[ "${SAME_STORE}" = "true" ] || { echo "staging and content are on different stores"; exit 1; }
|
||||
volumeMounts:
|
||||
- name: storage
|
||||
mountPath: /var/lib/backend/files
|
||||
securityContext:
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop: ["ALL"]
|
||||
volumes:
|
||||
- name: storage
|
||||
persistentVolumeClaim:
|
||||
claimName: fileserver-certification
|
||||
@@ -0,0 +1,61 @@
|
||||
# Network-filesystem certification environment.
|
||||
#
|
||||
# A local filesystem cannot reproduce the failures this environment exists to test: a rename whose
|
||||
# acknowledgement is lost, a stale file handle after the server restarts, and a client that keeps
|
||||
# writing across a network cut. Those are precisely the cases where "the write failed, retry it" is
|
||||
# the wrong conclusion, so they are certified against a real NFS server rather than a mock.
|
||||
#
|
||||
# Opt in with FILESERVER_NFS_TESTS=true; the default test run does not start this.
|
||||
#
|
||||
# docker compose -f infra/fileserver/nfs/compose.yml up -d
|
||||
# FILESERVER_NFS_TESTS=true ./gradlew :adapter:outbound:fileserver:test
|
||||
#
|
||||
# To exercise the ambiguity paths:
|
||||
# docker compose -f infra/fileserver/nfs/compose.yml restart nfs-server # stale handles
|
||||
# docker network disconnect fileserver-nfs <client> # lost responses
|
||||
|
||||
services:
|
||||
nfs-server:
|
||||
image: erichough/nfs-server:2.2.1
|
||||
container_name: fileserver-nfs-server
|
||||
privileged: true
|
||||
environment:
|
||||
NFS_EXPORT_0: "/exports *(rw,sync,no_subtree_check,no_root_squash,fsid=0)"
|
||||
NFS_VERSION: "4.2"
|
||||
NFS_LOG_LEVEL: DEBUG
|
||||
volumes:
|
||||
- nfs-exports:/exports
|
||||
ports:
|
||||
- "2049:2049"
|
||||
networks:
|
||||
- fileserver-nfs
|
||||
healthcheck:
|
||||
test: ["CMD", "rpcinfo", "-t", "localhost", "nfs", "4"]
|
||||
interval: 5s
|
||||
timeout: 3s
|
||||
retries: 10
|
||||
|
||||
nfs-client:
|
||||
image: eclipse-temurin:21-jdk
|
||||
container_name: fileserver-nfs-client
|
||||
privileged: true
|
||||
depends_on:
|
||||
nfs-server:
|
||||
condition: service_healthy
|
||||
# hard,intr is the correct production mount: a soft mount turns a slow server into a silent
|
||||
# short write, which is exactly the corruption the design refuses to accept.
|
||||
command: >
|
||||
bash -c "mkdir -p /mnt/fileserver &&
|
||||
mount -t nfs4 -o hard,timeo=50,retrans=2 nfs-server:/ /mnt/fileserver &&
|
||||
tail -f /dev/null"
|
||||
volumes:
|
||||
- ../../..:/workspace:ro
|
||||
networks:
|
||||
- fileserver-nfs
|
||||
|
||||
volumes:
|
||||
nfs-exports:
|
||||
|
||||
networks:
|
||||
fileserver-nfs:
|
||||
name: fileserver-nfs
|
||||
@@ -0,0 +1,67 @@
|
||||
# Fileserver front-proxy configuration.
|
||||
#
|
||||
# The application authorizes every download and then hands the transfer to Nginx with
|
||||
# X-Accel-Redirect. Two properties make that safe, and both are enforced here rather than assumed:
|
||||
#
|
||||
# 1. /__files/ is `internal`, so it is reachable ONLY through an internal redirect the application
|
||||
# issued. A direct request from a client returns 404 and never touches the content root.
|
||||
# 2. The application never emits an absolute path. It emits a relative URI below /__files/, and
|
||||
# the alias below is the only place that prefix becomes a filesystem location.
|
||||
#
|
||||
# Keep `alias` in sync with the storage root's content directory. A mismatch is a startup
|
||||
# misconfiguration, not a runtime fallback: the application's startup validator checks that the
|
||||
# internal mapping was proven before it accepts traffic.
|
||||
|
||||
worker_processes auto;
|
||||
|
||||
events {
|
||||
worker_connections 4096;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
sendfile on;
|
||||
sendfile_max_chunk 2m;
|
||||
tcp_nopush on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
# Uploads stream through to the application; buffering a large body to disk here would double
|
||||
# the write and defeat the streaming upload path.
|
||||
proxy_request_buffering off;
|
||||
client_max_body_size 0;
|
||||
|
||||
server {
|
||||
listen 8080;
|
||||
|
||||
# Public API. Everything, including download authorization, is decided by the application.
|
||||
location / {
|
||||
proxy_pass http://app:8081;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
||||
# The application must never see a client-supplied delegation header: it would let a
|
||||
# caller name an arbitrary internal object.
|
||||
proxy_set_header X-Accel-Redirect "";
|
||||
}
|
||||
|
||||
# Internal transfer location. Not reachable from outside; see property (1) above.
|
||||
location /__files/ {
|
||||
internal;
|
||||
alias /srv/files/content/;
|
||||
|
||||
sendfile on;
|
||||
sendfile_max_chunk 2m;
|
||||
|
||||
# Uploaded content is never trusted to describe itself.
|
||||
add_header X-Content-Type-Options nosniff always;
|
||||
add_header Content-Disposition $upstream_http_content_disposition always;
|
||||
add_header Cache-Control $upstream_http_cache_control always;
|
||||
add_header ETag $upstream_http_etag always;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user