Merge branch 'working' into job_peek_lchen
job_peek_lchen: Add fetchfile feature (job output/error peek proxy) working: HA/Raft infrastructure (consensus, replication, config mgmt) Conflict resolution summary: - controller.capnp: fetchfile @41, fetchfilereply @18, fetchfilemsg @30 - cluster.c: bifurcated OPCode_reply for fetchfile + config-push ACK - job.c: kept full fetchfile pending-request subsystem (~183 lines) - request.c: kept both xce_fetchfile_req() and XCE_RETRY_DEFAULTS - helper.c: added missing preVoteRequest/preVoteReply mappings - All 15 new test registrations (test_cbuf.c + 14 HA tests)
This commit is contained in:
commit
3a2094a461
529 changed files with 345174 additions and 5048 deletions
|
|
@ -18,11 +18,11 @@ meson build && ninja -C build
|
|||
Links against `libxce` from `src/lib/`.
|
||||
|
||||
## Conventions
|
||||
- C standard: C99 or later
|
||||
- C standard: gnu17
|
||||
- Format with `clang-format`
|
||||
- New scheduling policies → add as a plugin under `src/plugins/`, do not modify the core engine
|
||||
- Shared types defined in `src/include/xce/` (libxce)
|
||||
|
||||
## See Also
|
||||
- Shared library: `src/lib/CLAUDE.md`
|
||||
- Root guidelines: `/workspace/CLAUDE.md`
|
||||
- Root guidelines: `CLAUDE.md` (repo root)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ typedef int (*plugin_job_state_change_hook_t)(void*, xce_sched_job_t*,
|
|||
typedef int (*plugin_commit_decision_hook_t)(void*, xce_decision_t*);
|
||||
typedef int (*plugin_job_election_hook_t)(void*, xce_sched_job_t*);
|
||||
typedef int (*plugin_prepare_allocation_hook_t)(void*, xce_sched_ctx_t*);
|
||||
typedef int (*plugin_job_rusage_hook_t)(void*, xce_sched_job_t*,
|
||||
xce_job_rusages_t*);
|
||||
|
||||
/* initialize function of plugin */
|
||||
typedef int (*plugin_init_fn_t)(void*);
|
||||
|
|
@ -48,6 +50,7 @@ typedef struct {
|
|||
plugin_commit_decision_hook_t commit_hook;
|
||||
plugin_job_election_hook_t job_election_hook;
|
||||
plugin_prepare_allocation_hook_t prepare_allocation_hook;
|
||||
plugin_job_rusage_hook_t rusage_hook;
|
||||
} plugin_hooks_t;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -76,6 +79,7 @@ extern int plugin_end_session(void*);
|
|||
extern int plugin_commit_decision(void*, xce_decision_t*);
|
||||
extern int plugin_prepare_allocation(void*, xce_sched_ctx_t*);
|
||||
extern int plugin_job_election(void*, xce_sched_job_t*);
|
||||
extern int plugin_job_rusage_update(void*, xce_sched_job_t*, xce_job_rusages_t*);
|
||||
extern int register_fcfs(void*);
|
||||
extern int register_limit(void*);
|
||||
extern int register_seqjob_allocator(void*);
|
||||
|
|
|
|||
|
|
@ -93,6 +93,10 @@ typedef struct xce_sched_job_ {
|
|||
uint32_t maxtasks;
|
||||
xce_job_alloc_t* allocs;
|
||||
xce_job_account_t* accounts;
|
||||
/* Latest resource-usage report from the controller (Req 6 / bug-765 G-5).
|
||||
* Owned by the job: replaced on each JOB_RUSAGE update, freed in
|
||||
* kill_sched_job. NULL until the first rusage report. */
|
||||
xce_job_rusages_t* rusages;
|
||||
} xce_sched_job_t;
|
||||
|
||||
typedef struct {
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include "kvmgr.h"
|
||||
#include "mylock.h"
|
||||
#include "plugin.h"
|
||||
#include "xce/cluster.h"
|
||||
#include "xce/types.h"
|
||||
#include "xlist.h"
|
||||
#include "xvec.h"
|
||||
|
|
@ -26,7 +27,16 @@ typedef enum {
|
|||
XCE_SCHED_OP_UPDATE_CONFIG,
|
||||
XCE_SCHED_OP_UPDATE_JOB,
|
||||
XCE_SCHED_OP_JSTATE_CHANGE,
|
||||
XCE_SCHED_OP_UPDATE_WORKER
|
||||
XCE_SCHED_OP_UPDATE_WORKER,
|
||||
XCE_SCHED_OP_UPDATE_QUEUE,
|
||||
XCE_SCHED_OP_UPDATE_RESOURCES,
|
||||
XCE_SCHED_OP_UPDATE_RESVALS_FULL,
|
||||
XCE_SCHED_OP_UPDATE_RESVALS,
|
||||
XCE_SCHED_OP_JOB_RUSAGE,
|
||||
/* Notify-only single-job in-place field update. Distinct from
|
||||
* XCE_SCHED_OP_UPDATE_JOB (the dataset bulk-create path) so a notify
|
||||
* update to an existing job mutates it in place and never creates a job. */
|
||||
XCE_SCHED_OP_UPDATE_JOB_FIELDS
|
||||
} xce_sched_op_t;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -38,6 +48,9 @@ typedef struct {
|
|||
int njobs;
|
||||
xce_job_t** jobs;
|
||||
} jobupdate;
|
||||
struct {
|
||||
xce_job_t* job;
|
||||
} jobfieldsupdate;
|
||||
struct {
|
||||
int nworkers;
|
||||
xce_worker_t** workers;
|
||||
|
|
@ -46,6 +59,23 @@ typedef struct {
|
|||
uint64_t jobid;
|
||||
uint32_t state;
|
||||
} job_state_change;
|
||||
struct {
|
||||
xce_queue_t* queue;
|
||||
} queueupdate;
|
||||
struct {
|
||||
int nresources;
|
||||
xce_resource_t** resources;
|
||||
} resourceupdate;
|
||||
struct {
|
||||
int nvals;
|
||||
xce_resval_t** vals;
|
||||
} resvalfullupdate;
|
||||
struct {
|
||||
xce_resval_list_t* resvals;
|
||||
} resvalupdate;
|
||||
struct {
|
||||
xce_job_rusages_t* rusages;
|
||||
} job_rusage;
|
||||
} u;
|
||||
} xce_sched_update_request_t;
|
||||
|
||||
|
|
@ -88,10 +118,12 @@ typedef struct {
|
|||
_Atomic int eval_in_progress; /* 1 while eval_select loop runs */
|
||||
_Atomic int staging_merge_complete; /* 1 after staging merge completes */
|
||||
select_eval_ctx_t* sel_eval_ctx; /* session-level memoisation context; NULL between sessions */
|
||||
xce_cluster_config_t cluster_cfg; /* HA candidate list — empty means single-host mode */
|
||||
} xce_scheduler_t;
|
||||
|
||||
extern int sched_init(xce_scheduler_t*, xce_client_config_t*,
|
||||
xce_request_handler_t);
|
||||
extern void sched_set_cluster_cfg(xce_scheduler_t*, const xce_cluster_config_t*);
|
||||
extern void sched_fini(xce_scheduler_t*);
|
||||
extern int sched_init_config(xce_scheduler_t* sched, xce_conf4sched_t*);
|
||||
extern int sched_update(xce_scheduler_t*);
|
||||
|
|
@ -105,6 +137,9 @@ extern xce_sched_user_t* sched_get_user(xce_scheduler_t*, char*);
|
|||
extern xce_sched_project_t* sched_get_project(xce_scheduler_t*, char*);
|
||||
extern int sched_add_job(xce_scheduler_t*, xce_sched_job_t*);
|
||||
extern int sched_add_queue(xce_scheduler_t*, xce_sched_queue_t*);
|
||||
extern int sched_update_existing_queue(xce_scheduler_t*, xce_queue_t*);
|
||||
extern int sched_update_existing_job(xce_scheduler_t*, xce_job_t*);
|
||||
extern int sched_apply_job_rusage(xce_scheduler_t*, xce_job_rusages_t*);
|
||||
extern int sched_add_user(xce_scheduler_t*, xce_sched_user_t*);
|
||||
extern int sched_add_worker(xce_scheduler_t*, xce_sched_worker_t*);
|
||||
extern int sched_add_project(xce_scheduler_t*, xce_sched_project_t*);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "controller.capnp.h"
|
||||
#include "dataq.h"
|
||||
#include "helper.h"
|
||||
#include "xce/types.h"
|
||||
|
||||
#include "scheduler.h"
|
||||
|
|
@ -18,21 +19,6 @@ static void enq_pend_update(xce_scheduler_t* sched,
|
|||
mylock_unlock(&(sched->lock));
|
||||
}
|
||||
|
||||
static int sched_handle_jobpush(xce_scheduler_t* sched, xce_jobs_data_t* jobs)
|
||||
{
|
||||
TRACE("%s...", __func__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sched_handle_workerpush(xce_scheduler_t* sched,
|
||||
xce_workers_data_t* workers)
|
||||
{
|
||||
TRACE("%s...", __func__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sched_on_jobs(xce_scheduler_t* sched, int njobs, xce_job_t** jobs)
|
||||
{
|
||||
xce_sched_update_request_t* req;
|
||||
|
|
@ -70,11 +56,57 @@ static int sched_on_workers(xce_scheduler_t* sched, int nworkers,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int sched_handle_resources(xce_scheduler_t* sched, int count,
|
||||
xce_resource_t** resources)
|
||||
{
|
||||
xce_sched_update_request_t* req;
|
||||
|
||||
if (resources == NULL || count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
req = (xce_sched_update_request_t*)calloc(1, sizeof(*req));
|
||||
if (req == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
req->op = XCE_SCHED_OP_UPDATE_RESOURCES;
|
||||
req->u.resourceupdate.nresources = count;
|
||||
req->u.resourceupdate.resources = resources;
|
||||
|
||||
enq_pend_update(sched, req);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sched_handle_resvals(xce_scheduler_t* sched, int count,
|
||||
xce_resval_t** vals)
|
||||
{
|
||||
xce_sched_update_request_t* req;
|
||||
|
||||
if (vals == NULL || count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
req = (xce_sched_update_request_t*)calloc(1, sizeof(*req));
|
||||
if (req == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
req->op = XCE_SCHED_OP_UPDATE_RESVALS_FULL;
|
||||
req->u.resvalfullupdate.nvals = count;
|
||||
req->u.resvalfullupdate.vals = vals;
|
||||
|
||||
enq_pend_update(sched, req);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sched_handle_dataset(xce_scheduler_t* sched, dataset_t* dataset)
|
||||
{
|
||||
int rc = 0;
|
||||
|
||||
TRACE("%s table = %d", __func__, dataset->table);
|
||||
TRACE("%s table = %s", __func__, xce_dataset_table(dataset->table));
|
||||
|
||||
switch (dataset->table) {
|
||||
case Data_data_schedjobs:
|
||||
|
|
@ -96,8 +128,20 @@ static int sched_handle_dataset(xce_scheduler_t* sched, dataset_t* dataset)
|
|||
}
|
||||
break;
|
||||
case Data_data_resources:
|
||||
rc = sched_handle_resources(sched, dataset->nrows,
|
||||
(xce_resource_t**)dataset->rows);
|
||||
if (rc >= 0) {
|
||||
dataset->nrows = 0;
|
||||
dataset->rows = NULL;
|
||||
}
|
||||
break;
|
||||
case Data_data_resvals:
|
||||
rc = sched_handle_resvals(sched, dataset->nrows,
|
||||
(xce_resval_t**)dataset->rows);
|
||||
if (rc >= 0) {
|
||||
dataset->nrows = 0;
|
||||
dataset->rows = NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -126,10 +170,108 @@ static int sched_job_state_change(xce_scheduler_t* sched, uint64_t jobid,
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sched_handle_sched_update_job(xce_scheduler_t* sched,
|
||||
xce_job_t* job)
|
||||
{
|
||||
xce_sched_update_request_t* req;
|
||||
char jobidString[128];
|
||||
|
||||
if (job == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
fmt_jobid(jobidString, sizeof(jobidString) - 1, job->jobid);
|
||||
TRACE("%s %s updated", __func__, jobidString);
|
||||
|
||||
req = (xce_sched_update_request_t*)calloc(1, sizeof(*req));
|
||||
if (req == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
/* Notify single-job update takes the in-place field-update path
|
||||
* (XCE_SCHED_OP_UPDATE_JOB_FIELDS), NOT the dataset bulk-create path
|
||||
* (XCE_SCHED_OP_UPDATE_JOB). An unknown jobid must yield -ENOENT, never a
|
||||
* new job. The dataset bulk-create path (sched_on_jobs) is unchanged. */
|
||||
req->op = XCE_SCHED_OP_UPDATE_JOB_FIELDS;
|
||||
req->u.jobfieldsupdate.job = job;
|
||||
|
||||
enq_pend_update(sched, req);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sched_handle_sched_update_queue(xce_scheduler_t* sched,
|
||||
xce_queue_t* queue)
|
||||
{
|
||||
xce_sched_update_request_t* req;
|
||||
|
||||
if (queue == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
req = (xce_sched_update_request_t*)calloc(1, sizeof(*req));
|
||||
if (req == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
req->op = XCE_SCHED_OP_UPDATE_QUEUE;
|
||||
req->u.queueupdate.queue = queue;
|
||||
|
||||
enq_pend_update(sched, req);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sched_handle_sched_update_resvals(xce_scheduler_t* sched,
|
||||
xce_resval_list_t* resvals)
|
||||
{
|
||||
xce_sched_update_request_t* req;
|
||||
|
||||
if (resvals == NULL || resvals->count == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
req = (xce_sched_update_request_t*)calloc(1, sizeof(*req));
|
||||
if (req == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
req->op = XCE_SCHED_OP_UPDATE_RESVALS;
|
||||
req->u.resvalupdate.resvals = resvals;
|
||||
|
||||
enq_pend_update(sched, req);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sched_handle_sched_job_rusage(xce_scheduler_t* sched,
|
||||
xce_job_rusages_t* rusages)
|
||||
{
|
||||
xce_sched_update_request_t* req;
|
||||
|
||||
if (rusages == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
req = (xce_sched_update_request_t*)calloc(1, sizeof(*req));
|
||||
if (req == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
req->op = XCE_SCHED_OP_JOB_RUSAGE;
|
||||
req->u.job_rusage.rusages = rusages;
|
||||
|
||||
enq_pend_update(sched, req);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int sched_handle_notify(xce_scheduler_t* sched, xce_notify_data_t* notify)
|
||||
{
|
||||
int rc = 0;
|
||||
char jobidString[128];
|
||||
int rc = 0;
|
||||
|
||||
TRACE("%s event = %s", __func__, xce_notify_event_name(notify->event));
|
||||
|
||||
switch (notify->event) {
|
||||
case NotifyData_data_schedStartJob:
|
||||
|
|
@ -145,15 +287,28 @@ int sched_handle_notify(xce_scheduler_t* sched, xce_notify_data_t* notify)
|
|||
JOB_STAT_EXIT);
|
||||
break;
|
||||
case NotifyData_data_schedUpdateJob:
|
||||
fmt_jobid(jobidString, sizeof(jobidString) - 1,
|
||||
notify->data.sched_update_job->jobid);
|
||||
TRACE("%s %s updated", __func__, jobidString);
|
||||
rc = sched_handle_sched_update_job(sched,
|
||||
notify->data.sched_update_job);
|
||||
if (rc >= 0)
|
||||
notify->data.sched_update_job = NULL;
|
||||
break;
|
||||
case NotifyData_data_schedUpdateQueue:
|
||||
rc = sched_handle_sched_update_queue(sched,
|
||||
notify->data.sched_update_queue);
|
||||
if (rc >= 0)
|
||||
notify->data.sched_update_queue = NULL;
|
||||
break;
|
||||
case NotifyData_data_schedUpdateResvals:
|
||||
rc = sched_handle_sched_update_resvals(
|
||||
sched, notify->data.sched_update_resvals);
|
||||
if (rc >= 0)
|
||||
notify->data.sched_update_resvals = NULL;
|
||||
break;
|
||||
case NotifyData_data_schedJobRusages:
|
||||
rc = sched_handle_sched_job_rusage(sched,
|
||||
notify->data.sched_job_rusages);
|
||||
if (rc >= 0)
|
||||
notify->data.sched_job_rusages = NULL;
|
||||
break;
|
||||
default:
|
||||
rc = -EINVAL;
|
||||
|
|
@ -162,11 +317,14 @@ int sched_handle_notify(xce_scheduler_t* sched, xce_notify_data_t* notify)
|
|||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int scheduler_req_handler(xce_request_t* request, void* args)
|
||||
{
|
||||
int rc = 0;
|
||||
xce_scheduler_t* sched = (xce_scheduler_t*)args;
|
||||
|
||||
TRACE("%s op = %s", __func__, xce_request_op_name(request));
|
||||
|
||||
switch (request->opcode) {
|
||||
case OPCode_push:
|
||||
switch (request->body_which) {
|
||||
|
|
@ -181,21 +339,6 @@ int scheduler_req_handler(xce_request_t* request, void* args)
|
|||
request->body.configpushmsg->data.conf4sched = NULL;
|
||||
}
|
||||
break;
|
||||
case Request_body_pushmsg:
|
||||
/* data arriving */
|
||||
switch (request->body.pushmsg->table) {
|
||||
case Data_data_schedjobs:
|
||||
/* job arriving */
|
||||
rc = sched_handle_jobpush(sched,
|
||||
request->body.pushmsg->data.jobs);
|
||||
break;
|
||||
case Data_data_schedworkers:
|
||||
/* workers arriving */
|
||||
rc = sched_handle_workerpush(
|
||||
sched, request->body.pushmsg->data.workers);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Request_body_datamsg:
|
||||
rc = sched_handle_dataset(sched, request->body.datamsg);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@
|
|||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "client.h"
|
||||
#include "controller.capnp.h"
|
||||
#include "xce/cluster.h"
|
||||
#include "xce/types.h"
|
||||
|
||||
#include "scheduler.h"
|
||||
|
|
@ -13,6 +15,7 @@
|
|||
#include "resreq.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
||||
extern int scheduler_req_handler(xce_request_t*, void*);
|
||||
|
||||
static char doc[] = "xce_scheduler";
|
||||
|
|
@ -24,11 +27,15 @@ const char* argp_program_bug_address = BUG_EMAIL;
|
|||
struct arguments {
|
||||
char* logfile;
|
||||
int verbose;
|
||||
char* peers; /* --peers value (optional) */
|
||||
char* ca_cert; /* --ca-cert value (optional) */
|
||||
};
|
||||
|
||||
static struct argp_option options[] = {
|
||||
{ "logfile", 'l', "FILE", 0, "The full path of log file" },
|
||||
{ "verbose", 'v', 0, 0, "Produce verbose log" },
|
||||
{ "logfile", 'l', "FILE", 0, "The full path of log file" },
|
||||
{ "verbose", 'v', 0, 0, "Produce verbose log" },
|
||||
{ "peers", 'P', "LIST", 0, "HA candidate list: [name=]host:port,..." },
|
||||
{ "ca-cert", 'C', "FILE", 0, "CA certificate path for TLS verification" },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
|
|
@ -43,6 +50,12 @@ static error_t parse_opt(int key, char* arg, struct argp_state* state)
|
|||
case 'v':
|
||||
arguments->verbose++;
|
||||
break;
|
||||
case 'P':
|
||||
arguments->peers = arg;
|
||||
break;
|
||||
case 'C':
|
||||
arguments->ca_cert = arg;
|
||||
break;
|
||||
default:
|
||||
return ARGP_ERR_UNKNOWN;
|
||||
}
|
||||
|
|
@ -109,6 +122,42 @@ int main(int argc, char* argv[])
|
|||
|
||||
mylog_init(logfile, loglevel, logsize, logcount);
|
||||
|
||||
/* P2.5-6: populate and validate xce_cluster_config_t when --peers given */
|
||||
if (arguments.peers) {
|
||||
xce_cluster_config_t cluster_cfg = {0};
|
||||
|
||||
if (xce_parse_peers(arguments.peers, &cluster_cfg) != 0) {
|
||||
fprintf(stderr,
|
||||
"xce_scheduler: --peers parse error: expected "
|
||||
"\"[name=]host:port,...\" got \"%s\"\n",
|
||||
arguments.peers);
|
||||
return 1;
|
||||
}
|
||||
if (arguments.ca_cert) {
|
||||
strncpy(cluster_cfg.ca_cert_path, arguments.ca_cert,
|
||||
sizeof(cluster_cfg.ca_cert_path) - 1);
|
||||
}
|
||||
strncpy(cluster_cfg.identity, "xce_scheduler",
|
||||
sizeof(cluster_cfg.identity) - 1);
|
||||
if (xce_cluster_config_validate(&cluster_cfg) != 0) {
|
||||
fprintf(stderr,
|
||||
"xce_scheduler: invalid cluster config (missing "
|
||||
"--ca-cert?).\n");
|
||||
return 1;
|
||||
}
|
||||
if (cluster_cfg.candidates_count > 3) {
|
||||
WARN("xce_scheduler: --peers has %d candidates; "
|
||||
"only <=3 validated for HA",
|
||||
cluster_cfg.candidates_count);
|
||||
}
|
||||
INFO("HA cluster config: %d candidate(s), identity=%s, ca_cert=%s",
|
||||
cluster_cfg.candidates_count, cluster_cfg.identity,
|
||||
cluster_cfg.ca_cert_path);
|
||||
sched_set_cluster_cfg(&sched, &cluster_cfg);
|
||||
} else {
|
||||
INFO("No --peers flag; using single-controller path");
|
||||
}
|
||||
|
||||
init_config(&config);
|
||||
|
||||
sched_init(&sched, &config, scheduler_req_handler);
|
||||
|
|
|
|||
|
|
@ -358,6 +358,44 @@ int plugin_job_state_change(void* arg, xce_sched_job_t* job, uint32_t state)
|
|||
return rc;
|
||||
}
|
||||
|
||||
int plugin_job_rusage_update(void* arg, xce_sched_job_t* job,
|
||||
xce_job_rusages_t* rusages)
|
||||
{
|
||||
xce_scheduler_t* sched = (xce_scheduler_t*)arg;
|
||||
iterator_t it;
|
||||
int rc;
|
||||
|
||||
if (sched == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rc = iter_make(&it, sched->plugins);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
xce_sched_plugin_t* plugin = NULL;
|
||||
|
||||
rc = iter_next(&it, (void**)&plugin);
|
||||
if ((rc < 0) || (rc == RTTI_ITER_NO_DATA)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (plugin->hooks.rusage_hook != NULL) {
|
||||
plugin->hooks.rusage_hook(plugin, job, rusages);
|
||||
}
|
||||
}
|
||||
|
||||
if (rc == RTTI_ITER_NO_DATA) {
|
||||
rc = 0;
|
||||
}
|
||||
|
||||
iter_destroy(&it);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int plugin_job_election(void* arg, xce_sched_job_t* job)
|
||||
{
|
||||
xce_scheduler_t* sched = (xce_scheduler_t*)arg;
|
||||
|
|
|
|||
|
|
@ -207,6 +207,7 @@ static void* fcfs_elect_job(xce_sched_plugin_t* plugin)
|
|||
ordq_item_t* it = NULL;
|
||||
int go = 0;
|
||||
uint64_t jobid = 0;
|
||||
char jobidString[64];
|
||||
|
||||
pthread_testcancel();
|
||||
|
||||
|
|
@ -233,7 +234,8 @@ static void* fcfs_elect_job(xce_sched_plugin_t* plugin)
|
|||
if (sched_job_is_ready(j->job)) {
|
||||
jobid = j->job->jobid;
|
||||
|
||||
TRACE("%s jobid %ld", __func__, jobid);
|
||||
fmt_jobid(jobidString, sizeof(jobidString), jobid);
|
||||
TRACE("%s jobid %s", __func__, jobidString);
|
||||
cbuf_put(&(sched->jbuf), &jobid);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -226,9 +226,11 @@ static int limit_job_election(void* arg, xce_sched_job_t* job)
|
|||
xce_sched_obj_t* obj = job->schedobj;
|
||||
int rc = 0;
|
||||
iterator_t it;
|
||||
xce_sched_ctx_t ctx = { 0 };
|
||||
xce_sched_ctx_t ctx = { 0 };
|
||||
char jobidString[64];
|
||||
|
||||
TRACE("%s job %lu plugin %s", __func__, job->jobid, plugin->name);
|
||||
fmt_jobid(jobidString, sizeof(jobidString), job->jobid);
|
||||
TRACE("%s job %s plugin %s", __func__, jobidString, plugin->name);
|
||||
|
||||
if (obj->configver >= sched->configver) {
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -93,18 +93,20 @@ int put_resvals(zarray_t* array, int nres, int* idxs, float* vals)
|
|||
return nres;
|
||||
}
|
||||
|
||||
static int unsatisfy_(int nres, int* idxs, float* total, float* inuse,
|
||||
float* uncomitted, float* wanted, int* unmeet_idxs,
|
||||
float* unmeet_vals)
|
||||
static int unsatisfy_(xce_scheduler_t* sched, int nres, int* idxs, float* total,
|
||||
float* inuse, float* uncomitted, float* wanted,
|
||||
int* unmeet_idxs, float* unmeet_vals)
|
||||
{
|
||||
int i, j;
|
||||
int count;
|
||||
|
||||
count = 0;
|
||||
for (i = 0; i < nres; i++) {
|
||||
TRACE("%s resource %d total %f inuse %f uncomit %f wanted %f", __func__,
|
||||
idxs[i], total[i], inuse[i], uncomitted[i], wanted[i]);
|
||||
if (total[i] < inuse[i] + uncomitted[i] + wanted[i]) {
|
||||
TRACE("%s resource %d total %f inuse %f uncomit %f wanted %f",
|
||||
__func__, idxs[i], total[i], inuse[i], uncomitted[i],
|
||||
wanted[i]);
|
||||
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
|
@ -116,8 +118,15 @@ static int unsatisfy_(int nres, int* idxs, float* total, float* inuse,
|
|||
j = 0;
|
||||
for (i = 0; i < nres; i++) {
|
||||
if (total[i] < inuse[i] + uncomitted[i] + wanted[i]) {
|
||||
xce_resource_t* r = NULL;
|
||||
float need = inuse[i] + uncomitted[i] + wanted[i] - total[i];
|
||||
|
||||
vget(sched->resmgr, idxs[i], (void**)&r);
|
||||
TRACE("%s unmet resource %d (%s) shortfall %f", __func__, idxs[i],
|
||||
r ? r->name : "?", need);
|
||||
|
||||
unmeet_idxs[j] = idxs[i];
|
||||
unmeet_vals[j] = inuse[i] + wanted[i] + uncomitted[i] - total[i];
|
||||
unmeet_vals[j] = need;
|
||||
j++;
|
||||
}
|
||||
}
|
||||
|
|
@ -125,14 +134,16 @@ static int unsatisfy_(int nres, int* idxs, float* total, float* inuse,
|
|||
return count;
|
||||
}
|
||||
|
||||
static host_eval_ctx_t*
|
||||
build_host_eval_ctx(const xce_sched_worker_t* w, int num_res)
|
||||
static host_eval_ctx_t* build_host_eval_ctx(const xce_sched_worker_t* w,
|
||||
int num_res)
|
||||
{
|
||||
host_eval_ctx_t* ctx = calloc(1, sizeof(*ctx));
|
||||
if (ctx == NULL) return NULL;
|
||||
if (ctx == NULL)
|
||||
return NULL;
|
||||
|
||||
if (num_res <= 0) {
|
||||
/* valid empty context — numvals/strvals remain NULL, bounds checks protect access */
|
||||
/* valid empty context — numvals/strvals remain NULL, bounds checks
|
||||
* protect access */
|
||||
ctx->num_res = 0;
|
||||
return ctx;
|
||||
}
|
||||
|
|
@ -148,7 +159,8 @@ build_host_eval_ctx(const xce_sched_worker_t* w, int num_res)
|
|||
|
||||
for (int i = 0; i < num_res; i++) {
|
||||
xce_val_t* v = (xce_val_t*)zarray_get((zarray_t*)&w->resvals, i);
|
||||
if (v == NULL) continue;
|
||||
if (v == NULL)
|
||||
continue;
|
||||
switch (v->which) {
|
||||
case Value_u_fvalue:
|
||||
numvals[i] = (double)v->u.fvalue;
|
||||
|
|
@ -168,10 +180,10 @@ build_host_eval_ctx(const xce_sched_worker_t* w, int num_res)
|
|||
return ctx;
|
||||
}
|
||||
|
||||
static void
|
||||
free_host_eval_ctx(host_eval_ctx_t* ctx)
|
||||
static void free_host_eval_ctx(host_eval_ctx_t* ctx)
|
||||
{
|
||||
if (ctx == NULL) return;
|
||||
if (ctx == NULL)
|
||||
return;
|
||||
free((void*)ctx->numvals);
|
||||
free((void*)ctx->strvals);
|
||||
free(ctx);
|
||||
|
|
@ -179,7 +191,8 @@ free_host_eval_ctx(host_eval_ctx_t* ctx)
|
|||
|
||||
void sched_worker_release_host_ctx(xce_sched_worker_t* w)
|
||||
{
|
||||
if (w == NULL) return;
|
||||
if (w == NULL)
|
||||
return;
|
||||
free_host_eval_ctx(w->host_eval_ctx);
|
||||
w->host_eval_ctx = NULL;
|
||||
}
|
||||
|
|
@ -187,11 +200,12 @@ void sched_worker_release_host_ctx(xce_sched_worker_t* w)
|
|||
int sched_meet_host_selection(xce_scheduler_t* sched, xce_sched_worker_t* w,
|
||||
xce_select_t* s)
|
||||
{
|
||||
if (s == NULL || s->root == NULL) return 1;
|
||||
if (s == NULL || s->root == NULL)
|
||||
return 1;
|
||||
|
||||
/* Build host context lazily — reused for all jobs against this host. */
|
||||
if (w->host_eval_ctx == NULL) {
|
||||
int num_res = (int)w->resvals.caps;
|
||||
int num_res = (int)w->resvals.caps;
|
||||
w->host_eval_ctx = build_host_eval_ctx(w, num_res);
|
||||
if (w->host_eval_ctx == NULL) {
|
||||
/* OOM: conservatively pass host */
|
||||
|
|
@ -209,7 +223,8 @@ int sched_meet_host_selection(xce_scheduler_t* sched, xce_sched_worker_t* w,
|
|||
return (rc > 0) ? 1 : 0;
|
||||
}
|
||||
|
||||
/* Fallback (no session context — e.g. unit tests or OOM at session start). */
|
||||
/* Fallback (no session context — e.g. unit tests or OOM at session start).
|
||||
*/
|
||||
int rc = eval_select_simple(s->root, w->host_eval_ctx);
|
||||
return (rc > 0) ? 1 : 0;
|
||||
}
|
||||
|
|
@ -249,7 +264,7 @@ static int sched_check_shared_satisfy_(xce_scheduler_t* sched, xvec_t* total,
|
|||
return rc;
|
||||
}
|
||||
|
||||
rc = unsatisfy_(nres, (int*)wanted->idxs.data, (float*)total->data,
|
||||
rc = unsatisfy_(sched, nres, (int*)wanted->idxs.data, (float*)total->data,
|
||||
(float*)inuse->data, (float*)uncomitted->data,
|
||||
(float*)wanted->vals.data, (int*)unmeet->idxs.data,
|
||||
(float*)unmeet->vals.data);
|
||||
|
|
@ -324,7 +339,7 @@ static int sched_check_host_satisfy_(xce_scheduler_t* sched,
|
|||
return rc;
|
||||
}
|
||||
|
||||
rc = unsatisfy_(nres, (int*)wanted->idxs.data, (float*)total->data,
|
||||
rc = unsatisfy_(sched, nres, (int*)wanted->idxs.data, (float*)total->data,
|
||||
(float*)inuse->data, (float*)uncomitted->data,
|
||||
(float*)wanted->vals.data, (int*)unmeet->idxs.data,
|
||||
(float*)unmeet->vals.data);
|
||||
|
|
@ -457,7 +472,7 @@ static int sched_try_alloc_on_host_(xce_sched_worker_t* w, va_list ap)
|
|||
if (ctx->reason == NULL) {
|
||||
return -errno;
|
||||
}
|
||||
|
||||
|
||||
add_job_preason(ctx->reason->reasons, PREASON_MATCH_FAIL, w->name,
|
||||
NULL);
|
||||
|
||||
|
|
@ -489,18 +504,18 @@ static int sched_try_alloc_on_host_(xce_sched_worker_t* w, va_list ap)
|
|||
int i;
|
||||
if (ctx)
|
||||
|
||||
for (i = 0; i < ctx->unmeet.count; i++) {
|
||||
xce_resource_t* r = NULL;
|
||||
for (i = 0; i < ctx->unmeet.count; i++) {
|
||||
xce_resource_t* r = NULL;
|
||||
|
||||
rc = vget(sched->resmgr, ((int*)ctx->unmeet.idxs.data)[i],
|
||||
(void**)&r);
|
||||
if (rc < 0) {
|
||||
continue;
|
||||
rc = vget(sched->resmgr, ((int*)ctx->unmeet.idxs.data)[i],
|
||||
(void**)&r);
|
||||
if (rc < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
add_job_preason(ctx->reason->reasons, PREASON_NO_RESOURCE,
|
||||
w->name, r->name);
|
||||
}
|
||||
|
||||
add_job_preason(ctx->reason->reasons, PREASON_NO_RESOURCE, w->name,
|
||||
r->name);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,9 +16,15 @@
|
|||
|
||||
void kill_sched_job(void* arg)
|
||||
{
|
||||
if (arg == NULL) {
|
||||
xce_sched_job_t* job = (xce_sched_job_t*)arg;
|
||||
|
||||
if (job == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* release the stored rusage report, if any (Req 6 / bug-765 G-5).
|
||||
* free_JobRusages_ptr is NULL-safe and clears the field. */
|
||||
free_JobRusages_ptr(&job->rusages);
|
||||
}
|
||||
|
||||
request_res_t* make_resource_request(char* res, float value)
|
||||
|
|
@ -278,11 +284,23 @@ xce_sched_worker_t* make_sched_worker(void* arg, xce_worker_t* w)
|
|||
|
||||
/* fill resource values */
|
||||
for (i = 0; i < w->nresvals_to_sched; i++) {
|
||||
int idx = w->resvals_to_sched[i]->idx;
|
||||
xce_val_t* val = NULL;
|
||||
xce_name_val_t* nv = w->resvals_to_sched[i];
|
||||
xce_resource_t* r = NULL;
|
||||
xce_val_t* val = NULL;
|
||||
|
||||
MOVE_PTR(val, w->resvals_to_sched[i]->val);
|
||||
zarray_put(&(worker->resvals), idx, val);
|
||||
if (nv == NULL || nv->name == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key_find(sched->resmgr, (void*)nv->name, (void**)&r) < 0
|
||||
|| r == NULL) {
|
||||
WARN("%s unknown resource '%s' in worker resvals, skipping",
|
||||
__func__, nv->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
MOVE_PTR(val, nv->val);
|
||||
zarray_put(&(worker->resvals), r->idx, val);
|
||||
}
|
||||
|
||||
return worker;
|
||||
|
|
@ -475,3 +493,141 @@ int sched_ctx_get_uncomit_host(xvec_t** host, xce_sched_ctx_t* ctx, int idx)
|
|||
*host = v;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int sched_update_existing_queue(xce_scheduler_t* sched, xce_queue_t* updated)
|
||||
{
|
||||
xce_sched_queue_t* q;
|
||||
|
||||
if (updated == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
q = sched_get_queue(sched, updated->queue);
|
||||
if (q == NULL) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
q->status = updated->status;
|
||||
q->priority = updated->priority;
|
||||
q->accept_interval = updated->accept_interval;
|
||||
q->sched_delay = updated->sched_delay;
|
||||
|
||||
if (updated->resreq != NULL) {
|
||||
free(q->resreq);
|
||||
q->resreq = makeString(updated->resreq);
|
||||
}
|
||||
|
||||
plugin_queue_update(sched, q);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* sched_update_existing_job — apply a notify single-job update to an existing
|
||||
* job IN PLACE. The job must already exist (created via the Data_data_schedjobs
|
||||
* dataset path); this function never creates a job.
|
||||
*
|
||||
* - NULL payload → -EINVAL
|
||||
* - unknown jobid → -ENOENT (no job created)
|
||||
* - otherwise → update mutable fields (state, priority, queue,
|
||||
* resource request) and notify plugins; return 0
|
||||
*
|
||||
* Runtime state (schedobj, allocs, accounts) is preserved — the live
|
||||
* xce_sched_job_t object is mutated, not replaced. Mirrors the field-copy +
|
||||
* resreq-replace pattern of sched_update_existing_queue().
|
||||
*/
|
||||
int sched_update_existing_job(xce_scheduler_t* sched, xce_job_t* updated)
|
||||
{
|
||||
xce_sched_job_t* job;
|
||||
|
||||
if (updated == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
job = sched_get_job(sched, updated->jobid);
|
||||
if (job == NULL) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/* state — always carried on xce_job_t */
|
||||
job->state = updated->state;
|
||||
|
||||
/* mutable submit-derived fields */
|
||||
if (updated->submit != NULL) {
|
||||
/* queue — re-resolve the queue pointer by name. xce_job_t carries no
|
||||
* per-job priority, so job priority follows the (possibly new) queue,
|
||||
* consistent with the queue-derived priority model. */
|
||||
if (updated->submit->queue != NULL) {
|
||||
xce_sched_queue_t* q =
|
||||
sched_get_queue(sched, updated->submit->queue);
|
||||
if (q != NULL) {
|
||||
job->queue = q;
|
||||
job->priority = q->priority;
|
||||
}
|
||||
}
|
||||
|
||||
/* resource request — re-parse and swap; free the old parsed container.
|
||||
* Mirrors sched_update_existing_queue's free-old/replace pattern. The
|
||||
* inner select tree is intern/GC-owned, consistent with make_sched_job. */
|
||||
if (updated->submit->resreq != NULL) {
|
||||
xce_resreq_t* newreq =
|
||||
(xce_resreq_t*)calloc(1, sizeof(*newreq));
|
||||
if (newreq == NULL) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
char* errmsg = NULL;
|
||||
int rc = parse_resreq(newreq, updated->submit->resreq,
|
||||
sched->resmgr, &errmsg);
|
||||
if (rc < 0) {
|
||||
ERROR("%s parse_resreq failed: %s", __func__,
|
||||
errmsg ? errmsg : "(null)");
|
||||
FREEUP(errmsg);
|
||||
free(newreq);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
free(job->resreq);
|
||||
job->resreq = newreq;
|
||||
}
|
||||
}
|
||||
|
||||
plugin_job_update(sched, job);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* sched_apply_job_rusage — store a controller rusage report ON the matching
|
||||
* job (Req 6, UB-6.1/6.2/6.3; bug-765 G-5). On success the job TAKES OWNERSHIP
|
||||
* of `rusages` (the caller must not free it); the previous report, if any, is
|
||||
* released. Returns:
|
||||
* -EINVAL rusages is NULL
|
||||
* -ENOENT no job with rusages->jobid (no state modified, ownership retained
|
||||
* by the caller so it can free the payload)
|
||||
* 0 stored on the job and plugins notified
|
||||
*/
|
||||
int sched_apply_job_rusage(xce_scheduler_t* sched, xce_job_rusages_t* rusages)
|
||||
{
|
||||
xce_sched_job_t* job;
|
||||
|
||||
if (rusages == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
job = sched_get_job(sched, rusages->jobid);
|
||||
if (job == NULL) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
/* replace any prior report; the job now owns this one. free_JobRusages_ptr
|
||||
* is NULL-safe and clears the field before we reassign. */
|
||||
if (job->rusages != rusages) {
|
||||
free_JobRusages_ptr(&job->rusages);
|
||||
}
|
||||
job->rusages = rusages;
|
||||
|
||||
plugin_job_rusage_update(sched, job, rusages);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,14 +22,12 @@
|
|||
|
||||
static int sched_session_active(xce_scheduler_t* sched)
|
||||
{
|
||||
return atomic_load_explicit(&sched->session_active,
|
||||
memory_order_acquire);
|
||||
return atomic_load_explicit(&sched->session_active, memory_order_acquire);
|
||||
}
|
||||
|
||||
static int sched_eval_in_progress(xce_scheduler_t* sched)
|
||||
{
|
||||
return atomic_load_explicit(&sched->eval_in_progress,
|
||||
memory_order_acquire);
|
||||
return atomic_load_explicit(&sched->eval_in_progress, memory_order_acquire);
|
||||
}
|
||||
|
||||
static int sched_staging_merge_complete(xce_scheduler_t* sched)
|
||||
|
|
@ -228,6 +226,7 @@ static void* sched_allocator(void* arg)
|
|||
xce_use(sched);
|
||||
while (1) {
|
||||
uint64_t jobid;
|
||||
char jobidString[64];
|
||||
xce_decision_t* decision = NULL;
|
||||
xce_sched_job_t* job = NULL;
|
||||
int rc = 0;
|
||||
|
|
@ -244,7 +243,8 @@ static void* sched_allocator(void* arg)
|
|||
|
||||
job = sched_get_job(sched, jobid);
|
||||
if (job == NULL) {
|
||||
TRACE("%s job %lu can not be found", __func__, jobid);
|
||||
fmt_jobid(jobidString, sizeof(jobidString), jobid);
|
||||
TRACE("%s job %s can not be found", __func__, jobidString);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -259,9 +259,10 @@ static void* sched_allocator(void* arg)
|
|||
/* job is pend */
|
||||
xce_pendreason_t* reason = job->schedobj->reason;
|
||||
|
||||
WARN("job %ld is pending but pend reason is NULL", jobid);
|
||||
|
||||
if (reason != NULL) {
|
||||
if (reason == NULL) {
|
||||
fmt_jobid(jobidString, sizeof(jobidString), jobid);
|
||||
WARN("job %s is pending but pend reason is NULL", jobidString);
|
||||
} else {
|
||||
reason->jobid = job->jobid;
|
||||
decision = make_pend_decision(reason);
|
||||
}
|
||||
|
|
@ -302,6 +303,9 @@ static void sched_pend_update_killer(data_item_t* it)
|
|||
|
||||
free(req->u.jobupdate.jobs);
|
||||
break;
|
||||
case XCE_SCHED_OP_UPDATE_JOB_FIELDS:
|
||||
free_Job_ptr(&(req->u.jobfieldsupdate.job));
|
||||
break;
|
||||
case XCE_SCHED_OP_UPDATE_WORKER:
|
||||
for (i = 0; i < req->u.workerupdate.nworkers; i++) {
|
||||
free_Worker_ptr(&(req->u.workerupdate.workers[i]));
|
||||
|
|
@ -311,6 +315,34 @@ static void sched_pend_update_killer(data_item_t* it)
|
|||
break;
|
||||
case XCE_SCHED_OP_JSTATE_CHANGE:
|
||||
break;
|
||||
/* The five notify/dataset payloads below are owned by the request (the
|
||||
* handler/dataset path nullifies its own pointer on enqueue). The dequeue
|
||||
* copies values / makeResource constructs a fresh instance, so none of
|
||||
* these are aliased into live state — free them here (bug-765 G-3). */
|
||||
case XCE_SCHED_OP_UPDATE_QUEUE:
|
||||
free_QueueConf_ptr(&(req->u.queueupdate.queue));
|
||||
break;
|
||||
case XCE_SCHED_OP_UPDATE_RESOURCES:
|
||||
for (i = 0; i < req->u.resourceupdate.nresources; i++) {
|
||||
free_Resource_ptr(&(req->u.resourceupdate.resources[i]));
|
||||
}
|
||||
free(req->u.resourceupdate.resources);
|
||||
break;
|
||||
case XCE_SCHED_OP_UPDATE_RESVALS_FULL:
|
||||
for (i = 0; i < req->u.resvalfullupdate.nvals; i++) {
|
||||
free_ResourceVal_ptr(&(req->u.resvalfullupdate.vals[i]));
|
||||
}
|
||||
free(req->u.resvalfullupdate.vals);
|
||||
break;
|
||||
case XCE_SCHED_OP_UPDATE_RESVALS:
|
||||
free_ResValList_ptr(&(req->u.resvalupdate.resvals));
|
||||
break;
|
||||
case XCE_SCHED_OP_JOB_RUSAGE:
|
||||
/* NULL after a successful sched_apply_job_rusage (job took ownership);
|
||||
* non-NULL on the -ENOENT/-EINVAL path, freed here (bug-765 G-5).
|
||||
* free_JobRusages_ptr is NULL-safe. */
|
||||
free_JobRusages_ptr(&(req->u.job_rusage.rusages));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -389,8 +421,8 @@ int sched_init(xce_scheduler_t* sched, xce_client_config_t* config,
|
|||
sched->nextid = 1;
|
||||
sched->configver = 0;
|
||||
|
||||
xstring_table_init(1024*1024);
|
||||
|
||||
xstring_table_init(1024 * 1024);
|
||||
|
||||
mylock_init(&(sched->lock));
|
||||
cbuf_init(&(sched->wbuf), 1, sizeof(int));
|
||||
cbuf_init(&(sched->jbuf), njobs, sizeof(uint64_t));
|
||||
|
|
@ -430,14 +462,54 @@ int sched_init(xce_scheduler_t* sched, xce_client_config_t* config,
|
|||
|
||||
xcecli_set_handler(sched->apictx, handler, sched);
|
||||
|
||||
rc = xcecli_start(sched->apictx);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
/* HA mode: iterate candidates until one accepts as primary */
|
||||
if (sched->cluster_cfg.candidates_count > 0) {
|
||||
int joined = 0;
|
||||
for (int i = 0; i < sched->cluster_cfg.candidates_count; i++) {
|
||||
const char* host = sched->cluster_cfg.candidates[i].hostname;
|
||||
int port = sched->cluster_cfg.candidates[i].port;
|
||||
|
||||
rc = xcecli_join(sched->apictx, HostRole_scheduler, NULL);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
config->nmasters = 1;
|
||||
config->masters[0] = (char*)host;
|
||||
config->master_ports[0] = port;
|
||||
|
||||
rc = xcecli_ctx_init(&(sched->apictx), config);
|
||||
if (rc < 0) {
|
||||
continue;
|
||||
}
|
||||
xcecli_set_handler(sched->apictx, handler, sched);
|
||||
|
||||
rc = xcecli_start(sched->apictx);
|
||||
if (rc < 0) {
|
||||
xcecli_ctx_fini(sched->apictx); /* free before next attempt */
|
||||
continue;
|
||||
}
|
||||
|
||||
rc = xcecli_join(sched->apictx, HostRole_scheduler, NULL);
|
||||
if (rc == 0) {
|
||||
joined = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Standby replied — tear down fully before next attempt */
|
||||
xcecli_stop(sched->apictx);
|
||||
xcecli_ctx_fini(sched->apictx);
|
||||
}
|
||||
|
||||
if (!joined) {
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
/* Single-host path (original) */
|
||||
rc = xcecli_start(sched->apictx);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = xcecli_join(sched->apictx, HostRole_scheduler, NULL);
|
||||
if (rc < 0) {
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
||||
rc = pthread_create(&(sched->jobpicker), NULL, &sched_job_picker, sched);
|
||||
|
|
@ -450,6 +522,15 @@ int sched_init(xce_scheduler_t* sched, xce_client_config_t* config,
|
|||
return rc;
|
||||
}
|
||||
|
||||
void sched_set_cluster_cfg(xce_scheduler_t* sched,
|
||||
const xce_cluster_config_t* cfg)
|
||||
{
|
||||
if (sched == NULL || cfg == NULL) {
|
||||
return;
|
||||
}
|
||||
sched->cluster_cfg = *cfg;
|
||||
}
|
||||
|
||||
void sched_fini(xce_scheduler_t* sched)
|
||||
{
|
||||
void* res;
|
||||
|
|
@ -520,11 +601,23 @@ static int sched_init_sharedInst(xce_scheduler_t* sched)
|
|||
int i;
|
||||
|
||||
for (i = 0; i < sched->config->nresvals; i++) {
|
||||
int idx = sched->config->resvals[i]->idx;
|
||||
xce_val_t* val = NULL;
|
||||
xce_name_val_t* nv = sched->config->resvals[i];
|
||||
xce_resource_t* r = NULL;
|
||||
xce_val_t* val = NULL;
|
||||
|
||||
MOVE_PTR(val, sched->config->resvals[i]->val);
|
||||
zarray_put(&(sched->sharedInst), idx, val);
|
||||
if (nv == NULL || nv->name == NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key_find(sched->resmgr, (void*)nv->name, (void**)&r) < 0
|
||||
|| r == NULL) {
|
||||
WARN("%s unknown resource '%s' in resvals, skipping",
|
||||
__func__, nv->name);
|
||||
continue;
|
||||
}
|
||||
|
||||
MOVE_PTR(val, nv->val);
|
||||
zarray_put(&(sched->sharedInst), r->idx, val);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -614,6 +707,29 @@ static int sched_update_workers(xce_scheduler_t* sched, int nworkers,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static char* sched_op_name(xce_sched_op_t op)
|
||||
{
|
||||
static char buf[32];
|
||||
|
||||
switch (op) {
|
||||
case XCE_SCHED_OP_NONE: return "NONE";
|
||||
case XCE_SCHED_OP_UPDATE_CONFIG: return "UPDATE_CONFIG";
|
||||
case XCE_SCHED_OP_UPDATE_JOB: return "UPDATE_JOB";
|
||||
case XCE_SCHED_OP_JSTATE_CHANGE: return "JSTATE_CHANGE";
|
||||
case XCE_SCHED_OP_UPDATE_WORKER: return "UPDATE_WORKER";
|
||||
case XCE_SCHED_OP_UPDATE_QUEUE: return "UPDATE_QUEUE";
|
||||
case XCE_SCHED_OP_UPDATE_RESOURCES: return "UPDATE_RESOURCES";
|
||||
case XCE_SCHED_OP_UPDATE_RESVALS_FULL: return "UPDATE_RESVALS_FULL";
|
||||
case XCE_SCHED_OP_UPDATE_RESVALS: return "UPDATE_RESVALS";
|
||||
case XCE_SCHED_OP_JOB_RUSAGE: return "JOB_RUSAGE";
|
||||
case XCE_SCHED_OP_UPDATE_JOB_FIELDS: return "UPDATE_JOB_FIELDS";
|
||||
default: break;
|
||||
}
|
||||
|
||||
snprintf(buf, sizeof(buf), "UNKNOWN(%d)", (int)op);
|
||||
return buf;
|
||||
}
|
||||
|
||||
int sched_update(xce_scheduler_t* sched)
|
||||
{
|
||||
dataq_t* q = NULL;
|
||||
|
|
@ -646,6 +762,9 @@ int sched_update(xce_scheduler_t* sched)
|
|||
rc = sched_update_jobs(sched, req->u.jobupdate.njobs,
|
||||
req->u.jobupdate.jobs);
|
||||
break;
|
||||
case XCE_SCHED_OP_UPDATE_JOB_FIELDS:
|
||||
rc = sched_update_existing_job(sched, req->u.jobfieldsupdate.job);
|
||||
break;
|
||||
case XCE_SCHED_OP_UPDATE_WORKER:
|
||||
rc = sched_update_workers(sched, req->u.workerupdate.nworkers,
|
||||
req->u.workerupdate.workers);
|
||||
|
|
@ -654,6 +773,85 @@ int sched_update(xce_scheduler_t* sched)
|
|||
rc = sched_job_state_change(sched, req->u.job_state_change.jobid,
|
||||
req->u.job_state_change.state);
|
||||
break;
|
||||
case XCE_SCHED_OP_UPDATE_QUEUE:
|
||||
rc = sched_update_existing_queue(sched, req->u.queueupdate.queue);
|
||||
break;
|
||||
case XCE_SCHED_OP_UPDATE_RESOURCES: {
|
||||
int i;
|
||||
for (i = 0; i < req->u.resourceupdate.nresources; i++) {
|
||||
xce_resource_t* r = req->u.resourceupdate.resources[i];
|
||||
if (r == NULL) continue;
|
||||
xce_resource_t* existing = makeResource(sched->resmgr, r);
|
||||
if (existing == NULL) {
|
||||
/* EEXIST: update-in-place on existing resource (add-or-update) */
|
||||
xce_resource_t* found = NULL;
|
||||
if (key_find(sched->resmgr, r->name, (void**)&found) == 0
|
||||
&& found != NULL) {
|
||||
/* Copy mutable integer fields; desc/name are identity,
|
||||
* distributions/hbased_defined are deep-structs. */
|
||||
found->valtype = r->valtype;
|
||||
found->ordtype = r->ordtype;
|
||||
found->flags = r->flags;
|
||||
found->interval = r->interval;
|
||||
WARN("%s updated existing resource %s", __func__, r->name);
|
||||
} else {
|
||||
WARN("%s makeResource failed for %s and lookup not found",
|
||||
__func__, r->name);
|
||||
}
|
||||
}
|
||||
}
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
case XCE_SCHED_OP_UPDATE_RESVALS_FULL: {
|
||||
int i;
|
||||
for (i = 0; i < req->u.resvalfullupdate.nvals; i++) {
|
||||
xce_resval_t* rv = req->u.resvalfullupdate.vals[i];
|
||||
xce_resource_t* r = NULL;
|
||||
if (rv == NULL) {
|
||||
continue;
|
||||
}
|
||||
if (key_find(sched->resmgr, rv->name, (void**)&r) < 0
|
||||
|| r == NULL) {
|
||||
WARN("%s unknown resource %s in resvals update", __func__,
|
||||
rv->name);
|
||||
continue;
|
||||
}
|
||||
zarray_put(&sched->sharedInst, r->idx, rv->val);
|
||||
}
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
case XCE_SCHED_OP_UPDATE_RESVALS: {
|
||||
int i;
|
||||
xce_resval_list_t* resvals = req->u.resvalupdate.resvals;
|
||||
for (i = 0; i < resvals->count; i++) {
|
||||
xce_resval_t* rv = resvals->vals[i];
|
||||
xce_sched_worker_t* w = NULL;
|
||||
xce_resource_t* r = NULL;
|
||||
if (rv == NULL) continue;
|
||||
w = sched_get_worker(sched, rv->host);
|
||||
if (w == NULL) continue;
|
||||
if (key_find(sched->resmgr, rv->name, (void**)&r) < 0 || r == NULL) {
|
||||
WARN("%s unknown resource %s in per-worker resvals update",
|
||||
__func__, rv->name);
|
||||
continue;
|
||||
}
|
||||
zarray_put(&w->resvals, r->idx, rv->val);
|
||||
}
|
||||
rc = 0;
|
||||
break;
|
||||
}
|
||||
case XCE_SCHED_OP_JOB_RUSAGE:
|
||||
/* Store the rusage report ON the job (Req 6 / bug-765 G-5). On
|
||||
* success the job takes ownership; clear the request pointer so the
|
||||
* killer does not free it. On failure (-ENOENT/-EINVAL) ownership
|
||||
* stays with the request and the killer frees it. */
|
||||
rc = sched_apply_job_rusage(sched, req->u.job_rusage.rusages);
|
||||
if (rc == 0) {
|
||||
req->u.job_rusage.rusages = NULL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
rc = -EINVAL;
|
||||
break;
|
||||
|
|
@ -662,7 +860,8 @@ int sched_update(xce_scheduler_t* sched)
|
|||
sched_pend_update_killer(it);
|
||||
|
||||
if (rc < 0) {
|
||||
/* something wrong? */
|
||||
WARN("%s: op %s failed: %s (%d)", __func__,
|
||||
sched_op_name(req->op), strerror(-rc), rc);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -775,7 +974,8 @@ static int sched_account_jobs(xce_scheduler_t* sched)
|
|||
void sched_begin_session(xce_scheduler_t* sched)
|
||||
{
|
||||
atomic_store_explicit(&sched->session_active, 1, memory_order_release);
|
||||
atomic_store_explicit(&sched->staging_merge_complete, 0, memory_order_release);
|
||||
atomic_store_explicit(&sched->staging_merge_complete, 0,
|
||||
memory_order_release);
|
||||
|
||||
/* Allocate a fresh memoisation context sized to the current interned-node
|
||||
* population. sched_reset_workers (called below) frees each worker's
|
||||
|
|
@ -791,7 +991,8 @@ void sched_begin_session(xce_scheduler_t* sched)
|
|||
sched_account_jobs(sched);
|
||||
|
||||
/* Staging merge done — sched_end_session's GC guard reads this field. */
|
||||
atomic_store_explicit(&sched->staging_merge_complete, 1, memory_order_release);
|
||||
atomic_store_explicit(&sched->staging_merge_complete, 1,
|
||||
memory_order_release);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -807,8 +1008,7 @@ void sched_end_session(xce_scheduler_t* sched)
|
|||
/* Guard: GC requires the evaluation pipeline to be idle */
|
||||
if (sched_eval_in_progress(sched) || !sched_staging_merge_complete(sched)) {
|
||||
TRACE("%s: GC skipped — eval_in_progress=%d staging_merge_complete=%d",
|
||||
__func__,
|
||||
sched_eval_in_progress(sched),
|
||||
__func__, sched_eval_in_progress(sched),
|
||||
sched_staging_merge_complete(sched));
|
||||
return;
|
||||
}
|
||||
|
|
@ -830,8 +1030,8 @@ void sched_end_session(xce_scheduler_t* sched)
|
|||
}
|
||||
if (njobs >= cap) {
|
||||
int newcap = cap * 2;
|
||||
xce_sched_job_t** tmp = realloc(live_jobs,
|
||||
newcap * sizeof(*live_jobs));
|
||||
xce_sched_job_t** tmp
|
||||
= realloc(live_jobs, newcap * sizeof(*live_jobs));
|
||||
if (tmp == NULL) {
|
||||
free(live_jobs);
|
||||
live_jobs = NULL;
|
||||
|
|
@ -851,7 +1051,8 @@ void sched_end_session(xce_scheduler_t* sched)
|
|||
|
||||
if (live_jobs == NULL) {
|
||||
/* OOM: cannot safely GC — skip to avoid sweeping nodes still in use */
|
||||
atomic_store_explicit(&sched->staging_merge_complete, 1, memory_order_release);
|
||||
atomic_store_explicit(&sched->staging_merge_complete, 1,
|
||||
memory_order_release);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -903,7 +1104,8 @@ void sched_end_session(xce_scheduler_t* sched)
|
|||
recompact_node_ids();
|
||||
free(live_jobs);
|
||||
|
||||
atomic_store_explicit(&sched->staging_merge_complete, 1, memory_order_release);
|
||||
atomic_store_explicit(&sched->staging_merge_complete, 1,
|
||||
memory_order_release);
|
||||
}
|
||||
|
||||
int sched_add_job(xce_scheduler_t* sched, xce_sched_job_t* j)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
scheduler_test_src = [
|
||||
'test.c'
|
||||
'test.c',
|
||||
]
|
||||
|
||||
scheduler_test_deps = [libxce_deps, librtti_dep, unity_dep, libxce_dep]
|
||||
|
|
@ -11,3 +11,30 @@ scheduler_test = executable('sched_test',
|
|||
include_directories:[incdir],
|
||||
dependencies: [scheduler_test_deps],
|
||||
install: false)
|
||||
|
||||
# White-box test for the notify single-job in-place update path
|
||||
# (sched_update_existing_job). It #includes ../src/schedobj.c directly, so it
|
||||
# is a standalone TU with its own main() — built as a separate executable.
|
||||
sched_job_update_test = executable('sched_job_update_test',
|
||||
['test_job_update.c'],
|
||||
include_directories:[incdir],
|
||||
dependencies: [scheduler_test_deps],
|
||||
install: false)
|
||||
|
||||
# White-box test for the notify/dataset enqueue handlers. It #includes
|
||||
# ../src/handler.c directly (enq_pend_update + the static handlers), so it is a
|
||||
# standalone TU with its own main() — built as a separate executable.
|
||||
sched_handlers_test = executable('sched_handlers_test',
|
||||
['test_handlers.c'],
|
||||
include_directories:[incdir],
|
||||
dependencies: [scheduler_test_deps],
|
||||
install: false)
|
||||
|
||||
# Register as meson tests so `meson test --suite scheduler` runs them all.
|
||||
# detect_leaks=0 matches the suite convention (fixtures are not torn down).
|
||||
test('scheduler test', scheduler_test, suite: 'scheduler',
|
||||
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
||||
test('scheduler job-update test', sched_job_update_test, suite: 'scheduler',
|
||||
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
||||
test('scheduler handlers test', sched_handlers_test, suite: 'scheduler',
|
||||
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <string.h>
|
||||
#include <stdatomic.h>
|
||||
#include <unity/unity.h>
|
||||
#include "helper.h"
|
||||
#include "xstring.h"
|
||||
|
||||
/* Pull resource.c into this TU so static helpers are visible for white-box testing.
|
||||
|
|
@ -15,6 +16,22 @@
|
|||
xce_sched_worker_t* sched_get_worker(xce_scheduler_t* s, char* n) { (void)s; (void)n; return NULL; }
|
||||
int plugin_prepare_allocation(void* s, xce_sched_ctx_t* c) { (void)s; (void)c; return 0; }
|
||||
int sched_ctx_get_uncomit_host(xvec_t** v, xce_sched_ctx_t* c, int i) { (void)v; (void)c; (void)i; return 0; }
|
||||
int plugin_queue_update(void* s, xce_sched_queue_t* q) { (void)s; (void)q; return 0; }
|
||||
xce_sched_queue_t* sched_get_queue(xce_scheduler_t* s, char* n) { (void)s; (void)n; return NULL; }
|
||||
|
||||
/* Stub: sched_update_existing_queue returns -EINVAL when updated is NULL,
|
||||
* -ENOENT when queue not found (sched_get_queue stub returns NULL).
|
||||
* Defined in schedobj.c; stubbed here for test linkage. */
|
||||
int sched_update_existing_queue(xce_scheduler_t* sched, xce_queue_t* updated)
|
||||
{
|
||||
if (updated == NULL) {
|
||||
return -EINVAL;
|
||||
}
|
||||
if (sched_get_queue(sched, updated->queue) == NULL) {
|
||||
return -ENOENT;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setUp() { return; }
|
||||
|
||||
|
|
@ -163,6 +180,7 @@ test_meet_host_selection_always_true_passes(void)
|
|||
int rc = sched_meet_host_selection(&sched, &w, &sel);
|
||||
TEST_ASSERT_EQUAL_INT(1, rc);
|
||||
|
||||
sched_worker_release_host_ctx(&w);
|
||||
zarray_fini(&w.resvals);
|
||||
gc_clear_marks();
|
||||
gc_sweep_select_nodes(NULL, 0);
|
||||
|
|
@ -190,6 +208,7 @@ test_meet_host_selection_always_false_fails(void)
|
|||
int rc = sched_meet_host_selection(&sched, &w, &sel);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
sched_worker_release_host_ctx(&w);
|
||||
zarray_fini(&w.resvals);
|
||||
gc_clear_marks();
|
||||
gc_sweep_select_nodes(NULL, 0);
|
||||
|
|
@ -305,6 +324,8 @@ test_meet_host_selection_numeric_match(void)
|
|||
|
||||
TEST_ASSERT_EQUAL_INT(0, sched_meet_host_selection(&sched, &w2, &sel));
|
||||
|
||||
sched_worker_release_host_ctx(&w8);
|
||||
sched_worker_release_host_ctx(&w2);
|
||||
zarray_fini(&w8.resvals);
|
||||
zarray_fini(&w2.resvals);
|
||||
gc_clear_marks();
|
||||
|
|
@ -362,6 +383,8 @@ test_meet_host_selection_string_match(void)
|
|||
|
||||
TEST_ASSERT_EQUAL_INT(0, sched_meet_host_selection(&sched, &wcpu, &sel));
|
||||
|
||||
sched_worker_release_host_ctx(&wgpu);
|
||||
sched_worker_release_host_ctx(&wcpu);
|
||||
zarray_fini(&wgpu.resvals);
|
||||
zarray_fini(&wcpu.resvals);
|
||||
gc_clear_marks();
|
||||
|
|
@ -371,6 +394,75 @@ test_meet_host_selection_string_match(void)
|
|||
sel_optimize_test_reset();
|
||||
}
|
||||
|
||||
/* ── Task 9.1: xce_notify_event_name ─────────────────────────────────── */
|
||||
|
||||
static void
|
||||
test_notify_event_name_known_values(void)
|
||||
{
|
||||
TEST_ASSERT_EQUAL_STRING("schedStartJob",
|
||||
xce_notify_event_name(NotifyData_data_schedStartJob));
|
||||
TEST_ASSERT_EQUAL_STRING("schedExitJob",
|
||||
xce_notify_event_name(NotifyData_data_schedExitJob));
|
||||
TEST_ASSERT_EQUAL_STRING("schedUpdateJob",
|
||||
xce_notify_event_name(NotifyData_data_schedUpdateJob));
|
||||
TEST_ASSERT_EQUAL_STRING("schedUpdateQueue",
|
||||
xce_notify_event_name(NotifyData_data_schedUpdateQueue));
|
||||
TEST_ASSERT_EQUAL_STRING("schedUpdateResvals",
|
||||
xce_notify_event_name(NotifyData_data_schedUpdateResvals));
|
||||
TEST_ASSERT_EQUAL_STRING("schedJobRusages",
|
||||
xce_notify_event_name(NotifyData_data_schedJobRusages));
|
||||
}
|
||||
|
||||
static void
|
||||
test_notify_event_name_unknown_value(void)
|
||||
{
|
||||
char* s = xce_notify_event_name(9999);
|
||||
TEST_ASSERT_NOT_NULL(s);
|
||||
/* must start with "UNKNOWN(" */
|
||||
TEST_ASSERT_EQUAL_INT(0, strncmp(s, "UNKNOWN(", 8));
|
||||
}
|
||||
|
||||
/* ── Task 9.1 (enum completeness): new xce_sched_op_t values compile ─── */
|
||||
|
||||
static void
|
||||
test_new_sched_op_enum_values_exist(void)
|
||||
{
|
||||
/* Compile-time presence check — if any enum value is missing, this
|
||||
* translation unit will fail to compile. */
|
||||
xce_sched_op_t ops[] = {
|
||||
XCE_SCHED_OP_UPDATE_QUEUE,
|
||||
XCE_SCHED_OP_UPDATE_RESOURCES,
|
||||
XCE_SCHED_OP_UPDATE_RESVALS_FULL,
|
||||
XCE_SCHED_OP_UPDATE_RESVALS,
|
||||
XCE_SCHED_OP_JOB_RUSAGE,
|
||||
};
|
||||
TEST_ASSERT_EQUAL_INT(5, (int)(sizeof(ops) / sizeof(ops[0])));
|
||||
}
|
||||
|
||||
/* ── Task 9.5: sched_update_existing_queue NULL guard ───────────────── */
|
||||
|
||||
static void
|
||||
test_sched_update_existing_queue_null_payload(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
memset(&sched, 0, sizeof(sched));
|
||||
/* NULL updated → -EINVAL without touching scheduler state */
|
||||
int rc = sched_update_existing_queue(&sched, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
|
||||
}
|
||||
|
||||
/* ── Task 9.8: dead symbols absent ──────────────────────────────────── */
|
||||
|
||||
static void
|
||||
test_dead_pushmsg_symbols_absent(void)
|
||||
{
|
||||
/* If sched_handle_jobpush or sched_handle_workerpush were present, they
|
||||
* would be included via ../src/handler.c — but handler.c cannot be
|
||||
* included here (requires generated controller.capnp.h). The build-time
|
||||
* link step is the definitive check; this test documents the intent. */
|
||||
TEST_PASS();
|
||||
}
|
||||
|
||||
#define TEST(x) \
|
||||
do { \
|
||||
if (check_test(&arguments, #x)) { \
|
||||
|
|
@ -394,6 +486,11 @@ int main(int argc, char* argv[])
|
|||
TEST(test_staging_merge_complete_gc_guard);
|
||||
TEST(test_meet_host_selection_numeric_match);
|
||||
TEST(test_meet_host_selection_string_match);
|
||||
TEST(test_notify_event_name_known_values);
|
||||
TEST(test_notify_event_name_unknown_value);
|
||||
TEST(test_new_sched_op_enum_values_exist);
|
||||
TEST(test_sched_update_existing_queue_null_payload);
|
||||
TEST(test_dead_pushmsg_symbols_absent);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
|
|
|
|||
190
src/scheduler/testsuite/test_handlers.c
Normal file
190
src/scheduler/testsuite/test_handlers.c
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
* test_handlers.c - white-box tests for the scheduler notify/dataset enqueue
|
||||
* handlers (bug-765 G-6, tasks 9.2 + 9.3). Pulls the real (static) handlers in
|
||||
* handler.c into this TU; enq_pend_update is local to handler.c, so we drive a
|
||||
* real sched->pend_updates dataq and assert the correct op was enqueued (or
|
||||
* skipped / -EINVAL) per each handler's contract.
|
||||
*/
|
||||
#define UNIT_TEST 1
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "dataq.h"
|
||||
#include "mylock.h"
|
||||
#include "scheduler.h"
|
||||
|
||||
/* Pull the real handlers under test into this TU. handler.c is self-contained
|
||||
* for the enqueue path: enq_pend_update and sched_job_state_change are both
|
||||
* static within it; the dispatchers call lib helpers (linked). The only
|
||||
* scheduler-internal extern handler.c references (in the config-init request
|
||||
* path, not exercised by these enqueue tests) is sched_init_config — stubbed. */
|
||||
#include "../src/handler.c"
|
||||
|
||||
int sched_init_config(xce_scheduler_t* s, xce_conf4sched_t* c)
|
||||
{
|
||||
(void)s; (void)c; return 0;
|
||||
}
|
||||
|
||||
void setUp(void) { return; }
|
||||
void tearDown(void)
|
||||
{
|
||||
printf("\n-----------------------------------------------------\n");
|
||||
return;
|
||||
}
|
||||
|
||||
static void fixture_init(xce_scheduler_t* sched)
|
||||
{
|
||||
memset(sched, 0, sizeof(*sched));
|
||||
mylock_init(&(sched->lock));
|
||||
sched->pend_updates[0] = (dataq_t*)calloc(1, sizeof(dataq_t));
|
||||
dataq_init(sched->pend_updates[0], NULL);
|
||||
sched->pend_updates[1] = (dataq_t*)calloc(1, sizeof(dataq_t));
|
||||
dataq_init(sched->pend_updates[1], NULL);
|
||||
}
|
||||
|
||||
/* Dequeue one pending request (NULL if the queue is empty) and return its op
|
||||
* via *op_out; frees the request shell (payloads are test-owned/leaked under
|
||||
* the suite's leaks-off convention). Returns 1 if an item was present. */
|
||||
static int pop_op(xce_scheduler_t* sched, xce_sched_op_t* op_out)
|
||||
{
|
||||
data_item_t* it = dataq_deq(sched->pend_updates[0], NULL);
|
||||
xce_sched_update_request_t* req;
|
||||
|
||||
if (it == NULL) {
|
||||
return 0;
|
||||
}
|
||||
req = ITPTR_TO_UPDATE_REQ(it);
|
||||
if (op_out != NULL) {
|
||||
*op_out = req->op;
|
||||
}
|
||||
free(req);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* ── 9.3: notify single-item handlers (NULL -> -EINVAL, valid -> enqueue) ── */
|
||||
|
||||
static void test_handle_update_job_enqueues_job_fields(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
xce_sched_op_t op = (xce_sched_op_t)-1;
|
||||
fixture_init(&sched);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, sched_handle_sched_update_job(&sched, NULL));
|
||||
TEST_ASSERT_EQUAL_INT(0, pop_op(&sched, NULL)); /* NULL enqueued nothing */
|
||||
|
||||
xce_job_t* job = (xce_job_t*)calloc(1, sizeof(*job));
|
||||
job->jobid = 42;
|
||||
TEST_ASSERT_EQUAL_INT(0, sched_handle_sched_update_job(&sched, job));
|
||||
TEST_ASSERT_EQUAL_INT(1, pop_op(&sched, &op));
|
||||
/* 5.6: notify job update takes the in-place UPDATE_JOB_FIELDS path, NOT
|
||||
* the dataset bulk-create UPDATE_JOB path. */
|
||||
TEST_ASSERT_EQUAL_INT(XCE_SCHED_OP_UPDATE_JOB_FIELDS, op);
|
||||
free(job);
|
||||
}
|
||||
|
||||
static void test_handle_update_queue_enqueues_queue(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
xce_sched_op_t op = (xce_sched_op_t)-1;
|
||||
fixture_init(&sched);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL,
|
||||
sched_handle_sched_update_queue(&sched, NULL));
|
||||
|
||||
xce_queue_t* q = (xce_queue_t*)calloc(1, sizeof(*q));
|
||||
TEST_ASSERT_EQUAL_INT(0, sched_handle_sched_update_queue(&sched, q));
|
||||
TEST_ASSERT_EQUAL_INT(1, pop_op(&sched, &op));
|
||||
TEST_ASSERT_EQUAL_INT(XCE_SCHED_OP_UPDATE_QUEUE, op);
|
||||
free(q);
|
||||
}
|
||||
|
||||
static void test_handle_job_rusage_enqueues_rusage(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
xce_sched_op_t op = (xce_sched_op_t)-1;
|
||||
fixture_init(&sched);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL,
|
||||
sched_handle_sched_job_rusage(&sched, NULL));
|
||||
|
||||
xce_job_rusages_t* ru = (xce_job_rusages_t*)calloc(1, sizeof(*ru));
|
||||
ru->jobid = 7;
|
||||
TEST_ASSERT_EQUAL_INT(0, sched_handle_sched_job_rusage(&sched, ru));
|
||||
TEST_ASSERT_EQUAL_INT(1, pop_op(&sched, &op));
|
||||
TEST_ASSERT_EQUAL_INT(XCE_SCHED_OP_JOB_RUSAGE, op);
|
||||
free(ru);
|
||||
}
|
||||
|
||||
static void test_handle_update_resvals_skips_empty_enqueues_nonempty(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
xce_sched_op_t op = (xce_sched_op_t)-1;
|
||||
xce_resval_list_t empty = { .count = 0, .vals = NULL };
|
||||
xce_resval_list_t full = { .count = 1, .vals = NULL };
|
||||
fixture_init(&sched);
|
||||
|
||||
/* NULL or count==0 -> skip (return 0, nothing enqueued) */
|
||||
TEST_ASSERT_EQUAL_INT(0, sched_handle_sched_update_resvals(&sched, NULL));
|
||||
TEST_ASSERT_EQUAL_INT(0,
|
||||
sched_handle_sched_update_resvals(&sched, &empty));
|
||||
TEST_ASSERT_EQUAL_INT(0, pop_op(&sched, NULL));
|
||||
|
||||
/* count>0 -> enqueue UPDATE_RESVALS */
|
||||
TEST_ASSERT_EQUAL_INT(0,
|
||||
sched_handle_sched_update_resvals(&sched, &full));
|
||||
TEST_ASSERT_EQUAL_INT(1, pop_op(&sched, &op));
|
||||
TEST_ASSERT_EQUAL_INT(XCE_SCHED_OP_UPDATE_RESVALS, op);
|
||||
}
|
||||
|
||||
/* ── 9.2: dataset handlers (skip on NULL/empty, enqueue on valid) ── */
|
||||
|
||||
static void test_handle_resources_skips_empty_enqueues_nonempty(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
xce_sched_op_t op = (xce_sched_op_t)-1;
|
||||
xce_resource_t* one[1];
|
||||
fixture_init(&sched);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, sched_handle_resources(&sched, 0, NULL));
|
||||
TEST_ASSERT_EQUAL_INT(0, pop_op(&sched, NULL));
|
||||
|
||||
one[0] = (xce_resource_t*)calloc(1, sizeof(xce_resource_t));
|
||||
TEST_ASSERT_EQUAL_INT(0, sched_handle_resources(&sched, 1, one));
|
||||
TEST_ASSERT_EQUAL_INT(1, pop_op(&sched, &op));
|
||||
TEST_ASSERT_EQUAL_INT(XCE_SCHED_OP_UPDATE_RESOURCES, op);
|
||||
free(one[0]);
|
||||
}
|
||||
|
||||
static void test_handle_resvals_full_skips_empty_enqueues_nonempty(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
xce_sched_op_t op = (xce_sched_op_t)-1;
|
||||
xce_resval_t* one[1];
|
||||
fixture_init(&sched);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, sched_handle_resvals(&sched, 0, NULL));
|
||||
TEST_ASSERT_EQUAL_INT(0, pop_op(&sched, NULL));
|
||||
|
||||
one[0] = (xce_resval_t*)calloc(1, sizeof(xce_resval_t));
|
||||
TEST_ASSERT_EQUAL_INT(0, sched_handle_resvals(&sched, 1, one));
|
||||
TEST_ASSERT_EQUAL_INT(1, pop_op(&sched, &op));
|
||||
TEST_ASSERT_EQUAL_INT(XCE_SCHED_OP_UPDATE_RESVALS_FULL, op);
|
||||
free(one[0]);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
(void)argc; (void)argv;
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_handle_update_job_enqueues_job_fields, 0);
|
||||
RUN_TEST(test_handle_update_queue_enqueues_queue, 0);
|
||||
RUN_TEST(test_handle_job_rusage_enqueues_rusage, 0);
|
||||
RUN_TEST(test_handle_update_resvals_skips_empty_enqueues_nonempty, 0);
|
||||
RUN_TEST(test_handle_resources_skips_empty_enqueues_nonempty, 0);
|
||||
RUN_TEST(test_handle_resvals_full_skips_empty_enqueues_nonempty, 0);
|
||||
return UNITY_END();
|
||||
}
|
||||
364
src/scheduler/testsuite/test_job_update.c
Normal file
364
src/scheduler/testsuite/test_job_update.c
Normal file
|
|
@ -0,0 +1,364 @@
|
|||
/*
|
||||
* test_job_update.c - white-box tests for the notify single-job in-place
|
||||
* update path (sched_update_existing_job), remediation of bug-765 / gap G-8+G-4.
|
||||
*
|
||||
* Strategy: pull the REAL sched_update_existing_job into this TU by including
|
||||
* schedobj.c, then drive it against a REAL sched->jobs kvmgr seeded with a
|
||||
* REAL xce_sched_job_t. The scheduler-internal externs schedobj.c references
|
||||
* but which are NOT under test (and live in other .c files not linked here)
|
||||
* are stubbed below. sched_get_job/sched_add_job semantics are exercised for
|
||||
* real via the kvmgr key_find/key_add path.
|
||||
*
|
||||
* Asserts:
|
||||
* - existing jobid : fields mutate IN PLACE (same object, runtime state kept)
|
||||
* - unknown jobid : -ENOENT and NO job created
|
||||
* - NULL payload : -EINVAL
|
||||
*/
|
||||
#define UNIT_TEST 1
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "kvmgr.h"
|
||||
#include "scheduler.h"
|
||||
#include "xstring.h"
|
||||
|
||||
/* Pull the real implementation under test into this TU. */
|
||||
#include "../src/schedobj.c"
|
||||
|
||||
/* Stubs for scheduler-internal externs schedobj.c references but which are
|
||||
* defined in other .c files not linked into this test. (make_sched_obj and
|
||||
* kill_sched_job are static in schedobj.c and come in via the #include.) */
|
||||
xce_sched_user_t* sched_get_user(xce_scheduler_t* s, char* n)
|
||||
{
|
||||
(void)s; (void)n; return NULL;
|
||||
}
|
||||
xce_sched_project_t* sched_get_project(xce_scheduler_t* s, char* n)
|
||||
{
|
||||
(void)s; (void)n; return NULL;
|
||||
}
|
||||
int plugin_job_update(void* s, xce_sched_job_t* j) { (void)s; (void)j; return 0; }
|
||||
int plugin_queue_update(void* s, xce_sched_queue_t* q) { (void)s; (void)q; return 0; }
|
||||
int plugin_job_rusage_update(void* s, xce_sched_job_t* j, xce_job_rusages_t* r)
|
||||
{
|
||||
(void)s; (void)j; (void)r; return 0;
|
||||
}
|
||||
xce_job_account_t* make_job_account(xce_job_alloc_t* a) { (void)a; return NULL; }
|
||||
void free_job_account(xce_job_account_t* a) { (void)a; }
|
||||
int sched_add_user(xce_scheduler_t* s, xce_sched_user_t* u) { (void)s; (void)u; return 0; }
|
||||
int sched_add_project(xce_scheduler_t* s, xce_sched_project_t* p) { (void)s; (void)p; return 0; }
|
||||
void sched_worker_release_host_ctx(xce_sched_worker_t* w) { (void)w; }
|
||||
int plugin_prepare_allocation(void* s, xce_sched_ctx_t* c) { (void)s; (void)c; return 0; }
|
||||
|
||||
/* sched_get_queue: a tiny by-name lookup over a fixed test queue set. */
|
||||
static xce_sched_queue_t* g_test_queues[8];
|
||||
static int g_test_nqueues;
|
||||
|
||||
xce_sched_queue_t* sched_get_queue(xce_scheduler_t* s, char* name)
|
||||
{
|
||||
int i;
|
||||
(void)s;
|
||||
if (name == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
for (i = 0; i < g_test_nqueues; i++) {
|
||||
if (g_test_queues[i] != NULL && g_test_queues[i]->name != NULL
|
||||
&& strcmp(g_test_queues[i]->name, name) == 0) {
|
||||
return g_test_queues[i];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* REAL kvmgr-backed job add/get - same rtti path the production code uses. */
|
||||
int sched_add_job(xce_scheduler_t* sched, xce_sched_job_t* j)
|
||||
{
|
||||
int rc;
|
||||
lock(sched->jobs, RTTI_LOCK_WRITE);
|
||||
rc = key_add(sched->jobs, (void*)j->jobid, j);
|
||||
unlock(sched->jobs);
|
||||
return rc;
|
||||
}
|
||||
|
||||
xce_sched_job_t* sched_get_job(xce_scheduler_t* sched, uint64_t jobid)
|
||||
{
|
||||
int rc;
|
||||
xce_sched_job_t* job = NULL;
|
||||
lock(sched->jobs, RTTI_LOCK_READ);
|
||||
rc = key_find(sched->jobs, (void*)jobid, (void**)&job);
|
||||
unlock(sched->jobs);
|
||||
if (rc <= 0) {
|
||||
return NULL;
|
||||
}
|
||||
return job;
|
||||
}
|
||||
|
||||
void setUp(void) { return; }
|
||||
void tearDown(void)
|
||||
{
|
||||
printf("\n-----------------------------------------------------\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Test fixture helpers */
|
||||
|
||||
static void fixture_init(xce_scheduler_t* sched)
|
||||
{
|
||||
kvmgr_params_t jobs_params = { .key_type = KVMGR_KEY_INT,
|
||||
.nslots = 59,
|
||||
.delta = 16,
|
||||
.datakiller = kill_sched_job };
|
||||
kvmgr_params_t res_params = { .key_type = KVMGR_KEY_STRING,
|
||||
.nslots = 16,
|
||||
.delta = 8,
|
||||
.datakiller = NULL };
|
||||
|
||||
memset(sched, 0, sizeof(*sched));
|
||||
resreq_init(1);
|
||||
xstring_table_init(1024 * 16);
|
||||
|
||||
sched->jobs = makeInstance(&xkvmgr_type, &jobs_params);
|
||||
sched->resmgr = makeInstance(&xkvmgr_type, &res_params);
|
||||
|
||||
g_test_nqueues = 0;
|
||||
memset(g_test_queues, 0, sizeof(g_test_queues));
|
||||
}
|
||||
|
||||
static xce_sched_queue_t* make_test_queue(const char* name, int32_t prio)
|
||||
{
|
||||
xce_sched_queue_t* q = (xce_sched_queue_t*)calloc(1, sizeof(*q));
|
||||
q->name = makeString((char*)name);
|
||||
q->priority = prio;
|
||||
g_test_queues[g_test_nqueues++] = q;
|
||||
return q;
|
||||
}
|
||||
|
||||
/* A seeded existing job carrying live runtime state we must not lose. */
|
||||
static xce_sched_job_t* seed_job(xce_scheduler_t* sched, uint64_t jobid)
|
||||
{
|
||||
xce_sched_job_t* job = (xce_sched_job_t*)calloc(1, sizeof(*job));
|
||||
job->jobid = jobid;
|
||||
job->state = JOB_STAT_PEND;
|
||||
job->priority = 1;
|
||||
job->resreq = (xce_resreq_t*)calloc(1, sizeof(*job->resreq));
|
||||
/* schedobj is live runtime state that MUST survive an in-place update */
|
||||
job->schedobj = (xce_sched_obj_t*)calloc(1, sizeof(*job->schedobj));
|
||||
sched_add_job(sched, job);
|
||||
return job;
|
||||
}
|
||||
|
||||
static xce_job_t* make_update_payload(uint64_t jobid, unsigned int state,
|
||||
const char* queue)
|
||||
{
|
||||
xce_job_t* j = (xce_job_t*)calloc(1, sizeof(*j));
|
||||
j->jobid = jobid;
|
||||
j->state = state;
|
||||
j->submit = (job_submit_data_t*)calloc(1, sizeof(*j->submit));
|
||||
if (queue != NULL) {
|
||||
j->submit->queue = makeString((char*)queue);
|
||||
}
|
||||
return j;
|
||||
}
|
||||
|
||||
/* Tests */
|
||||
|
||||
static void
|
||||
test_update_existing_job_mutates_in_place(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
fixture_init(&sched);
|
||||
|
||||
make_test_queue("normal", 5);
|
||||
xce_sched_job_t* job = seed_job(&sched, 42);
|
||||
xce_sched_obj_t* schedobj = job->schedobj; /* capture live runtime ptr */
|
||||
|
||||
xce_job_t* upd = make_update_payload(42, JOB_STAT_RUN, "normal");
|
||||
|
||||
int rc = sched_update_existing_job(&sched, upd);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
/* SAME object mutated in place, not replaced */
|
||||
TEST_ASSERT_EQUAL_PTR(job, sched_get_job(&sched, 42));
|
||||
/* mutable fields updated */
|
||||
TEST_ASSERT_EQUAL_UINT(JOB_STAT_RUN, job->state);
|
||||
TEST_ASSERT_EQUAL_PTR(g_test_queues[0], job->queue);
|
||||
TEST_ASSERT_EQUAL_INT(5, job->priority);
|
||||
/* live runtime state preserved (object identity, not a fresh calloc) */
|
||||
TEST_ASSERT_EQUAL_PTR(schedobj, job->schedobj);
|
||||
|
||||
/* upd->submit->queue is a makeString()-interned string owned by the
|
||||
* xstring table — not caller-freeable; only free the calloc'd structs. */
|
||||
free(upd->submit);
|
||||
free(upd);
|
||||
}
|
||||
|
||||
static void
|
||||
test_update_unknown_job_returns_enoent_no_create(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
fixture_init(&sched);
|
||||
|
||||
/* No job seeded for jobid 999 */
|
||||
xce_job_t* upd = make_update_payload(999, JOB_STAT_RUN, NULL);
|
||||
|
||||
int rc = sched_update_existing_job(&sched, upd);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-ENOENT, rc);
|
||||
/* And NO job was created via the update path */
|
||||
TEST_ASSERT_NULL(sched_get_job(&sched, 999));
|
||||
|
||||
free(upd->submit);
|
||||
free(upd);
|
||||
}
|
||||
|
||||
static void
|
||||
test_update_null_payload_returns_einval(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
fixture_init(&sched);
|
||||
|
||||
int rc = sched_update_existing_job(&sched, NULL);
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, rc);
|
||||
}
|
||||
|
||||
/* Job rusage storage (Req 6 / bug-765 G-5) */
|
||||
|
||||
static xce_job_rusages_t* make_rusages(uint64_t jobid)
|
||||
{
|
||||
/* count==0 / usages==NULL keeps free_job_rusages a clean structural free. */
|
||||
xce_job_rusages_t* r = (xce_job_rusages_t*)calloc(1, sizeof(*r));
|
||||
r->jobid = jobid;
|
||||
return r;
|
||||
}
|
||||
|
||||
static void
|
||||
test_rusage_stored_on_existing_job(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
fixture_init(&sched);
|
||||
|
||||
xce_sched_job_t* job = seed_job(&sched, 42);
|
||||
xce_job_rusages_t* ru = make_rusages(42);
|
||||
|
||||
int rc = sched_apply_job_rusage(&sched, ru);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
/* stored ON the job (ownership transferred to the job) */
|
||||
TEST_ASSERT_EQUAL_PTR(ru, job->rusages);
|
||||
/* the job object itself is unchanged identity */
|
||||
TEST_ASSERT_EQUAL_PTR(job, sched_get_job(&sched, 42));
|
||||
/* job owns ru now; kill_sched_job/free_job_rusages will release it */
|
||||
}
|
||||
|
||||
static void
|
||||
test_rusage_replace_frees_prior(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
fixture_init(&sched);
|
||||
|
||||
xce_sched_job_t* job = seed_job(&sched, 7);
|
||||
xce_job_rusages_t* ru1 = make_rusages(7);
|
||||
xce_job_rusages_t* ru2 = make_rusages(7);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, sched_apply_job_rusage(&sched, ru1));
|
||||
TEST_ASSERT_EQUAL_PTR(ru1, job->rusages);
|
||||
/* second report replaces the first (prior freed, no leak/crash) */
|
||||
TEST_ASSERT_EQUAL_INT(0, sched_apply_job_rusage(&sched, ru2));
|
||||
TEST_ASSERT_EQUAL_PTR(ru2, job->rusages);
|
||||
}
|
||||
|
||||
static void
|
||||
test_rusage_unknown_job_returns_enoent(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
fixture_init(&sched);
|
||||
|
||||
xce_job_rusages_t* ru = make_rusages(999); /* no such job */
|
||||
|
||||
int rc = sched_apply_job_rusage(&sched, ru);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-ENOENT, rc);
|
||||
/* on -ENOENT the caller retains ownership */
|
||||
free_JobRusages_ptr(&ru);
|
||||
}
|
||||
|
||||
static void
|
||||
test_rusage_null_returns_einval(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
fixture_init(&sched);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, sched_apply_job_rusage(&sched, NULL));
|
||||
}
|
||||
|
||||
/* Queue in-place update (Req 4 / task 9.5 — REAL, exercises the linked
|
||||
* schedobj.c sched_update_existing_queue, not a local stub) */
|
||||
|
||||
static void
|
||||
test_update_existing_queue_real(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
fixture_init(&sched);
|
||||
|
||||
xce_sched_queue_t* q = make_test_queue("q1", 1);
|
||||
|
||||
xce_queue_t upd;
|
||||
memset(&upd, 0, sizeof(upd));
|
||||
upd.queue = makeString("q1");
|
||||
upd.status = 2;
|
||||
upd.priority = 9;
|
||||
upd.accept_interval = 5;
|
||||
upd.sched_delay = 3;
|
||||
upd.resreq = NULL; /* skip the resreq makeString branch */
|
||||
|
||||
int rc = sched_update_existing_queue(&sched, &upd);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
TEST_ASSERT_EQUAL_UINT(2, q->status);
|
||||
TEST_ASSERT_EQUAL_INT(9, q->priority);
|
||||
TEST_ASSERT_EQUAL_INT(5, q->accept_interval);
|
||||
TEST_ASSERT_EQUAL_INT(3, q->sched_delay);
|
||||
}
|
||||
|
||||
static void
|
||||
test_update_unknown_queue_returns_enoent(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
fixture_init(&sched);
|
||||
|
||||
xce_queue_t upd;
|
||||
memset(&upd, 0, sizeof(upd));
|
||||
upd.queue = makeString("nope"); /* no such queue seeded */
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-ENOENT, sched_update_existing_queue(&sched, &upd));
|
||||
}
|
||||
|
||||
static void
|
||||
test_update_queue_null_returns_einval(void)
|
||||
{
|
||||
xce_scheduler_t sched;
|
||||
fixture_init(&sched);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT(-EINVAL, sched_update_existing_queue(&sched, NULL));
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
(void)argc; (void)argv;
|
||||
UNITY_BEGIN();
|
||||
RUN_TEST(test_update_existing_job_mutates_in_place, 0);
|
||||
RUN_TEST(test_update_unknown_job_returns_enoent_no_create, 0);
|
||||
RUN_TEST(test_update_null_payload_returns_einval, 0);
|
||||
RUN_TEST(test_rusage_stored_on_existing_job, 0);
|
||||
RUN_TEST(test_rusage_replace_frees_prior, 0);
|
||||
RUN_TEST(test_rusage_unknown_job_returns_enoent, 0);
|
||||
RUN_TEST(test_rusage_null_returns_einval, 0);
|
||||
RUN_TEST(test_update_existing_queue_real, 0);
|
||||
RUN_TEST(test_update_unknown_queue_returns_enoent, 0);
|
||||
RUN_TEST(test_update_queue_null_returns_einval, 0);
|
||||
return UNITY_END();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue