Only live's sched_init_config() (scheduler.c) registered the config's
named user and host groups into core.ugmgr/hgmgr. The simulator takes its
config from the replay snapshot (snapshot_load.c) and later pushes through
the core CONFIG_PUSH event (core_events.c); neither loaded groups, so a
limit whose users name a group ("ug1") never matched in sim, while the
same limit matched in live.
New xce_core_load_groups() (core.c) is the one loader, called by
sched_init_config(), the CONFIG_PUSH event and snapshot restore, each
before limit consumers are built. snapshot.c's field notes now say the
groups are rebuilt from the snapshot's config.
test_sim_group_limit_parity.c checks a ug1-scoped limit matches the same
job on both drivers and that CONFIG_PUSH alone registers ug1 and hg1.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
348 lines
12 KiB
C
348 lines
12 KiB
C
/*
|
|
* test_sim_group_limit_parity.c - bug-1943: the simulator must load the
|
|
* cluster's named user/host groups exactly as the live scheduler does, so
|
|
* a limit scoped to a user group matches the same jobs on both drivers.
|
|
*
|
|
* Live applies a pushed config through sched_init_config() (scheduler.c),
|
|
* which registers config->ugroups/hgroups into core.ugmgr/hgmgr before the
|
|
* limit plugin builds its consumers (bug-1784: a limit member "ug1" only
|
|
* resolves to the group if "ug1" is already registered). The simulator
|
|
* takes its config from the replay snapshot (xce_sched_core_load_
|
|
* snapshot(), snapshot_load.c), seeds the limit plugin from it
|
|
* (sim_bootstrap_limit_plugin()), and applies later pushes through the
|
|
* core's CONFIG_PUSH event (core_events.c).
|
|
*
|
|
* Part 1: one config carrying user group ug1 = {user1} and a host-scoped
|
|
* limit whose users are ["ug1"], applied the live way and the sim way; a
|
|
* job of user1 must match the limit (one usage instance) on both drivers.
|
|
* Part 2: the core's CONFIG_PUSH event alone must register ug1 and hg1.
|
|
*
|
|
* Fixture technique and safety conventions as in
|
|
* test_sim_contention_limit_driver_parity.c (real scheduler_srcs, real
|
|
* picker/allocator threads, `static` fixtures, exactly one RUN_TEST()).
|
|
*/
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <unity/unity.h>
|
|
|
|
#include "controller.capnp.h"
|
|
#include "decision.h"
|
|
#include "group.h"
|
|
#include "plugin.h"
|
|
#include "preason.h"
|
|
#include "resobj.h"
|
|
#include "sched/types.h"
|
|
#include "scheduler.h"
|
|
#include "sim.h"
|
|
#include "xcelimit.h"
|
|
#include "xstring.h"
|
|
|
|
extern void sim_bootstrap_picker(xce_sched_sim_driver_t* sim);
|
|
extern void sim_bootstrap_limit_plugin(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 "grplim_worker"
|
|
#define QUEUE_NAME "normal"
|
|
#define TEST_RESREQ ((char*)"select[type==any] rusage[mem=1]")
|
|
#define LIMIT_RESNAME "grplim_host_res"
|
|
#define LIMIT_NAME "grplim_ug1_limit"
|
|
#define JOB_USER "user1"
|
|
#define JOB_ID ((uint64_t)194301ULL)
|
|
|
|
void setUp(void) { return; }
|
|
void tearDown(void) { return; }
|
|
|
|
static void register_resources(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));
|
|
|
|
memset(&res, 0, sizeof(res));
|
|
res.name = strdup(LIMIT_RESNAME);
|
|
TEST_ASSERT_NOT_NULL(makeResource(resmgr, &res));
|
|
}
|
|
|
|
static void add_worker_resval(xce_sched_worker_t* w, xce_sched_core_t* core,
|
|
const char* resname, double value)
|
|
{
|
|
xce_resource_t* r = NULL;
|
|
xce_val_t* val = NULL;
|
|
int rc;
|
|
|
|
rc = key_find(core->resmgr, (void*)resname, (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 = value;
|
|
zarray_put(&(w->resvals), r->idx, val);
|
|
}
|
|
|
|
static void populate(xce_sched_core_t* core, xce_scheduler_t* job_wrapper,
|
|
xce_sched_plugin_t* picker)
|
|
{
|
|
xce_sched_queue_t* q = (xce_sched_queue_t*)calloc(1, sizeof(*q));
|
|
xce_sched_worker_t* w = (xce_sched_worker_t*)calloc(1, sizeof(*w));
|
|
job_submit_data_t submit;
|
|
xce_job_t j;
|
|
xce_sched_job_t* job;
|
|
int rc;
|
|
|
|
TEST_ASSERT_NOT_NULL(q);
|
|
q->name = makeString(QUEUE_NAME);
|
|
rc = sched_add_queue(core, q);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
picker->picker_iface->on_queue_update(picker, q);
|
|
|
|
TEST_ASSERT_NOT_NULL(w);
|
|
w->name = strdup(WORKER_NAME);
|
|
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);
|
|
add_worker_resval(w, core, "slots", 4.0);
|
|
add_worker_resval(w, core, LIMIT_RESNAME, 1000.0);
|
|
rc = sched_add_worker(core, w);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
|
|
memset(&submit, 0, sizeof(submit));
|
|
submit.queue = (char*)QUEUE_NAME;
|
|
submit.user = (char*)JOB_USER;
|
|
submit.project_name = (char*)"default";
|
|
submit.resreq = TEST_RESREQ;
|
|
submit.mintasks = 1;
|
|
submit.maxtasks = 1;
|
|
|
|
memset(&j, 0, sizeof(j));
|
|
j.jobid = JOB_ID;
|
|
j.state = JOB_STAT_PEND;
|
|
j.sub_time = 1000ULL;
|
|
j.queue_time = 1000ULL;
|
|
j.submit = &submit;
|
|
|
|
job = make_sched_job(job_wrapper, &j);
|
|
TEST_ASSERT_NOT_NULL(job);
|
|
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);
|
|
}
|
|
|
|
static void run_round(xce_sched_core_t* core)
|
|
{
|
|
pthread_t picker_tid;
|
|
pthread_t allocator_tid;
|
|
xce_decision_t* d;
|
|
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);
|
|
|
|
do {
|
|
d = NULL;
|
|
cbuf_get(&(core->decisions), &d);
|
|
} while (d != NULL);
|
|
|
|
rc = xce_core_end_session(core);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
}
|
|
|
|
static int count_usage_instances(xce_sched_core_t* core)
|
|
{
|
|
var handle = limit_get_usage_instances(core);
|
|
iterator_t it;
|
|
void* item = NULL;
|
|
int count = 0;
|
|
int rc;
|
|
|
|
if (handle == NULL) {
|
|
return 0;
|
|
}
|
|
|
|
rc = iter_make(&it, handle);
|
|
if (rc < 0) {
|
|
return 0;
|
|
}
|
|
|
|
for (;;) {
|
|
rc = iter_next(&it, &item);
|
|
if ((rc < 0) || (rc == RTTI_ITER_NO_DATA)) {
|
|
break;
|
|
}
|
|
count++;
|
|
}
|
|
|
|
iter_destroy(&it);
|
|
|
|
return count;
|
|
}
|
|
|
|
static xce_scheduler_t g_normal;
|
|
static xce_sched_sim_driver_t g_sim;
|
|
static xce_scheduler_t g_sim_job_wrapper;
|
|
static xce_sched_sim_driver_t g_evsim;
|
|
|
|
/* config pieces: `static` so the configs the cores take over outlive the
|
|
* test function (no core here is ever torn down or handed a newer config,
|
|
* so none of them is freed) */
|
|
static xce_group_config_t g_ug1 = {
|
|
.name = "ug1",
|
|
.members = { .count = 1, .data = (char*[]) { JOB_USER } }
|
|
};
|
|
static xce_group_config_t g_hg1 = {
|
|
.name = "hg1",
|
|
.members = { .count = 1, .data = (char*[]) { WORKER_NAME } }
|
|
};
|
|
static xce_group_config_t* g_ugroups[1] = { &g_ug1 };
|
|
static xce_group_config_t* g_hgroups[1] = { &g_hg1 };
|
|
static xce_group_config_t g_limit_users = {
|
|
.members = { .count = 1, .data = (char*[]) { "ug1" } }
|
|
};
|
|
static xce_limit_val_t g_limit_val = { .resname = (char*)LIMIT_RESNAME,
|
|
.percent = 1,
|
|
.val = 50.0 };
|
|
static xce_limit_config_t g_limit_cfg = { .name = (char*)LIMIT_NAME,
|
|
.users = &g_limit_users,
|
|
.val = &g_limit_val };
|
|
static xce_limit_config_t* g_limits[1] = { &g_limit_cfg };
|
|
|
|
static void init_config(xce_conf4sched_t* config)
|
|
{
|
|
memset(config, 0, sizeof(*config));
|
|
config->n_ugroups = 1;
|
|
config->ugroups = g_ugroups;
|
|
config->n_hgroups = 1;
|
|
config->hgroups = g_hgroups;
|
|
config->nlimits = 1;
|
|
config->limits = g_limits;
|
|
}
|
|
|
|
static xce_conf4sched_t g_config_normal;
|
|
static xce_conf4sched_t g_config_sim;
|
|
static xce_conf4sched_t g_config_ev;
|
|
|
|
static void test_user_group_limit_matches_in_sim_as_in_live(void)
|
|
{
|
|
xce_sched_plugin_t* fcfs_normal = NULL;
|
|
xce_sched_plugin_t* fcfs_sim = NULL;
|
|
sched_snapshot_data_t snap;
|
|
xce_core_event_t ev;
|
|
xce_group_t* g;
|
|
int n_normal;
|
|
int n_sim;
|
|
int rc;
|
|
|
|
init_config(&g_config_normal);
|
|
init_config(&g_config_sim);
|
|
init_config(&g_config_ev);
|
|
|
|
/* ---- Part 1, live: config applied by sched_init_config() ---- */
|
|
memset(&g_normal, 0, sizeof(g_normal));
|
|
rc = xce_sched_core_init(&(g_normal.core));
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
register_resources(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 = plugin_register(&g_normal, limit_new_plugin());
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
rc = register_seqjob_allocator(&g_normal);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
rc = register_parjob_allocator(&g_normal);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
|
|
rc = sched_init_config(&g_normal, &g_config_normal);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
populate(&(g_normal.core), &g_normal, fcfs_normal);
|
|
run_round(&(g_normal.core));
|
|
|
|
/* ---- Part 1, sim: config from the replay snapshot, then the same
|
|
* bootstrap order as sim_driver_run_body() (sim_loop.c) ---- */
|
|
memset(&g_sim, 0, sizeof(g_sim));
|
|
rc = xce_sched_core_init(&(g_sim.core));
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
register_resources(g_sim.core.resmgr);
|
|
|
|
memset(&snap, 0, sizeof(snap));
|
|
snap.config = &g_config_sim;
|
|
rc = xce_sched_core_load_snapshot(&(g_sim.core), &snap);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
|
|
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_limit_plugin(&g_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);
|
|
run_round(&(g_sim.core));
|
|
|
|
n_normal = count_usage_instances(&(g_normal.core));
|
|
n_sim = count_usage_instances(&(g_sim.core));
|
|
|
|
TEST_ASSERT_EQUAL_INT_MESSAGE(
|
|
1, n_normal,
|
|
"live: user1 is in ug1, so the ug1-scoped limit must match its job");
|
|
TEST_ASSERT_EQUAL_INT_MESSAGE(
|
|
n_normal, n_sim,
|
|
"sim must match the user-group-scoped limit exactly as live does");
|
|
|
|
/* ---- Part 2: the core CONFIG_PUSH event registers the groups ---- */
|
|
memset(&g_evsim, 0, sizeof(g_evsim));
|
|
rc = xce_sched_core_init(&(g_evsim.core));
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
memset(&ev, 0, sizeof(ev));
|
|
ev.type = XCE_CORE_EVENT_CONFIG_PUSH;
|
|
ev.u.config_push.config = &g_config_ev;
|
|
rc = xce_core_push_events(&(g_evsim.core), &ev, 1);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
|
|
g = get_group(g_evsim.core.ugmgr, (char*)"ug1");
|
|
TEST_ASSERT_NOT_NULL_MESSAGE(
|
|
g, "CONFIG_PUSH must register the config's user groups by name");
|
|
TEST_ASSERT_TRUE(is_in_group(g, (char*)JOB_USER) > 0);
|
|
g = get_group(g_evsim.core.hgmgr, (char*)"hg1");
|
|
TEST_ASSERT_NOT_NULL_MESSAGE(
|
|
g, "CONFIG_PUSH must register the config's host groups by name");
|
|
TEST_ASSERT_TRUE(is_in_group(g, (char*)WORKER_NAME) > 0);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
UNITY_BEGIN();
|
|
RUN_TEST(test_user_group_limit_matches_in_sim_as_in_live);
|
|
return UNITY_END();
|
|
}
|