Skip to content

Commit

Permalink
job-manager
Browse files Browse the repository at this point in the history
  • Loading branch information
magland committed Feb 19, 2025
1 parent d9f2c63 commit 628af79
Show file tree
Hide file tree
Showing 31 changed files with 2,783 additions and 16 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/docker-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Build and Push Docker Image

on:
push:
branches: [ main-v2 ]
paths:
- 'job-manager/job-runner/**'
workflow_dispatch:

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}-job-runner

jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Log in to the Container registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}

- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: job-manager/job-runner
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changes

## February 18, 2025
- Added distributed job processing system (neurosift-job-manager)
- Added support for embedded=1 query parameter
- Added feedback/issues link on home page footer
- Migration to v2 deployed
Expand Down
8 changes: 8 additions & 0 deletions job-manager/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# MongoDB connection string
MONGODB_URI=mongodb://localhost:27017/neurosift_jobs

# API Keys for authentication
API_SUBMIT_KEY=your-submit-key-here

# Memobin API Key for file storage
MEMOBIN_API_KEY=your-memobin-key-here
55 changes: 55 additions & 0 deletions job-manager/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/
/build/

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
ENV/
.env
*.egg-info/
dist/
build/

# IDE
.idea/
.vscode/
*.swp
*.swo

# OS
.DS_Store
Thumbs.db
55 changes: 55 additions & 0 deletions job-manager/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Neurosift Job Manager

A distributed job processing system that allows the Neurosift UI to submit processing jobs that are executed by remote compute clients.

## Local Development Setup

1. Prerequisites:
- Docker with Docker Compose plugin (not the standalone docker-compose)
- Node.js and npm
- Python 3.x

To install Docker Compose plugin:
```bash
# For Debian/Ubuntu
sudo apt-get update
sudo apt-get install docker-compose-plugin

# For macOS with Homebrew
brew install docker-compose
```

2. Run the development setup script:
```bash
./dev-setup.sh
```
This script will:
- Start MongoDB in a Docker container
- Create a `.env` file
- Install npm dependencies

3. In a separate terminal, start the service:

```bash
npm run dev
```

4. The following services will be available:
- MongoDB: mongodb://localhost:27017/neurosift_jobs
- API Server: http://localhost:3000/api

To stop the services:
1. Stop the API server Ctrl+C in their terminal
2. Stop MongoDB: `docker compose down`

## Running a job

1. Install Python dependencies:
```bash
pip install -r requirements.txt
```

2. Start the compute client:
```bash
./run-job.py <JOB_ID>
```
146 changes: 146 additions & 0 deletions job-manager/app/api/jobs/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/**
* API Routes for Individual Job Operations
*
* This module provides endpoints for retrieving and updating specific jobs
* using their unique identifier. Supports both job status checks and
* progress updates.
*/

import { NextRequest, NextResponse } from 'next/server';
import connectDB, { Job, IJob } from '../../../../lib/db';
import { validateJobState } from '../../../../middleware/auth';
import { UpdateQuery } from 'mongoose';

/**
* Handle CORS preflight requests
* Enables cross-origin requests from the development server
*
* @returns Response with appropriate CORS headers
*/
export async function OPTIONS() {
return new NextResponse(null, {
status: 200,
headers: {
'Access-Control-Allow-Origin': 'http://localhost:5173',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, PATCH, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
}
});
}

/**
* Retrieve details of a specific job
*
* @param request NextRequest - The incoming request
* @param params.id string - The unique identifier of the job
* @returns
* - Success: Job object with all details
* - Error: 404 if job not found, 500 for server errors
*
* No API key required - job ID is used as authentication
*/
export async function GET(
request: NextRequest,
{ params }: { params: { id: string } }
) {
try {
await connectDB();
const job = await Job.findById(params.id);

// Simple not found check is sufficient since job ID is considered secret
if (!job) {
return new NextResponse('Job not found', { status: 404 });
}

return NextResponse.json(job);
} catch (error) {
console.error('Error fetching job:', error);
return new NextResponse('Internal Server Error', { status: 500 });
}
}

/**
* Update the status and progress of a specific job
*
* @param request NextRequest containing any of:
* - status: 'pending' | 'running' | 'completed' | 'failed'
* - progress: number (0-100)
* - output: job output data
* - error: error information if job failed
* @param params.id string - The unique identifier of the job
* @returns
* - Success: Updated job object
* - Error: 404 if job not found, 500 for server errors
*
* Required API key: 'fulfill'
*
* Only specified fields will be updated, maintaining data integrity
*/
export async function PATCH(
request: NextRequest,
{ params }: { params: { id: string } }
) {
// No API key needed for job updates - we use job ID as authentication

try {
const body = await request.json();
const { status, progress, output, error } = body;

await connectDB();
const job = await Job.findById(params.id);

// Validate job exists and state transition is valid
const validationError = await validateJobState(job, status);
if (validationError) return validationError;

// Only allow updates to specific fields based on current state
const updates: UpdateQuery<IJob> = {};

if (status) {
updates.$set = updates.$set || {};
updates.$set.status = status;
}

// Only allow progress updates for running jobs
if (typeof progress === 'number') {
if (job.status !== 'running' && status !== 'running') {
return new NextResponse('Progress can only be updated for running jobs', { status: 400 });
}
if (progress < 0 || progress > 100) {
return new NextResponse('Progress must be between 0 and 100', { status: 400 });
}
updates.$set = updates.$set || {};
updates.$set.progress = progress;
}

// Only allow output updates for running or completed jobs
if (output) {
if (!['running', 'completed'].includes(job.status) &&
!(status && ['running', 'completed'].includes(status))) {
return new NextResponse('Output can only be set for running or completed jobs', { status: 400 });
}
updates.$set = updates.$set || {};
updates.$set.output = output;
}

// Only allow error updates when transitioning to failed status
if (error) {
if (status !== 'failed') {
return new NextResponse('Error can only be set when status is being set to failed', { status: 400 });
}
updates.$set = updates.$set || {};
updates.$set.error = error;
}

const updatedJob = await Job.findByIdAndUpdate(
params.id,
updates,
{ new: true, runValidators: true }
);

return NextResponse.json(updatedJob);
} catch (error) {
console.error('Error updating job:', error);
return new NextResponse('Internal Server Error', { status: 500 });
}
}
Loading

0 comments on commit 628af79

Please sign in to comment.