update example and execution
This commit is contained in:
@@ -33,6 +33,7 @@ if help_requested {
|
|||||||
mut r := &runner.Runner{
|
mut r := &runner.Runner{
|
||||||
name: runner_name
|
name: runner_name
|
||||||
namespace: namespace
|
namespace: namespace
|
||||||
|
client: &runner.Client{
|
||||||
redis_conn: redisclient.new(
|
redis_conn: redisclient.new(
|
||||||
if redis_url.len > 0 {
|
if redis_url.len > 0 {
|
||||||
redis_url
|
redis_url
|
||||||
@@ -40,6 +41,7 @@ mut r := &runner.Runner{
|
|||||||
'127.0.0.1:6379'
|
'127.0.0.1:6379'
|
||||||
}
|
}
|
||||||
)!
|
)!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spawn r.run()
|
spawn r.run()
|
||||||
|
BIN
examples/runner
BIN
examples/runner
Binary file not shown.
@@ -1,34 +1,39 @@
|
|||||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
||||||
|
|
||||||
import time
|
import time
|
||||||
import freeflowuniverse.herolib.baobab.runner
|
import herocode.runner
|
||||||
import freeflowuniverse.herolib.core.redisclient
|
import freeflowuniverse.herolib.core.redisclient
|
||||||
|
|
||||||
const namespace = ''
|
const namespace = 'example'
|
||||||
|
|
||||||
mut r := &runner.Runner{
|
mut r := &runner.Runner{
|
||||||
name: 'test_runner'
|
name: 'example_runner'
|
||||||
|
namespace: namespace
|
||||||
|
client: &runner.Client{
|
||||||
redis_conn: redisclient.new('127.0.0.1:6379')!
|
redis_conn: redisclient.new('127.0.0.1:6379')!
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spawn r.run()
|
spawn r.run()
|
||||||
|
|
||||||
// job := runner.Job{
|
job := runner.Job{
|
||||||
// id: 'test_job_1'
|
id: 'test_job_1'
|
||||||
// caller_id: 'test_caller'
|
caller_id: 'test_caller'
|
||||||
// context_id: 'test_context_1'
|
context_id: 'test_context_1'
|
||||||
// runner: 'test_runner'
|
runner: 'test_runner'
|
||||||
// executor: 'tmux.session1'
|
executor: 'tmux.session1'
|
||||||
// payload: 'sleep 10\necho "Hello from job 1"\nsleep 10'
|
payload: '!!git.list'
|
||||||
// status: .dispatched
|
status: .dispatched
|
||||||
// timeout: 30
|
timeout: 30
|
||||||
// created_at: time.now()
|
created_at: time.now()
|
||||||
// updated_at: time.now()
|
updated_at: time.now()
|
||||||
// }
|
}
|
||||||
|
|
||||||
// mut redis_conn := redisclient.new('127.0.0.1:6379')!
|
mut client := runner.Client{
|
||||||
// job.store_in_redis(mut redis_conn, namespace)!
|
redis_conn: redisclient.new('127.0.0.1:6379')!
|
||||||
// mut runner_q := redis_conn.queue_get(r.queue_key()!)
|
}
|
||||||
// runner_q.add(job.id)!
|
client.store_in_redis(job.id, job)!
|
||||||
|
mut runner_q := client.redis_conn.queue_get(r.queue_key()!)
|
||||||
|
runner_q.add(job.id)!
|
||||||
|
|
||||||
for {}
|
for {}
|
@@ -1,107 +0,0 @@
|
|||||||
#!/usr/bin/env -S v -n -w -cg -gc none -cc tcc -d use_openssl -enable-globals run
|
|
||||||
|
|
||||||
import freeflowuniverse.herolib.core.redisclient
|
|
||||||
import freeflowuniverse.herolib.baobab.runner
|
|
||||||
import time
|
|
||||||
import log
|
|
||||||
|
|
||||||
// Test script to send RPC calls over Redis to test the runner
|
|
||||||
fn main() {
|
|
||||||
log.info('Starting runner RPC test script')
|
|
||||||
|
|
||||||
// Connect to Redis
|
|
||||||
mut redis := redisclient.new('127.0.0.1:6379')!
|
|
||||||
|
|
||||||
// Test runner configuration
|
|
||||||
runner_name := 'test_runner'
|
|
||||||
namespace := ''
|
|
||||||
|
|
||||||
// Create test jobs
|
|
||||||
test_jobs := [
|
|
||||||
runner.Job{
|
|
||||||
id: 'test_job_1'
|
|
||||||
caller_id: 'test_caller'
|
|
||||||
context_id: 'test_context_1'
|
|
||||||
runner: runner_name
|
|
||||||
executor: 'tmux.session1'
|
|
||||||
payload: 'echo "Hello from job 1"'
|
|
||||||
status: .dispatched
|
|
||||||
timeout: 30
|
|
||||||
created_at: time.now()
|
|
||||||
updated_at: time.now()
|
|
||||||
},
|
|
||||||
runner.Job{
|
|
||||||
id: 'test_job_2'
|
|
||||||
caller_id: 'test_caller'
|
|
||||||
context_id: 'test_context_2'
|
|
||||||
runner: runner_name
|
|
||||||
executor: 'tmux.session2.window1'
|
|
||||||
payload: 'ls -la && echo "Job 2 completed"'
|
|
||||||
status: .dispatched
|
|
||||||
timeout: 30
|
|
||||||
created_at: time.now()
|
|
||||||
updated_at: time.now()
|
|
||||||
},
|
|
||||||
runner.Job{
|
|
||||||
id: 'test_job_3'
|
|
||||||
caller_id: 'test_caller'
|
|
||||||
context_id: 'test_context_3'
|
|
||||||
runner: runner_name
|
|
||||||
executor: 'tmux.session3.window1.pane1'
|
|
||||||
payload: 'date && echo "Current time from job 3"'
|
|
||||||
status: .dispatched
|
|
||||||
timeout: 30
|
|
||||||
created_at: time.now()
|
|
||||||
updated_at: time.now()
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
log.info('Storing ${test_jobs.len} test jobs in Redis and dispatching to runner queue')
|
|
||||||
|
|
||||||
// Store jobs in Redis and dispatch them to the runner queue
|
|
||||||
for job in test_jobs {
|
|
||||||
// Store job data in Redis
|
|
||||||
job.store_in_redis(mut redis, namespace) or {
|
|
||||||
log.error('Failed to store job ${job.id}: ${err}')
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dispatch job to runner queue by pushing job ID to the queue
|
|
||||||
queue_key := if namespace.len > 0 {
|
|
||||||
"${namespace}:runner:${runner_name}"
|
|
||||||
} else {
|
|
||||||
"runner:${runner_name}"
|
|
||||||
}
|
|
||||||
|
|
||||||
redis.rpush(queue_key, job.id) or {
|
|
||||||
log.error('Failed to dispatch job ${job.id} to queue ${queue_key}: ${err}')
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info('Dispatched job ${job.id} to queue ${queue_key}')
|
|
||||||
|
|
||||||
// Small delay between jobs
|
|
||||||
time.sleep(1 * time.second)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info('All test jobs dispatched. Monitoring job status...')
|
|
||||||
|
|
||||||
// Monitor job status for a while
|
|
||||||
for i in 0..30 { // Monitor for 30 seconds
|
|
||||||
log.info('--- Status check ${i + 1} ---')
|
|
||||||
|
|
||||||
for job in test_jobs {
|
|
||||||
loaded_job := runner.load_from_redis(mut redis, job.id, namespace) or {
|
|
||||||
log.error('Failed to load job ${job.id}: ${err}')
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info('Job ${loaded_job.id}: status=${loaded_job.status}, result="${loaded_job.result}", error="${loaded_job.error}"')
|
|
||||||
}
|
|
||||||
|
|
||||||
time.sleep(1 * time.second)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.info('Test completed. Check tmux sessions to see if commands were executed.')
|
|
||||||
log.info('You can run "tmux list-sessions" to see created sessions.')
|
|
||||||
}
|
|
126
src/client.v
Normal file
126
src/client.v
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
module runner
|
||||||
|
|
||||||
|
import rand
|
||||||
|
import time
|
||||||
|
import json
|
||||||
|
import freeflowuniverse.herolib.core.redisclient
|
||||||
|
|
||||||
|
pub struct Client {
|
||||||
|
namespace string
|
||||||
|
pub mut:
|
||||||
|
redis_conn &redisclient.Redis
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (client Client) job_key(job_id string) ! string {
|
||||||
|
return if client.namespace.len > 0 {
|
||||||
|
'${client.namespace}:job:${job_id}'
|
||||||
|
} else {
|
||||||
|
'job:${job_id}'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (client Client) jobs_key() ! string {
|
||||||
|
return if client.namespace.len > 0 {
|
||||||
|
'${client.namespace}:job'
|
||||||
|
} else {
|
||||||
|
'job'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store this job in Redis
|
||||||
|
pub fn (mut client Client) store_in_redis(job_id string, job Job) ! {
|
||||||
|
job_key := client.job_key(job_id)!
|
||||||
|
|
||||||
|
// Store job as JSON
|
||||||
|
job_json := json.encode(job)
|
||||||
|
client.redis_conn.hset(job_key, 'data', job_json) or { return error('Failed to store job ${job.id}: ${err}') }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load a job from Redis by ID
|
||||||
|
pub fn (mut client Client) load_from_redis(job_id string) !Job {
|
||||||
|
job_key := client.job_key(job_id)!
|
||||||
|
|
||||||
|
// Get job JSON from Redis
|
||||||
|
job_json := client.redis_conn.hget(job_key, 'data') or {
|
||||||
|
return error('Failed to load job ${job_id} from Redis: ${err}')
|
||||||
|
}
|
||||||
|
|
||||||
|
if job_json.len == 0 {
|
||||||
|
return error('Job ${job_id} not found in Redis')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse job from JSON
|
||||||
|
job := json.decode(Job, job_json) or {
|
||||||
|
return error('Failed to decode job ${job_id} JSON: ${err}')
|
||||||
|
}
|
||||||
|
|
||||||
|
return job
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update job status in Redis
|
||||||
|
pub fn (mut client Client) update_job_status(job_id string, status JobStatus) ! {
|
||||||
|
// Load job, update status, and store back
|
||||||
|
mut job := client.load_from_redis(job_id)!
|
||||||
|
job.status = status
|
||||||
|
job.updated_at = time.now()
|
||||||
|
client.store_in_redis(job_id, job)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get job status from Redis
|
||||||
|
pub fn (mut client Client) get_status(job_id string) !JobStatus {
|
||||||
|
job := client.load_from_redis(job_id)!
|
||||||
|
return job.status
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set job result in Redis
|
||||||
|
pub fn (mut client Client) set_result(job_id string, result string) ! {
|
||||||
|
// Load job, update result, and store back
|
||||||
|
mut job := client.load_from_redis(job_id)!
|
||||||
|
job.result = result
|
||||||
|
job.updated_at = time.now()
|
||||||
|
client.store_in_redis(job_id, job)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set job error in Redis
|
||||||
|
pub fn (mut client Client) set_error(job_id string, error_msg string) ! {
|
||||||
|
// Load job, update error, and store back
|
||||||
|
mut job := client.load_from_redis(job_id)!
|
||||||
|
job.error = error_msg
|
||||||
|
job.status = .error
|
||||||
|
job.updated_at = time.now()
|
||||||
|
client.store_in_redis(job_id, job)!
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete job from Redis
|
||||||
|
pub fn (mut client Client) delete_from_redis(job_id string) ! {
|
||||||
|
job_key := client.job_key(job_id)!
|
||||||
|
client.redis_conn.del(job_key) or {
|
||||||
|
return error('Failed to delete job ${job_id} from Redis: ${err}')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// List all job IDs from Redis
|
||||||
|
pub fn (mut client Client) list_all_job_ids() ![]string {
|
||||||
|
keys := client.redis_conn.keys(client.jobs_key()!) or {
|
||||||
|
return error('Failed to list job keys: ${err}')
|
||||||
|
}
|
||||||
|
|
||||||
|
mut job_ids := []string{}
|
||||||
|
for key in keys {
|
||||||
|
// Extract job ID from key (remove namespace prefix)
|
||||||
|
if key.starts_with(client.jobs_key()!) {
|
||||||
|
job_id := key[client.jobs_key()!.len..]
|
||||||
|
job_ids << job_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return job_ids
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn (client Client) queues_key() ! string {
|
||||||
|
return if client.namespace.len > 0 {
|
||||||
|
"${client.namespace}:runner"
|
||||||
|
} else {
|
||||||
|
"runner"
|
||||||
|
}
|
||||||
|
}
|
91
src/job.v
91
src/job.v
@@ -41,94 +41,3 @@ fn generate_job_id() string {
|
|||||||
random_part := rand.int_in_range(1000, 9999) or { 1234 }
|
random_part := rand.int_in_range(1000, 9999) or { 1234 }
|
||||||
return 'job_${timestamp}_${random_part}'
|
return 'job_${timestamp}_${random_part}'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store this job in Redis
|
|
||||||
pub fn (job Job) store_in_redis(mut conn redisclient.Redis, namespace string) ! {
|
|
||||||
job_key := '${namespace}${job.id}'
|
|
||||||
|
|
||||||
// Store job as JSON
|
|
||||||
job_json := json.encode(job)
|
|
||||||
conn.set(job_key, job_json) or { return error('Failed to store job ${job.id}: ${err}') }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load a job from Redis by ID
|
|
||||||
pub fn load_from_redis(mut conn redisclient.Redis, job_id string, namespace string) !Job {
|
|
||||||
job_key := '${namespace}${job_id}'
|
|
||||||
|
|
||||||
// Get job JSON from Redis
|
|
||||||
job_json := conn.get(job_key) or {
|
|
||||||
return error('Failed to load job ${job_id} from Redis: ${err}')
|
|
||||||
}
|
|
||||||
|
|
||||||
if job_json.len == 0 {
|
|
||||||
return error('Job ${job_id} not found in Redis')
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse job from JSON
|
|
||||||
job := json.decode(Job, job_json) or {
|
|
||||||
return error('Failed to decode job ${job_id} JSON: ${err}')
|
|
||||||
}
|
|
||||||
|
|
||||||
return job
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update job status in Redis
|
|
||||||
pub fn update_job_status(mut conn redisclient.Redis, namespace string, job_id string, status JobStatus) ! {
|
|
||||||
// Load job, update status, and store back
|
|
||||||
mut job := load_from_redis(mut conn, job_id, namespace)!
|
|
||||||
job.status = status
|
|
||||||
job.updated_at = time.now()
|
|
||||||
job.store_in_redis(mut conn, namespace)!
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get job status from Redis
|
|
||||||
pub fn get_status(mut conn redisclient.Redis, job_id string, namespace string) !JobStatus {
|
|
||||||
job := load_from_redis(mut conn, job_id, namespace)!
|
|
||||||
return job.status
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set job result in Redis
|
|
||||||
pub fn set_result(mut conn redisclient.Redis, namespace string, job_id string, result string) ! {
|
|
||||||
// Load job, update result, and store back
|
|
||||||
mut job := load_from_redis(mut conn, job_id, namespace)!
|
|
||||||
job.result = result
|
|
||||||
job.updated_at = time.now()
|
|
||||||
job.store_in_redis(mut conn, namespace)!
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set job error in Redis
|
|
||||||
pub fn set_error(mut conn redisclient.Redis, namespace string, job_id string, error_msg string) ! {
|
|
||||||
// Load job, update error, and store back
|
|
||||||
mut job := load_from_redis(mut conn, job_id, namespace)!
|
|
||||||
job.error = error_msg
|
|
||||||
job.status = .error
|
|
||||||
job.updated_at = time.now()
|
|
||||||
job.store_in_redis(mut conn, namespace)!
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete job from Redis
|
|
||||||
pub fn delete_from_redis(mut conn redisclient.Redis, job_id string, namespace string) ! {
|
|
||||||
job_key := '${namespace}${job_id}'
|
|
||||||
conn.del(job_key) or {
|
|
||||||
return error('Failed to delete job ${job_id} from Redis: ${err}')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// List all job IDs from Redis
|
|
||||||
pub fn list_all_job_ids(mut conn redisclient.Redis, namespace string) ![]string {
|
|
||||||
pattern := '${namespace}*'
|
|
||||||
keys := conn.keys(pattern) or {
|
|
||||||
return error('Failed to list job keys: ${err}')
|
|
||||||
}
|
|
||||||
|
|
||||||
mut job_ids := []string{}
|
|
||||||
for key in keys {
|
|
||||||
// Extract job ID from key (remove namespace prefix)
|
|
||||||
if key.starts_with(namespace) {
|
|
||||||
job_id := key[namespace.len..]
|
|
||||||
job_ids << job_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return job_ids
|
|
||||||
}
|
|
26
src/runner.v
26
src/runner.v
@@ -18,20 +18,12 @@ pub:
|
|||||||
name string
|
name string
|
||||||
namespace string
|
namespace string
|
||||||
pub mut:
|
pub mut:
|
||||||
redis_conn &redisclient.Redis
|
client &Client
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constants for Redis operations
|
// Constants for Redis operations
|
||||||
const blpop_timeout_seconds = 5
|
const blpop_timeout_seconds = 5
|
||||||
|
|
||||||
pub fn (r Runner) queue_key() ! string {
|
|
||||||
return if r.namespace.len > 0 {
|
|
||||||
"${r.namespace}:runner:${r.name}"
|
|
||||||
} else {
|
|
||||||
"runner:${r.name}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Spawn an actor with the trait-based interface
|
// Spawn an actor with the trait-based interface
|
||||||
// This provides the common actor loop implementation that all actors can use
|
// This provides the common actor loop implementation that all actors can use
|
||||||
pub fn (mut r Runner) run() ! {
|
pub fn (mut r Runner) run() ! {
|
||||||
@@ -49,7 +41,7 @@ pub fn (mut r Runner) run() ! {
|
|||||||
log.info('Channel closed')
|
log.info('Channel closed')
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
job := load_from_redis(mut r.redis_conn, job_id, r.namespace) or {
|
job := r.client.load_from_redis(job_id) or {
|
||||||
log.error('Actor "${r.name}" failed to load job ${job_id}: ${err}')
|
log.error('Actor "${r.name}" failed to load job ${job_id}: ${err}')
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -65,7 +57,7 @@ pub fn (mut r Runner) run() ! {
|
|||||||
pub fn (mut r Runner) listen_for_jobs(channel chan string) ! {
|
pub fn (mut r Runner) listen_for_jobs(channel chan string) ! {
|
||||||
for {
|
for {
|
||||||
queue_key := r.queue_key()!
|
queue_key := r.queue_key()!
|
||||||
result := r.redis_conn.blpop([queue_key], 5) or {
|
result := r.client.redis_conn.blpop([queue_key], 5) or {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// blpop returns [queue_key, job_id], we want the job_id (index 1)
|
// blpop returns [queue_key, job_id], we want the job_id (index 1)
|
||||||
@@ -79,10 +71,10 @@ pub fn (mut r Runner) listen_for_jobs(channel chan string) ! {
|
|||||||
pub fn (mut r Runner) run_job(job Job) ! {
|
pub fn (mut r Runner) run_job(job Job) ! {
|
||||||
log.info('MyCustomActor "${r.name}": Processing job ${job.id} with custom logic')
|
log.info('MyCustomActor "${r.name}": Processing job ${job.id} with custom logic')
|
||||||
|
|
||||||
command := "hero -s ${job.payload}"
|
command := "hero run -s ${job.payload}"
|
||||||
|
|
||||||
// Update job status to started
|
// Update job status to started
|
||||||
update_job_status(mut r.redis_conn, r.namespace, job.id, .started)!
|
r.client.update_job_status(job.id, .started)!
|
||||||
if job.executor.starts_with('tmux') {
|
if job.executor.starts_with('tmux') {
|
||||||
session_id := job.executor.all_after_first('tmux')
|
session_id := job.executor.all_after_first('tmux')
|
||||||
mut t := tmux.Tmux{
|
mut t := tmux.Tmux{
|
||||||
@@ -119,7 +111,11 @@ pub fn (mut r Runner) run_job(job Job) ! {
|
|||||||
} else {
|
} else {
|
||||||
return error('Unknown executor: ${job.executor}')
|
return error('Unknown executor: ${job.executor}')
|
||||||
}
|
}
|
||||||
update_job_status(mut r.redis_conn, r.namespace, job.id, .finished)!
|
r.client.update_job_status(job.id, .finished)!
|
||||||
set_result(mut r.redis_conn, r.namespace, job.id, '')!
|
r.client.set_result(job.id, '')!
|
||||||
log.info('MyCustomActor "${r.name}": Job ${job.id} completed successfully')
|
log.info('MyCustomActor "${r.name}": Job ${job.id} completed successfully')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn (r Runner) queue_key() ! string {
|
||||||
|
return "${r.client.queues_key()!}:${r.name}"
|
||||||
|
}
|
Reference in New Issue
Block a user