/* * test_queue_admin_control.c - bug-1938: admin queue control must reach * dispatch, identically on the normal-mode and sim-mode drivers. * * LSF semantics, which the controller's queue control (controller/queue.c) * follows: * - QUEUE_STAT_INACT_ADMIN ("inactive"): the queue dispatches nothing; * its pending jobs stay pending. * - QUEUE_STAT_CLOSED ("closed"): the queue accepts no new submissions, * but jobs already pending in it are still dispatched. * * The fixture has three queues (open, closed, inactive), one pending job in * each and a worker big enough for all three, so capacity never decides * the outcome: only the queue state does. Both drivers' fixtures are built * the way test_sim_contention_limit_driver_parity.c builds them (real * scheduler_srcs, real picker/allocator threads, the sim side bootstrapped * through sim_loop.c's own sim_bootstrap_* entry points), `static` fixtures * and exactly one RUN_TEST() (bug-1598/1632/1763 fixture-safety convention). */ #include #include #include #include #include #include #include "controller.capnp.h" #include "decision.h" #include "plugin.h" #include "preason.h" #include "resobj.h" #include "sched/types.h" #include "scheduler.h" #include "sim.h" #include "xstring.h" extern void sim_bootstrap_picker(xce_sched_sim_driver_t* sim); extern void sim_bootstrap_seqjob_allocator(xce_sched_sim_driver_t* sim); extern void sim_bootstrap_parjob_allocator(xce_sched_sim_driver_t* sim); #define WORKER_NAME "qctl_worker" #define QUEUE_OPEN_N "qctl_open" #define QUEUE_CLOSED_N "qctl_closed" #define QUEUE_INACT_N "qctl_inactive" #define TEST_RESREQ ((char*)"select[type==any] rusage[mem=1]") #define JOB_OPEN_ID ((uint64_t)193801ULL) #define JOB_CLOSED_ID ((uint64_t)193802ULL) #define JOB_INACT_ID ((uint64_t)193803ULL) void setUp(void) { return; } void tearDown(void) { return; } static void register_slots_resource(var resmgr) { xce_resource_t res; memset(&res, 0, sizeof(res)); res.name = strdup("slots"); res.valtype = XCE_VAL_NUMERIC; res.ordtype = XCE_ORD_DECR; res.flags = XCE_RESF_LIMIT | XCE_RESF_CONSUMABLE | XCE_RESF_RELEASE | XCE_RESF_DEFAULT_HOST; TEST_ASSERT_NOT_NULL(makeResource(resmgr, &res)); } static xce_sched_worker_t* make_worker(xce_sched_core_t* core, double slots) { xce_sched_worker_t* w = (xce_sched_worker_t*)calloc(1, sizeof(*w)); xce_resource_t* r = NULL; xce_val_t* val = NULL; int rc; TEST_ASSERT_NOT_NULL(w); w->name = strdup(WORKER_NAME); w->status = 0; w->idx = -1; zarray_init(&(w->resvals), 128, (zarray_item_killer_t)kill_sched_resval); zarray_init(&(w->resInUse), 128, (zarray_item_killer_t)kill_sched_resval); rc = key_find(core->resmgr, (void*)"slots", (void**)&r); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(r); val = (xce_val_t*)calloc(1, sizeof(*val)); TEST_ASSERT_NOT_NULL(val); val->which = Value_u_fvalue; val->u.fvalue = slots; zarray_put(&(w->resvals), r->idx, val); return w; } static void add_queue(xce_sched_core_t* core, xce_sched_plugin_t* picker, const char* name, int status) { xce_sched_queue_t* q = (xce_sched_queue_t*)calloc(1, sizeof(*q)); int rc; TEST_ASSERT_NOT_NULL(q); q->name = makeString((char*)name); q->status = status; rc = sched_add_queue(core, q); TEST_ASSERT_TRUE(rc >= 0); picker->picker_iface->on_queue_update(picker, q); } static void push_job(xce_sched_core_t* core, xce_scheduler_t* job_ctor_wrapper, xce_sched_plugin_t* picker, uint64_t jobid, const char* queue, uint64_t queue_time) { job_submit_data_t submit; xce_job_t j; xce_sched_job_t* job; int rc; memset(&submit, 0, sizeof(submit)); submit.queue = (char*)queue; submit.user = (char*)"qctl_user"; submit.project_name = (char*)"default"; submit.resreq = TEST_RESREQ; submit.mintasks = 1; submit.maxtasks = 1; memset(&j, 0, sizeof(j)); j.jobid = jobid; j.state = JOB_STAT_PEND; j.sub_time = queue_time; j.queue_time = queue_time; j.submit = &submit; job = make_sched_job(job_ctor_wrapper, &j); TEST_ASSERT_NOT_NULL(job); TEST_ASSERT_EQUAL_INT(XCE_RESREQ_PRIMARY, job->schedobj->tag); job->schedobj->u.primary->resreq->minTasks = 1; job->schedobj->u.primary->resreq->maxTasks = 1; rc = sched_add_job(core, job); TEST_ASSERT_TRUE(rc >= 0); picker->picker_iface->on_job_push(picker, job); } /* run_round - one full picker/allocator session; returns how many decisions * the session produced before its end-of-session NULL marker. Draining up * to the marker (instead of expecting a fixed count) keeps a wrong count a * plain assertion failure rather than a hang. */ static int run_round(xce_sched_core_t* core) { pthread_t picker_tid; pthread_t allocator_tid; xce_decision_t* d; int n = 0; int rc; rc = pthread_create(&picker_tid, NULL, sched_job_picker, core); TEST_ASSERT_EQUAL_INT(0, rc); rc = pthread_create(&allocator_tid, NULL, sched_allocator, core); TEST_ASSERT_EQUAL_INT(0, rc); rc = xce_core_go(core); TEST_ASSERT_EQUAL_INT(0, rc); for (;;) { d = NULL; cbuf_get(&(core->decisions), &d); if (d == NULL) { break; } n++; } rc = xce_core_end_session(core); TEST_ASSERT_EQUAL_INT(0, rc); return n; } static void populate(xce_sched_core_t* core, xce_scheduler_t* job_wrapper, xce_sched_plugin_t* picker) { int rc; add_queue(core, picker, QUEUE_OPEN_N, QUEUE_STAT_OPEN); add_queue(core, picker, QUEUE_CLOSED_N, QUEUE_STAT_CLOSED); add_queue(core, picker, QUEUE_INACT_N, QUEUE_STAT_INACT_ADMIN); rc = sched_add_worker(core, make_worker(core, 8.0)); TEST_ASSERT_TRUE(rc >= 0); push_job(core, job_wrapper, picker, JOB_OPEN_ID, QUEUE_OPEN_N, 1000ULL); push_job(core, job_wrapper, picker, JOB_CLOSED_ID, QUEUE_CLOSED_N, 1001ULL); push_job(core, job_wrapper, picker, JOB_INACT_ID, QUEUE_INACT_N, 1002ULL); } static void assert_outcome(xce_sched_core_t* core, int ndecisions, const char* mode) { char msg[160]; snprintf(msg, sizeof(msg), "%s: only the open and closed queues' jobs may be decided " "(an inactive queue dispatches nothing)", mode); TEST_ASSERT_EQUAL_INT_MESSAGE(2, ndecisions, msg); snprintf(msg, sizeof(msg), "%s: open queue job must dispatch", mode); TEST_ASSERT_EQUAL_UINT_MESSAGE(JOB_STAT_RUN, sched_get_job(core, JOB_OPEN_ID)->state, msg); snprintf(msg, sizeof(msg), "%s: closed queue job already pending must still dispatch", mode); TEST_ASSERT_EQUAL_UINT_MESSAGE(JOB_STAT_RUN, sched_get_job(core, JOB_CLOSED_ID)->state, msg); snprintf(msg, sizeof(msg), "%s: inactive queue job must stay pending", mode); TEST_ASSERT_EQUAL_UINT_MESSAGE(JOB_STAT_PEND, sched_get_job(core, JOB_INACT_ID)->state, msg); } static xce_scheduler_t g_normal; static xce_sched_sim_driver_t g_sim; static xce_scheduler_t g_sim_job_wrapper; static void test_inactive_queue_holds_jobs_closed_queue_dispatches(void) { xce_sched_plugin_t* fcfs_normal = NULL; xce_sched_plugin_t* fcfs_sim = NULL; int n_normal; int n_sim; int rc; /* normal-mode-shaped fixture */ memset(&g_normal, 0, sizeof(g_normal)); rc = xce_sched_core_init(&(g_normal.core)); TEST_ASSERT_EQUAL_INT(0, rc); register_slots_resource(g_normal.core.resmgr); fcfs_normal = builtin_fcfs_new_plugin(); TEST_ASSERT_NOT_NULL(fcfs_normal); rc = plugin_register(&g_normal, fcfs_normal); TEST_ASSERT_TRUE(rc >= 0); g_normal.core.job_pick_plugin = (char*)"builtin_fcfs"; rc = register_seqjob_allocator(&g_normal); TEST_ASSERT_TRUE(rc >= 0); rc = register_parjob_allocator(&g_normal); TEST_ASSERT_TRUE(rc >= 0); populate(&(g_normal.core), &g_normal, fcfs_normal); n_normal = run_round(&(g_normal.core)); /* sim-mode-shaped fixture */ memset(&g_sim, 0, sizeof(g_sim)); rc = xce_sched_core_init(&(g_sim.core)); TEST_ASSERT_EQUAL_INT(0, rc); register_slots_resource(g_sim.core.resmgr); sim_bootstrap_picker(&g_sim); rc = key_find(g_sim.core.plugins, "builtin_fcfs", (void**)&fcfs_sim); TEST_ASSERT_TRUE(rc >= 0); TEST_ASSERT_NOT_NULL(fcfs_sim); sim_bootstrap_seqjob_allocator(&g_sim); sim_bootstrap_parjob_allocator(&g_sim); memset(&g_sim_job_wrapper, 0, sizeof(g_sim_job_wrapper)); rc = xce_sched_core_init(&(g_sim_job_wrapper.core)); TEST_ASSERT_EQUAL_INT(0, rc); g_sim_job_wrapper.core.resmgr = g_sim.core.resmgr; g_sim_job_wrapper.core.workers = g_sim.core.workers; g_sim_job_wrapper.core.queues = g_sim.core.queues; populate(&(g_sim.core), &g_sim_job_wrapper, fcfs_sim); n_sim = run_round(&(g_sim.core)); assert_outcome(&(g_normal.core), n_normal, "normal mode"); assert_outcome(&(g_sim.core), n_sim, "sim mode"); } int main(void) { UNITY_BEGIN(); RUN_TEST(test_inactive_queue_holds_jobs_closed_queue_dispatches); return UNITY_END(); }