235 lines
6.4 KiB
HTTP
235 lines
6.4 KiB
HTTP
# SciMesh Coordinator — API requests
|
|
#
|
|
# Runnable from any editor with a REST client (VSCodium/VS Code "REST Client",
|
|
# JetBrains HTTP Client). Click "Send Request" above each block, top to bottom:
|
|
# later requests reuse ids captured from earlier responses.
|
|
#
|
|
# Start the stack first: docker compose up -d
|
|
|
|
@host = http://localhost:8080
|
|
@token = change-me
|
|
@worker = worker-1
|
|
|
|
### Readiness — the only unauthenticated endpoint (probes the database)
|
|
GET {{host}}/health
|
|
|
|
### Auth check — no token must be rejected with 401
|
|
POST {{host}}/tasks/claim
|
|
Content-Type: application/json
|
|
|
|
{ "worker_id": "{{worker}}" }
|
|
|
|
### 0. Register a worker (201)
|
|
# @name register
|
|
POST {{host}}/workers/register
|
|
Authorization: Bearer {{token}}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"name": "lab-worker-01",
|
|
"capabilities": ["similarity_search"],
|
|
"cpu_count": 8,
|
|
"memory_mb": 16384
|
|
}
|
|
|
|
@workerId = {{register.response.body.worker_id}}
|
|
|
|
### 0b. Upload a dataset — the coordinator splits it into shard tasks (201)
|
|
# Text fields first, the file part last (it is streamed, not buffered).
|
|
# @name uploadJob
|
|
POST {{host}}/jobs/upload
|
|
Authorization: Bearer {{token}}
|
|
Content-Type: multipart/form-data; boundary=----scimesh
|
|
|
|
------scimesh
|
|
Content-Disposition: form-data; name="workload"
|
|
|
|
similarity_search
|
|
------scimesh
|
|
Content-Disposition: form-data; name="parameters"
|
|
|
|
{"top_k":10}
|
|
------scimesh
|
|
Content-Disposition: form-data; name="chunk_rows"
|
|
|
|
2
|
|
------scimesh
|
|
Content-Disposition: form-data; name="file"; filename="chembl.tsv"
|
|
Content-Type: text/tab-separated-values
|
|
|
|
id smiles
|
|
A CC
|
|
B CCC
|
|
C CCCC
|
|
D CCCCC
|
|
------scimesh--
|
|
|
|
### Download a task's input shard (200) — taskId must be a shard task from an
|
|
### uploaded job (claim one first; its input.uri is /tasks/{id}/input).
|
|
GET {{host}}/tasks/{{taskId}}/input
|
|
Authorization: Bearer {{token}}
|
|
|
|
### 1. Create a job and its chunks (201)
|
|
# The coordinator splits the submission into one task per chunk, transactionally.
|
|
# @name createJob
|
|
POST {{host}}/jobs
|
|
Authorization: Bearer {{token}}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"workload": "similarity_search",
|
|
"input_uri": "s3://chembl/full.sdf",
|
|
"parameters": { "top_k": 10 },
|
|
"chunks": [
|
|
{ "chunk_index": 0, "input_uri": "s3://chembl/shard-0.sdf", "input_sha256": "aaa", "max_attempts": 3 },
|
|
{ "chunk_index": 1, "input_uri": "s3://chembl/shard-1.sdf", "input_sha256": "bbb", "max_attempts": 3 },
|
|
{ "chunk_index": 2, "input_uri": "s3://chembl/shard-2.sdf", "input_sha256": "ccc", "max_attempts": 3 }
|
|
]
|
|
}
|
|
|
|
@jobId = {{createJob.response.body.id}}
|
|
|
|
### 2. Claim a task (200, or 204 when the queue is empty)
|
|
# Each call leases a different task; run it repeatedly to see chunk_index advance.
|
|
# @name claim
|
|
POST {{host}}/tasks/claim
|
|
Authorization: Bearer {{token}}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"worker_id": "{{worker}}",
|
|
"capabilities": ["similarity_search"],
|
|
"max_concurrency": 1
|
|
}
|
|
|
|
@taskId = {{claim.response.body.task_id}}
|
|
@attempt = {{claim.response.body.attempt}}
|
|
|
|
### 3. Heartbeat — renew the lease while the task is still running (200)
|
|
POST {{host}}/tasks/{{taskId}}/heartbeat
|
|
Authorization: Bearer {{token}}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"worker_id": "{{worker}}",
|
|
"attempt": {{attempt}}
|
|
}
|
|
|
|
### 3a. Upload a partial-result artifact (200) — while the task is leased
|
|
# Identity travels in headers per the contract; the body is streamed as-is.
|
|
# @name uploadArtifact
|
|
PUT {{host}}/tasks/{{taskId}}/artifacts/result.csv
|
|
Authorization: Bearer {{token}}
|
|
Content-Type: text/csv
|
|
X-Worker-ID: {{worker}}
|
|
X-Task-Attempt: {{attempt}}
|
|
|
|
query,match,score
|
|
CHEMBL25,CHEMBL139,0.87
|
|
|
|
@artifactId = {{uploadArtifact.response.body.artifact_id}}
|
|
|
|
### 3b. Download the artifact by id (200)
|
|
GET {{host}}/artifacts/{{artifactId}}/download
|
|
Authorization: Bearer {{token}}
|
|
|
|
### 3c. Upload a second artifact — used by the conflict check below (200)
|
|
# @name uploadArtifact2
|
|
PUT {{host}}/tasks/{{taskId}}/artifacts/secondary.csv
|
|
Authorization: Bearer {{token}}
|
|
Content-Type: text/csv
|
|
X-Worker-ID: {{worker}}
|
|
X-Task-Attempt: {{attempt}}
|
|
|
|
query,match,score
|
|
CHEMBL25,CHEMBL521,0.42
|
|
|
|
@artifactId2 = {{uploadArtifact2.response.body.artifact_id}}
|
|
|
|
### 4. Submit the result, referencing the uploaded artifact (200)
|
|
POST {{host}}/tasks/{{taskId}}/result
|
|
Authorization: Bearer {{token}}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"worker_id": "{{worker}}",
|
|
"attempt": {{attempt}},
|
|
"result": { "artifact_id": "{{artifactId}}", "content_type": "text/csv" },
|
|
"metrics": { "elapsed_ms": 1234, "candidates": 50000 }
|
|
}
|
|
|
|
### 4a. Replay the same result — must be idempotent (200, not 409)
|
|
POST {{host}}/tasks/{{taskId}}/result
|
|
Authorization: Bearer {{token}}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"worker_id": "{{worker}}",
|
|
"attempt": {{attempt}},
|
|
"result": { "artifact_id": "{{artifactId}}" }
|
|
}
|
|
|
|
### 4b. A different artifact for the same task — conflict (409)
|
|
POST {{host}}/tasks/{{taskId}}/result
|
|
Authorization: Bearer {{token}}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"worker_id": "{{worker}}",
|
|
"attempt": {{attempt}},
|
|
"result": { "artifact_id": "{{artifactId2}}" }
|
|
}
|
|
|
|
### 4c. Another worker submitting for this task — conflict (409)
|
|
POST {{host}}/tasks/{{taskId}}/result
|
|
Authorization: Bearer {{token}}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"worker_id": "impostor",
|
|
"attempt": {{attempt}},
|
|
"result": { "artifact_id": "{{artifactId}}" }
|
|
}
|
|
|
|
### 5. Report a failure instead (200)
|
|
# retryable=true returns the task to the queue while attempts remain;
|
|
# retryable=false fails it terminally.
|
|
POST {{host}}/tasks/{{taskId}}/failure
|
|
Authorization: Bearer {{token}}
|
|
Content-Type: application/json
|
|
|
|
{
|
|
"worker_id": "{{worker}}",
|
|
"attempt": {{attempt}},
|
|
"error_code": "download_failed",
|
|
"error_message": "checksum mismatch on shard",
|
|
"retryable": true
|
|
}
|
|
|
|
### 6. Job progress (200)
|
|
GET {{host}}/jobs/{{jobId}}
|
|
Authorization: Bearer {{token}}
|
|
|
|
### --- error cases -------------------------------------------------------
|
|
|
|
### Malformed UUID in the path (400)
|
|
POST {{host}}/tasks/not-a-uuid/result
|
|
Authorization: Bearer {{token}}
|
|
Content-Type: application/json
|
|
|
|
{ "worker_id": "{{worker}}", "attempt": 1, "result_uri": "s3://x", "result_sha256": "x" }
|
|
|
|
### Unknown field in the body (400) — a misspelled key must not pass silently
|
|
POST {{host}}/tasks/claim
|
|
Authorization: Bearer {{token}}
|
|
Content-Type: application/json
|
|
|
|
{ "worker_ID": "{{worker}}" }
|
|
|
|
### Unknown job (404)
|
|
GET {{host}}/jobs/00000000-0000-0000-0000-000000000000
|
|
Authorization: Bearer {{token}}
|
|
|
|
### Stitching is not implemented yet (501)
|
|
# Any endpoint whose use case is still a stub answers 501.
|