Re-types plugin_job_election()/plugin_prepare_allocation()/ plugin_commit_decision() from (void* arg, ...) to (xce_sched_core_t* core, ...), iterating core->plugins directly instead of deriving it from a normal-mode-only driver struct -- mirroring plugin_begin_session()/plugin_end_session()'s already-completed migration. Adds a new, honestly-typed xce_sched_plugin_t.core field, set by both plugin_register() (normal mode) and plugin_register_core() (simulation mode, closing a gap left since bug-1825 where this path set nothing). The existing plugin->sched field and its plugins/fcfs.c legacy consumer are left untouched. Necessarily updates the 4 existing normal-mode call sites (scheduler.c, resource.c, schedobj.c) to pass &(...->core) -- a C signature migration cannot keep the build green without touching every caller in the same commit; each fix is a minimal argument wrap with no algorithm change, matching design.md's own Implementation Notes for this component. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RLSNJhNoULvVcUhXEFafQC
1314 lines
56 KiB
C
1314 lines
56 KiB
C
/*
|
|
* test_resource_accounting.c - Requirement 1.4 proof for the Core-Level
|
|
* Resource Accounting component (scheduler-sim-decision-parity spec, task
|
|
* 1.1; design.md's "Core-Level Resource Accounting" Unit Tests bullet):
|
|
* "produces identical resInUse/sharedInUse output whether called with a
|
|
* core obtained from xce_scheduler_t or from xce_sched_sim_driver_t."
|
|
*
|
|
* Task 1.1 itself is a pure signature relocation (xce_scheduler_t* ->
|
|
* xce_sched_core_t*) of sched_account_jobs()/sched_charge_account()/
|
|
* sched_uncharge_account()/sched_update_account()/
|
|
* sched_update_res_accounts()/sched_commit_job_alloc() -- body unchanged,
|
|
* every reference already `sched->core.*` before the move (confirmed via
|
|
* `grep "sched->" resource.c | grep -v "sched->core"` returning zero
|
|
* matches, design.md's Existing Architecture Analysis). Requirement 1.4 is
|
|
* nonetheless a genuinely NEW observable property worth its own test (not
|
|
* just "nothing broke"): now that these functions take xce_sched_core_t*
|
|
* directly, they are usable identically regardless of which driver-level
|
|
* container struct (xce_scheduler_t, the normal-mode driver's; or
|
|
* xce_sched_sim_driver_t, the simulation driver's, sim.h) embeds the core.
|
|
* This file drives the REAL sched_charge_account()/sched_uncharge_account()/
|
|
* sched_commit_job_alloc() (resource.c) against two independently
|
|
* initialized xce_sched_core_t instances -- one embedded in an
|
|
* xce_scheduler_t, one embedded in an xce_sched_sim_driver_t -- with
|
|
* identical worker/account state, and asserts byte-identical
|
|
* resInUse/sharedInUse results.
|
|
*
|
|
* sched_account_jobs() itself (scheduler.c) is NOT white-box #included here
|
|
* -- unlike this file's own sched_charge_account()/sched_commit_job_alloc()
|
|
* targets (resource.c, a lightweight, already-proven-includable file per
|
|
* test.c's own #include "../src/resource.c"), scheduler.c is a ~1400-line
|
|
* file with a much larger link-time dependency surface (controller-join,
|
|
* capnp decision publishing, HA candidate handling, picker/allocator thread
|
|
* lifecycle) that no existing test in this directory white-box #includes for
|
|
* exactly that reason (confirmed via grep across testsuite/ before writing
|
|
* this file). sched_account_jobs() itself is a thin, ~20-line wrapper
|
|
* (iterate core->jobs, call sched_charge_account() for each RUN-state job
|
|
* with a known allocation) with no accounting logic of its own -- its
|
|
* driver-ordering discipline (called at the right point in the round
|
|
* boundary, on both drivers) is already proven by
|
|
* test_driver_session_ordering.c (a spy, by design -- see that file's own
|
|
* header comment) and this feature's later tasks (2.x/3.x, sim_loop.c's own
|
|
* round-boundary integration). This file instead proves the actual
|
|
* accounting *body* -- the part design.md's Testing Strategy bullet is
|
|
* about -- via its real callees, exercised directly.
|
|
*
|
|
* White-box #include of ../src/core.c (for the real
|
|
* xce_sched_core_init()/sched_add_worker()/sched_get_worker() the fixture
|
|
* needs), ../src/resource.c (the functions under test), and
|
|
* ../src/schedobj.c (for the real sched_ctx_get_uncomit_host() that the
|
|
* new sched_ctx_uncommit_host() under test itself calls -- white-box
|
|
* #included here rather than reimplemented, the same convention
|
|
* test_picker_config_fields.c's own #include "../src/core.c" +
|
|
* #include "../src/schedobj.c" pairing already established in this
|
|
* directory "so it exercises the real functions, not a re-implementation
|
|
* of any of them"). None of the three needs core_clock.c/core_events.c/
|
|
* core_session.c/decision.c/plugin.c -- this test never calls
|
|
* xce_core_push_events()/xce_core_begin_session()/anything decision- or
|
|
* plugin-registration-related, only the plain add-worker/charge-account/
|
|
* uncommit-host entry points core.c/resource.c/schedobj.c themselves
|
|
* define. The kill_sched_* family (the kvmgr datakillers
|
|
* xce_sched_core_init() references) is now the REAL implementation
|
|
* pulled in from schedobj.c, not a stub -- only plugin_unregister remains
|
|
* a link-satisfying no-op (plugin.c is still not white-box #included).
|
|
* sched_worker_release_host_ctx() is a real (non-stub) symbol this pulls
|
|
* from resource.c itself, not from core_session.c -- unused here (this
|
|
* test never calls it), listed only because resource.c defines it.
|
|
* plugin_job_update()/plugin_queue_update()/plugin_job_rusage_update()
|
|
* below are new link-satisfying stubs for schedobj.c's OWN unrelated
|
|
* functions (sched_update_existing_queue()/sched_update_existing_job()/
|
|
* sched_apply_job_rusage()) that reference them (plugin.c, still not
|
|
* included) -- never actually invoked, since this file never calls any
|
|
* of those three schedobj.c functions.
|
|
*/
|
|
#include <errno.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include <unity/unity.h>
|
|
|
|
#include "resobj.h"
|
|
#include "scheduler.h"
|
|
#include "sim.h"
|
|
|
|
/* Pull in the real xce_sched_core_init()/sched_add_worker()/
|
|
* sched_get_worker() (core.c), sched_charge_account()/
|
|
* sched_uncharge_account()/sched_commit_job_alloc()/get_resvals()/
|
|
* sched_ctx_uncommit_host() (resource.c, the functions under test), and
|
|
* sched_ctx_get_uncomit_host() (schedobj.c) into this TU (white-box
|
|
* #include, same convention as every other file in this directory --
|
|
* see this file's own header comment for why schedobj.c specifically). */
|
|
#include "../src/core.c"
|
|
#include "../src/resource.c"
|
|
#include "../src/schedobj.c"
|
|
|
|
void plugin_unregister(void* arg) { (void)arg; }
|
|
|
|
/* Link-satisfying stub for resource.c's sched_try_alloc_on_host_() (its
|
|
* OWN callee plugin_prepare_allocation() lives in plugin.c, not white-box
|
|
* #included here; its other callee, sched_ctx_get_uncomit_host(), is now
|
|
* the real function pulled in from schedobj.c above). Never actually
|
|
* invoked: this file never calls sched_try_alloc_on_hosts()/
|
|
* sched_try_alloc_on_host_(), only sched_charge_account()/
|
|
* sched_uncharge_account()/sched_commit_job_alloc(). Same stub test.c's
|
|
* own #include "../src/resource.c" already establishes needing. */
|
|
int plugin_prepare_allocation(xce_sched_core_t* s, xce_sched_ctx_t* c)
|
|
{
|
|
(void)s;
|
|
(void)c;
|
|
return 0;
|
|
}
|
|
|
|
/* Link-satisfying stubs for schedobj.c's OWN unrelated functions
|
|
* (sched_update_existing_queue()/sched_update_existing_job()/
|
|
* sched_apply_job_rusage()) that reference plugin_job_update()/
|
|
* plugin_queue_update()/plugin_job_rusage_update() (plugin.c, not
|
|
* white-box #included here). Never actually invoked: this file never
|
|
* calls any of those three schedobj.c functions. */
|
|
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;
|
|
}
|
|
|
|
void setUp(void) { return; }
|
|
void tearDown(void) { return; }
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Fixture helpers */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* setup_core_with_worker - real xce_sched_core_init() (core.c) plus one
|
|
* real worker (sched_add_worker(), core.c), its resvals/resInUse zarrays
|
|
* freshly zarray_init()'d exactly the way a real worker join would leave
|
|
* them (sched_add_worker() itself does not do this -- callers own it, same
|
|
* as every other white-box fixture in this directory that adds a worker
|
|
* directly rather than through the JOBS_PUSH/WORKERS_PUSH event path).
|
|
*/
|
|
static void setup_core_with_worker(xce_sched_core_t* core,
|
|
const char* worker_name)
|
|
{
|
|
xce_sched_worker_t* w;
|
|
int rc;
|
|
|
|
rc = xce_sched_core_init(core);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
w = (xce_sched_worker_t*)calloc(1, sizeof(*w));
|
|
TEST_ASSERT_NOT_NULL(w);
|
|
|
|
w->name = strdup(worker_name);
|
|
TEST_ASSERT_NOT_NULL(w->name);
|
|
|
|
rc = zarray_init(&(w->resvals), 8, NULL);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
rc = zarray_init(&(w->resInUse), 8, NULL);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
rc = sched_add_worker(core, w);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
}
|
|
|
|
/*
|
|
* make_test_account - one shared-resource charge (residx `shared_residx`)
|
|
* plus one per-host charge (residx `host_residx`, against `worker_name`) --
|
|
* enough to exercise both of sched_update_account()'s two branches
|
|
* (core->sharedInUse and a specific worker's resInUse). residx is set
|
|
* non-negative directly (bypassing sched_update_res_accounts()'s
|
|
* resmgr-lookup branch entirely) -- this test targets the charge/uncharge
|
|
* arithmetic itself, not resource-name resolution, which is unrelated,
|
|
* already-existing logic this task's relocation does not touch.
|
|
*/
|
|
static xce_job_account_t*
|
|
make_test_account(const char* worker_name, int shared_residx,
|
|
double shared_used, double shared_reserved, int host_residx,
|
|
double host_used, double host_reserved)
|
|
{
|
|
xce_job_account_t* acct
|
|
= (xce_job_account_t*)calloc(1, sizeof(*acct));
|
|
TEST_ASSERT_NOT_NULL(acct);
|
|
|
|
acct->nres_shared = 1;
|
|
acct->accounts_shared = (xce_res_account_t**)calloc(
|
|
1, sizeof(*acct->accounts_shared));
|
|
TEST_ASSERT_NOT_NULL(acct->accounts_shared);
|
|
acct->accounts_shared[0]
|
|
= (xce_res_account_t*)calloc(1, sizeof(xce_res_account_t));
|
|
TEST_ASSERT_NOT_NULL(acct->accounts_shared[0]);
|
|
acct->accounts_shared[0]->residx = shared_residx;
|
|
acct->accounts_shared[0]->used = shared_used;
|
|
acct->accounts_shared[0]->reserved = shared_reserved;
|
|
|
|
acct->nhosts = 1;
|
|
acct->accounts = (xce_host_account_t**)calloc(1, sizeof(*acct->accounts));
|
|
TEST_ASSERT_NOT_NULL(acct->accounts);
|
|
acct->accounts[0]
|
|
= (xce_host_account_t*)calloc(1, sizeof(xce_host_account_t));
|
|
TEST_ASSERT_NOT_NULL(acct->accounts[0]);
|
|
acct->accounts[0]->host = strdup(worker_name);
|
|
acct->accounts[0]->naccount = 1;
|
|
acct->accounts[0]->accounts
|
|
= (xce_res_account_t**)calloc(1, sizeof(*acct->accounts[0]->accounts));
|
|
TEST_ASSERT_NOT_NULL(acct->accounts[0]->accounts);
|
|
acct->accounts[0]->accounts[0]
|
|
= (xce_res_account_t*)calloc(1, sizeof(xce_res_account_t));
|
|
TEST_ASSERT_NOT_NULL(acct->accounts[0]->accounts[0]);
|
|
acct->accounts[0]->accounts[0]->residx = host_residx;
|
|
acct->accounts[0]->accounts[0]->used = host_used;
|
|
acct->accounts[0]->accounts[0]->reserved = host_reserved;
|
|
|
|
return acct;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
/* Tests */
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
#define WORKER_NAME "worker-a"
|
|
#define SHARED_RESIDX 0
|
|
#define HOST_RESIDX 1
|
|
#define SHARED_USED 3.0
|
|
#define SHARED_RESERVED 1.5
|
|
#define HOST_USED 2.0
|
|
#define HOST_RESERVED 0.5
|
|
/* SHARED_USED + SHARED_RESERVED, HOST_USED + HOST_RESERVED */
|
|
#define EXPECTED_SHARED_CHARGE 4.5f
|
|
#define EXPECTED_HOST_CHARGE 2.5f
|
|
|
|
/*
|
|
* test_charge_account_identical_across_core_containers - Requirement 1.4:
|
|
* sched_charge_account()/sched_uncharge_account() (resource.c) produce
|
|
* byte-identical resInUse/sharedInUse results whether `core` came from an
|
|
* xce_scheduler_t (sched.core, the normal-mode driver's container) or an
|
|
* xce_sched_sim_driver_t (sim.core, the simulation driver's container) --
|
|
* the exact property that makes the simulation driver's build now able to
|
|
* call the same accounting service the normal-mode driver already used.
|
|
*/
|
|
static void test_charge_account_identical_across_core_containers(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_sched_sim_driver_t sim;
|
|
xce_job_account_t* acct;
|
|
xce_sched_worker_t* w_sched;
|
|
xce_sched_worker_t* w_sim;
|
|
float shared_sched[1];
|
|
float shared_sim[1];
|
|
float host_sched[1];
|
|
float host_sim[1];
|
|
int shared_idx[1] = { SHARED_RESIDX };
|
|
int host_idx[1] = { HOST_RESIDX };
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
memset(&sim, 0, sizeof(sim));
|
|
|
|
setup_core_with_worker(&(sched.core), WORKER_NAME);
|
|
setup_core_with_worker(&(sim.core), WORKER_NAME);
|
|
|
|
/* Same account struct fed to both cores -- sched_charge_account() never
|
|
* mutates it when residx is already non-negative (see
|
|
* make_test_account()'s own comment), so reuse is safe and keeps the
|
|
* two calls genuinely identical, not merely equal-by-construction. */
|
|
acct = make_test_account(WORKER_NAME, SHARED_RESIDX, SHARED_USED,
|
|
SHARED_RESERVED, HOST_RESIDX, HOST_USED,
|
|
HOST_RESERVED);
|
|
|
|
rc = sched_charge_account(&(sched.core), acct);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
rc = sched_charge_account(&(sim.core), acct);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
/* --- sharedInUse: identical across both containers, and correct --- */
|
|
get_resvals(&(sched.core.sharedInUse), 1, shared_idx, shared_sched);
|
|
get_resvals(&(sim.core.sharedInUse), 1, shared_idx, shared_sim);
|
|
TEST_ASSERT_EQUAL_FLOAT(EXPECTED_SHARED_CHARGE, shared_sched[0]);
|
|
TEST_ASSERT_EQUAL_FLOAT(shared_sched[0], shared_sim[0]);
|
|
|
|
/* --- per-worker resInUse: identical across both containers --- */
|
|
w_sched = sched_get_worker(&(sched.core), WORKER_NAME);
|
|
w_sim = sched_get_worker(&(sim.core), WORKER_NAME);
|
|
TEST_ASSERT_NOT_NULL(w_sched);
|
|
TEST_ASSERT_NOT_NULL(w_sim);
|
|
|
|
get_resvals(&(w_sched->resInUse), 1, host_idx, host_sched);
|
|
get_resvals(&(w_sim->resInUse), 1, host_idx, host_sim);
|
|
TEST_ASSERT_EQUAL_FLOAT(EXPECTED_HOST_CHARGE, host_sched[0]);
|
|
TEST_ASSERT_EQUAL_FLOAT(host_sched[0], host_sim[0]);
|
|
|
|
/* --- sched_uncharge_account() mirrors this identically too --- */
|
|
rc = sched_uncharge_account(&(sched.core), acct);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
rc = sched_uncharge_account(&(sim.core), acct);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
get_resvals(&(sched.core.sharedInUse), 1, shared_idx, shared_sched);
|
|
get_resvals(&(sim.core.sharedInUse), 1, shared_idx, shared_sim);
|
|
TEST_ASSERT_EQUAL_FLOAT(0.0f, shared_sched[0]);
|
|
TEST_ASSERT_EQUAL_FLOAT(shared_sched[0], shared_sim[0]);
|
|
|
|
get_resvals(&(w_sched->resInUse), 1, host_idx, host_sched);
|
|
get_resvals(&(w_sim->resInUse), 1, host_idx, host_sim);
|
|
TEST_ASSERT_EQUAL_FLOAT(0.0f, host_sched[0]);
|
|
TEST_ASSERT_EQUAL_FLOAT(host_sched[0], host_sim[0]);
|
|
}
|
|
|
|
/*
|
|
* test_commit_job_alloc_identical_across_core_containers - Requirement 1.4
|
|
* for sched_commit_job_alloc() specifically (the third relocated group
|
|
* member scheduler.c's sched_commit_decision() calls, design.md's own File
|
|
* Structure Plan entry for scheduler.c): both its no-op (job->accounts ==
|
|
* NULL) and its charging path behave identically regardless of which
|
|
* driver-level struct the core came from.
|
|
*/
|
|
static void test_commit_job_alloc_identical_across_core_containers(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_sched_sim_driver_t sim;
|
|
xce_sched_job_t job_sched;
|
|
xce_sched_job_t job_sim;
|
|
xce_job_account_t* acct;
|
|
float shared_sched[1];
|
|
float shared_sim[1];
|
|
int shared_idx[1] = { SHARED_RESIDX };
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
memset(&sim, 0, sizeof(sim));
|
|
memset(&job_sched, 0, sizeof(job_sched));
|
|
memset(&job_sim, 0, sizeof(job_sim));
|
|
|
|
setup_core_with_worker(&(sched.core), WORKER_NAME);
|
|
setup_core_with_worker(&(sim.core), WORKER_NAME);
|
|
|
|
/* --- job->accounts == NULL: documented no-op, identical on both --- */
|
|
job_sched.accounts = NULL;
|
|
job_sim.accounts = NULL;
|
|
|
|
rc = sched_commit_job_alloc(&(sched.core), &job_sched);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
rc = sched_commit_job_alloc(&(sim.core), &job_sim);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
get_resvals(&(sched.core.sharedInUse), 1, shared_idx, shared_sched);
|
|
get_resvals(&(sim.core.sharedInUse), 1, shared_idx, shared_sim);
|
|
TEST_ASSERT_EQUAL_FLOAT(0.0f, shared_sched[0]);
|
|
TEST_ASSERT_EQUAL_FLOAT(shared_sched[0], shared_sim[0]);
|
|
|
|
/* --- job->accounts set: charges through sched_charge_account(),
|
|
* identical on both --- */
|
|
acct = make_test_account(WORKER_NAME, SHARED_RESIDX,
|
|
SHARED_USED, SHARED_RESERVED,
|
|
HOST_RESIDX, HOST_USED,
|
|
HOST_RESERVED);
|
|
job_sched.accounts = acct;
|
|
job_sim.accounts = acct;
|
|
|
|
rc = sched_commit_job_alloc(&(sched.core), &job_sched);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
rc = sched_commit_job_alloc(&(sim.core), &job_sim);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
get_resvals(&(sched.core.sharedInUse), 1, shared_idx, shared_sched);
|
|
get_resvals(&(sim.core.sharedInUse), 1, shared_idx, shared_sim);
|
|
TEST_ASSERT_EQUAL_FLOAT(EXPECTED_SHARED_CHARGE, shared_sched[0]);
|
|
TEST_ASSERT_EQUAL_FLOAT(shared_sched[0], shared_sim[0]);
|
|
}
|
|
|
|
/*
|
|
* test_charge_account_skips_reconciling_or_unavail_worker_host_charge -
|
|
* bug-1799 fix: sched_charge_account()'s per-host resInUse charge must be
|
|
* skipped for a worker currently IS_WORKER_RECONCILING or IS_WORKER_UNAVAIL.
|
|
* core_reset_workers() (core_session.c, bug-1751) already skips RESETTING
|
|
* such a worker's resInUse every round; sched_account_jobs()'s own
|
|
* per-round re-derive pass reaches resInUse only through this same
|
|
* sched_charge_account()->sched_update_account() path, so without a
|
|
* matching skip here it re-added a full charge ON TOP of the not-reset
|
|
* total every round the worker stayed RECONCILING/UNAVAIL -- resInUse grew
|
|
* unboundedly and could exceed the resource's real capacity (live-observed:
|
|
* a 22-slot worker showing used=26.00). core->sharedInUse has no
|
|
* per-worker gating at all (its own reset, core_reset_shared_resval(), is
|
|
* unconditional) and must keep charging normally regardless of any one
|
|
* host's status.
|
|
*/
|
|
static void
|
|
test_charge_account_skips_reconciling_or_unavail_worker_host_charge(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_job_account_t* acct;
|
|
xce_sched_worker_t* w;
|
|
float shared_val[1];
|
|
float host_val[1];
|
|
int shared_idx[1] = { SHARED_RESIDX };
|
|
int host_idx[1] = { HOST_RESIDX };
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
setup_core_with_worker(&(sched.core), WORKER_NAME);
|
|
|
|
w = sched_get_worker(&(sched.core), WORKER_NAME);
|
|
TEST_ASSERT_NOT_NULL(w);
|
|
|
|
acct = make_test_account(WORKER_NAME, SHARED_RESIDX, SHARED_USED,
|
|
SHARED_RESERVED, HOST_RESIDX, HOST_USED,
|
|
HOST_RESERVED);
|
|
|
|
/* --- RECONCILING: shared charges normally, per-host charge skipped --- */
|
|
w->status = XCE_WORKER_RECONCILING;
|
|
rc = sched_charge_account(&(sched.core), acct);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
get_resvals(&(sched.core.sharedInUse), 1, shared_idx, shared_val);
|
|
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
|
|
EXPECTED_SHARED_CHARGE, shared_val[0],
|
|
"shared resources have no per-worker gating and must still charge "
|
|
"while the host is RECONCILING");
|
|
|
|
get_resvals(&(w->resInUse), 1, host_idx, host_val);
|
|
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
|
|
0.0f, host_val[0],
|
|
"bug-1799: a RECONCILING worker's per-host resInUse must NOT be "
|
|
"charged -- core_reset_workers() already skips resetting it, so "
|
|
"charging on top would grow it every round it stays RECONCILING");
|
|
|
|
/* --- UNAVAIL: same per-host skip --- */
|
|
w->status = XCE_WORKER_UNAVAIL;
|
|
rc = sched_charge_account(&(sched.core), acct);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
get_resvals(&(w->resInUse), 1, host_idx, host_val);
|
|
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
|
|
0.0f, host_val[0],
|
|
"bug-1799: an UNAVAIL worker's per-host resInUse must NOT be "
|
|
"charged either -- the same reset-skip condition covers both bits");
|
|
|
|
/* --- status clears: normal charging resumes on the very next call --- */
|
|
w->status = 0;
|
|
rc = sched_charge_account(&(sched.core), acct);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
get_resvals(&(w->resInUse), 1, host_idx, host_val);
|
|
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
|
|
EXPECTED_HOST_CHARGE, host_val[0],
|
|
"once the worker's status bit clears, charging must resume "
|
|
"normally on the very next call -- this is a per-call skip, not a "
|
|
"permanent block");
|
|
}
|
|
|
|
/*
|
|
* resource-accounting.md's reservation-vs-actual-usage reconciliation for
|
|
* a REAL (gauge-style) resource, e.g. "mem" -- sched_reconcile_host_
|
|
* resource_usage() (the single-account-entry primitive) and
|
|
* sched_reconcile_job_resource_usage() (the rusages-report wrapper it's
|
|
* built for). Both are defined in resource.c, already white-box #included
|
|
* above.
|
|
*/
|
|
|
|
#define MEM_RESIDX 2
|
|
#define MEM_RESERVED_MB 4.0
|
|
|
|
/* make_mem_account - a single-host xce_job_account_t with one "mem"
|
|
* xce_res_account_t entry (residx MEM_RESIDX, reserved MEM_RESERVED_MB,
|
|
* used 0.0 -- a job that has just been dispatched and never had a rusage
|
|
* tick applied yet). resname is real (strdup'd), unlike make_test_account's
|
|
* residx-only entries above, since sched_reconcile_job_resource_usage()
|
|
* looks accounts up BY NAME. */
|
|
static xce_job_account_t* make_mem_account(const char* worker_name)
|
|
{
|
|
xce_job_account_t* acct = (xce_job_account_t*)calloc(1, sizeof(*acct));
|
|
TEST_ASSERT_NOT_NULL(acct);
|
|
|
|
acct->nhosts = 1;
|
|
acct->accounts = (xce_host_account_t**)calloc(1, sizeof(*acct->accounts));
|
|
TEST_ASSERT_NOT_NULL(acct->accounts);
|
|
acct->accounts[0]
|
|
= (xce_host_account_t*)calloc(1, sizeof(xce_host_account_t));
|
|
TEST_ASSERT_NOT_NULL(acct->accounts[0]);
|
|
acct->accounts[0]->host = strdup(worker_name);
|
|
acct->accounts[0]->naccount = 1;
|
|
acct->accounts[0]->accounts
|
|
= (xce_res_account_t**)calloc(1, sizeof(*acct->accounts[0]->accounts));
|
|
TEST_ASSERT_NOT_NULL(acct->accounts[0]->accounts);
|
|
acct->accounts[0]->accounts[0]
|
|
= (xce_res_account_t*)calloc(1, sizeof(xce_res_account_t));
|
|
TEST_ASSERT_NOT_NULL(acct->accounts[0]->accounts[0]);
|
|
acct->accounts[0]->accounts[0]->resname = strdup("mem");
|
|
acct->accounts[0]->accounts[0]->residx = MEM_RESIDX;
|
|
acct->accounts[0]->accounts[0]->used = 0.0;
|
|
acct->accounts[0]->accounts[0]->reserved = MEM_RESERVED_MB;
|
|
acct->accounts[0]->accounts[0]->allocated = MEM_RESERVED_MB;
|
|
|
|
return acct;
|
|
}
|
|
|
|
static xce_job_rusages_t* make_mem_rusages(float memory_rss_mb)
|
|
{
|
|
xce_job_rusages_t* r = (xce_job_rusages_t*)calloc(1, sizeof(*r));
|
|
TEST_ASSERT_NOT_NULL(r);
|
|
|
|
r->count = 1;
|
|
r->usages = (xce_rusage_t**)calloc(1, sizeof(*r->usages));
|
|
TEST_ASSERT_NOT_NULL(r->usages);
|
|
r->usages[0] = (xce_rusage_t*)calloc(1, sizeof(xce_rusage_t));
|
|
TEST_ASSERT_NOT_NULL(r->usages[0]);
|
|
r->usages[0]->resname = strdup("memory_rss");
|
|
r->usages[0]->value = memory_rss_mb;
|
|
|
|
return r;
|
|
}
|
|
|
|
static float get_worker_resinuse(xce_sched_worker_t* w, int residx)
|
|
{
|
|
int idx[1] = { residx };
|
|
float val[1];
|
|
|
|
get_resvals(&(w->resInUse), 1, idx, val);
|
|
return val[0];
|
|
}
|
|
|
|
/*
|
|
* test_reconcile_host_resource_usage_below_reservation_keeps_reservation -
|
|
* resource-accounting.md case (a): actual usage below the reservation must
|
|
* still charge the FULL reservation (used stays 0, since overage is 0).
|
|
*/
|
|
static void
|
|
test_reconcile_host_resource_usage_below_reservation_keeps_reservation(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_sched_worker_t* w;
|
|
xce_res_account_t acct_entry = { 0 };
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
setup_core_with_worker(&(sched.core), WORKER_NAME);
|
|
w = sched_get_worker(&(sched.core), WORKER_NAME);
|
|
TEST_ASSERT_NOT_NULL(w);
|
|
|
|
acct_entry.residx = MEM_RESIDX;
|
|
acct_entry.reserved = MEM_RESERVED_MB;
|
|
acct_entry.used = 0.0;
|
|
|
|
/* First charge: reservation only (mirrors sched_charge_account() at
|
|
* dispatch time -- used=0, reserved=4 -> pool charge 4). */
|
|
rc = sched_reconcile_host_resource_usage(&(sched.core), w, &acct_entry,
|
|
0.0);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
TEST_ASSERT_EQUAL_FLOAT(0.0, (float)acct_entry.used);
|
|
|
|
/* actual usage (1MB) is below the 4MB reservation -- used must stay 0,
|
|
* the pool must still show the full 4MB reservation charged, not the
|
|
* 1MB actually in use. */
|
|
rc = sched_reconcile_host_resource_usage(&(sched.core), w, &acct_entry,
|
|
1.0);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
TEST_ASSERT_EQUAL_FLOAT(0.0, (float)acct_entry.used);
|
|
}
|
|
|
|
/*
|
|
* test_reconcile_host_resource_usage_above_reservation_charges_actual -
|
|
* resource-accounting.md case (b): actual usage at/above the reservation
|
|
* must charge the REAL usage instead of the stale reservation.
|
|
*/
|
|
static void
|
|
test_reconcile_host_resource_usage_above_reservation_charges_actual(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_sched_worker_t* w;
|
|
xce_res_account_t acct_entry = { 0 };
|
|
int rc;
|
|
int idx[1] = { MEM_RESIDX };
|
|
float val[1];
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
setup_core_with_worker(&(sched.core), WORKER_NAME);
|
|
w = sched_get_worker(&(sched.core), WORKER_NAME);
|
|
TEST_ASSERT_NOT_NULL(w);
|
|
|
|
acct_entry.residx = MEM_RESIDX;
|
|
acct_entry.reserved = MEM_RESERVED_MB;
|
|
acct_entry.used = 0.0;
|
|
|
|
/* Seed the pool with the initial reservation charge, exactly as
|
|
* sched_charge_account() would at dispatch time. */
|
|
val[0] = (float)(acct_entry.used + acct_entry.reserved);
|
|
put_resvals(&(w->resInUse), 1, idx, val);
|
|
|
|
/* actual usage (6MB) EXCEEDS the 4MB reservation -- overage = 2MB, so
|
|
* used must become 2 and the pool must show 6 (reserved 4 + overage 2 =
|
|
* actual 6), not the stale 4MB reservation. */
|
|
rc = sched_reconcile_host_resource_usage(&(sched.core), w, &acct_entry,
|
|
6.0);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
TEST_ASSERT_EQUAL_FLOAT(2.0, (float)acct_entry.used);
|
|
TEST_ASSERT_EQUAL_FLOAT(6.0, get_worker_resinuse(w, MEM_RESIDX));
|
|
|
|
/* usage drops back to 3MB (still >= reserved? no: 3 < 4) -- back to
|
|
* charging the flat reservation, pool returns to 4. */
|
|
rc = sched_reconcile_host_resource_usage(&(sched.core), w, &acct_entry,
|
|
3.0);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
TEST_ASSERT_EQUAL_FLOAT(0.0, (float)acct_entry.used);
|
|
TEST_ASSERT_EQUAL_FLOAT(4.0, get_worker_resinuse(w, MEM_RESIDX));
|
|
}
|
|
|
|
/*
|
|
* test_reconcile_host_resource_usage_repeated_growth_applies_only_delta -
|
|
* confirms the pool is updated by DELTA each call, not a full recompute
|
|
* that would double-count a sibling resource's own charge in the same
|
|
* pool array (resource.c's own doc comment on this function).
|
|
*/
|
|
static void
|
|
test_reconcile_host_resource_usage_repeated_growth_applies_only_delta(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_sched_worker_t* w;
|
|
xce_res_account_t acct_entry = { 0 };
|
|
int slots_idx[1] = { 0 }; /* a sibling resource */
|
|
float slots_val[1] = { 7.0f };
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
setup_core_with_worker(&(sched.core), WORKER_NAME);
|
|
w = sched_get_worker(&(sched.core), WORKER_NAME);
|
|
TEST_ASSERT_NOT_NULL(w);
|
|
|
|
/* A sibling resource ("slots", a different residx) already charged in
|
|
* the same pool array -- must be untouched by every call below. */
|
|
put_resvals(&(w->resInUse), 1, slots_idx, slots_val);
|
|
|
|
acct_entry.residx = MEM_RESIDX;
|
|
acct_entry.reserved = MEM_RESERVED_MB;
|
|
acct_entry.used = 0.0;
|
|
|
|
rc = sched_reconcile_host_resource_usage(&(sched.core), w, &acct_entry,
|
|
5.0); /* overage 1 */
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
TEST_ASSERT_EQUAL_FLOAT(1.0, get_worker_resinuse(w, MEM_RESIDX));
|
|
|
|
rc = sched_reconcile_host_resource_usage(&(sched.core), w, &acct_entry,
|
|
9.0); /* overage 5 -- delta +4 */
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
TEST_ASSERT_EQUAL_FLOAT(5.0, get_worker_resinuse(w, MEM_RESIDX));
|
|
|
|
/* Sibling resource never touched by any of the above. */
|
|
TEST_ASSERT_EQUAL_FLOAT(7.0, get_worker_resinuse(w, 0));
|
|
}
|
|
|
|
static void test_reconcile_host_resource_usage_null_args_return_einval(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_res_account_t acct_entry = { 0 };
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
setup_core_with_worker(&(sched.core), WORKER_NAME);
|
|
|
|
TEST_ASSERT_EQUAL_INT(
|
|
-EINVAL, sched_reconcile_host_resource_usage(NULL, NULL, NULL, 0.0));
|
|
TEST_ASSERT_EQUAL_INT(-EINVAL,
|
|
sched_reconcile_host_resource_usage(
|
|
&(sched.core), NULL, &acct_entry, 0.0));
|
|
}
|
|
|
|
/*
|
|
* test_reconcile_host_resource_usage_skips_reconciling_or_unavail_worker -
|
|
* bug-1814 follow-up: sched_reconcile_host_resource_usage() is a THIRD
|
|
* w->resInUse-mutating path (alongside sched_charge_account()/sched_
|
|
* uncharge_account(), both already gated by bug-1801) that must respect the
|
|
* same "frozen while RECONCILING/UNAVAIL" invariant core_reset_workers()
|
|
* (bug-1751) and sched_update_account() (bug-1801) already share -- a live
|
|
* replay showed a job's "mem used" growing and never self-correcting until
|
|
* the job finished, root-caused to this exact gap: a rusage tick landing
|
|
* mid-resync kept mutating resInUse (and advancing acct_entry->used) on top
|
|
* of a pool a concurrent per-round rebuild was deliberately leaving alone.
|
|
*/
|
|
static void
|
|
test_reconcile_host_resource_usage_skips_reconciling_or_unavail_worker(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_sched_worker_t* w;
|
|
xce_res_account_t acct_entry = { 0 };
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
setup_core_with_worker(&(sched.core), WORKER_NAME);
|
|
w = sched_get_worker(&(sched.core), WORKER_NAME);
|
|
TEST_ASSERT_NOT_NULL(w);
|
|
|
|
acct_entry.residx = MEM_RESIDX;
|
|
acct_entry.reserved = MEM_RESERVED_MB;
|
|
acct_entry.used = 0.0;
|
|
|
|
/* --- RECONCILING: no mutation at all, not to the pool, not to
|
|
* acct_entry->used --- */
|
|
w->status = XCE_WORKER_RECONCILING;
|
|
rc = sched_reconcile_host_resource_usage(&(sched.core), w,
|
|
&acct_entry, 9.0);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
|
|
0.0f, get_worker_resinuse(w, MEM_RESIDX),
|
|
"bug-1814: a RECONCILING worker's resInUse must not be touched by a "
|
|
"rusage tick -- it is deliberately frozen until a clean per-round "
|
|
"rebuild can reconcile it, same invariant bug-1801 already "
|
|
"restores for the regular charge path");
|
|
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
|
|
0.0, (float)acct_entry.used,
|
|
"the account's own used baseline must also stay untouched while "
|
|
"skipped, so the eventual real reconcile call sees a clean delta");
|
|
|
|
/* --- UNAVAIL: same skip --- */
|
|
w->status = XCE_WORKER_UNAVAIL;
|
|
rc = sched_reconcile_host_resource_usage(&(sched.core), w,
|
|
&acct_entry, 9.0);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
|
|
0.0f, get_worker_resinuse(w, MEM_RESIDX),
|
|
"bug-1814: an UNAVAIL worker's resInUse must not be touched either");
|
|
|
|
/* --- status clears: reconciliation resumes normally on the very next
|
|
* call, not a permanent block --- */
|
|
w->status = 0;
|
|
rc = sched_reconcile_host_resource_usage(&(sched.core), w,
|
|
&acct_entry, 9.0);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
TEST_ASSERT_EQUAL_FLOAT_MESSAGE(
|
|
5.0, (float)acct_entry.used,
|
|
"overage = 9 - 4 reserved = 5, applied on the first call once the "
|
|
"worker is no longer RECONCILING/UNAVAIL");
|
|
TEST_ASSERT_EQUAL_FLOAT(5.0, get_worker_resinuse(w, MEM_RESIDX));
|
|
}
|
|
|
|
/*
|
|
* test_reconcile_job_resource_usage_* - the rusages-report wrapper: resname
|
|
* mapping ("memory_rss" on the wire -> "mem" in the account), the single-
|
|
* host scope, and the no-request no-op.
|
|
*/
|
|
|
|
static void
|
|
test_reconcile_job_resource_usage_maps_memory_rss_to_mem_account(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_sched_worker_t* w;
|
|
xce_job_account_t* jacct;
|
|
xce_job_rusages_t* rusages;
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
setup_core_with_worker(&(sched.core), WORKER_NAME);
|
|
w = sched_get_worker(&(sched.core), WORKER_NAME);
|
|
TEST_ASSERT_NOT_NULL(w);
|
|
|
|
jacct = make_mem_account(WORKER_NAME);
|
|
rusages = make_mem_rusages(7.0f); /* > 4MB reserved -> overage 3 */
|
|
|
|
/* Seed the pool with the initial dispatch-time reservation charge
|
|
* (mirrors what sched_charge_account()/sched_commit_job_alloc() would
|
|
* already have done before any rusage tick ever arrives -- the
|
|
* function under test only ever applies a DELTA on top of this, per
|
|
* its own doc comment, never a from-scratch total). */
|
|
{
|
|
int idx[1] = { MEM_RESIDX };
|
|
float val[1] = { (float)MEM_RESERVED_MB };
|
|
put_resvals(&(w->resInUse), 1, idx, val);
|
|
}
|
|
|
|
rc = sched_reconcile_job_resource_usage(&(sched.core), jacct, rusages);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
TEST_ASSERT_EQUAL_FLOAT(3.0,
|
|
(float)jacct->accounts[0]->accounts[0]->used);
|
|
TEST_ASSERT_EQUAL_FLOAT(7.0, get_worker_resinuse(w, MEM_RESIDX));
|
|
}
|
|
|
|
static void test_reconcile_job_resource_usage_skips_multihost_jobs(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_job_account_t jacct = { 0 };
|
|
xce_job_rusages_t* rusages = make_mem_rusages(99.0f);
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
setup_core_with_worker(&(sched.core), WORKER_NAME);
|
|
|
|
jacct.nhosts = 2; /* multi-host: no per-report host attribution possible
|
|
* (see sched_reconcile_job_resource_usage()'s own
|
|
* doc comment) -- must be a documented no-op. */
|
|
|
|
rc = sched_reconcile_job_resource_usage(&(sched.core), &jacct, rusages);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
}
|
|
|
|
/*
|
|
* test_reconcile_job_resource_usage_charges_unreserved_job_full_usage -
|
|
* resource-accounting.md's correction: a job with NO rusage[mem=...] at
|
|
* all is reserved=0, but that must NOT mean "nothing to charge". This
|
|
* codebase's admission check has no live OS read at decision time (see
|
|
* the steering doc) -- w->resInUse is the ONLY place a job's real memory
|
|
* footprint is ever reflected, so an unreserved job's actual usage must
|
|
* still be charged in full (max(0, actual) == actual), or the scheduler's
|
|
* own capacity tracking silently under-counts real consumption and can
|
|
* over-admit new jobs onto memory a live, unreserved job is genuinely
|
|
* sitting on.
|
|
*/
|
|
static void
|
|
test_reconcile_job_resource_usage_charges_unreserved_job_full_usage(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_sched_worker_t* w;
|
|
xce_job_account_t* jacct;
|
|
xce_job_rusages_t* rusages = make_mem_rusages(5.0f);
|
|
xce_resource_t mem_res = { 0 };
|
|
xce_resource_t* registered;
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
setup_core_with_worker(&(sched.core), WORKER_NAME);
|
|
w = sched_get_worker(&(sched.core), WORKER_NAME);
|
|
TEST_ASSERT_NOT_NULL(w);
|
|
|
|
/* "mem" must be a real, registered resource for ensure_host_account_
|
|
* by_resname()'s key_find() lookup to resolve a residx -- mirrors how
|
|
* a real cluster config would already have it registered long before
|
|
* any job ever runs. */
|
|
mem_res.name = strdup(SCHED_MEM_RESOURCE_NAME);
|
|
registered = makeResource(sched.core.resmgr, &mem_res);
|
|
TEST_ASSERT_NOT_NULL(registered);
|
|
|
|
/* A single-host account with NO "mem" entry at all -- the job never
|
|
* requested rusage[mem=...]. */
|
|
jacct = (xce_job_account_t*)calloc(1, sizeof(*jacct));
|
|
jacct->nhosts = 1;
|
|
jacct->accounts = (xce_host_account_t**)calloc(1, sizeof(*jacct->accounts));
|
|
jacct->accounts[0] = (xce_host_account_t*)calloc(1, sizeof(xce_host_account_t));
|
|
jacct->accounts[0]->host = strdup(WORKER_NAME);
|
|
jacct->accounts[0]->naccount = 0;
|
|
|
|
rc = sched_reconcile_job_resource_usage(&(sched.core), jacct, rusages);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
/* An account entry now exists (created on first use), reserved=0,
|
|
* used == the full actual usage, and the pool reflects it. */
|
|
TEST_ASSERT_EQUAL_INT(1, jacct->accounts[0]->naccount);
|
|
TEST_ASSERT_EQUAL_STRING(SCHED_MEM_RESOURCE_NAME,
|
|
jacct->accounts[0]->accounts[0]->resname);
|
|
TEST_ASSERT_EQUAL_FLOAT(0.0,
|
|
(float)jacct->accounts[0]->accounts[0]->reserved);
|
|
TEST_ASSERT_EQUAL_FLOAT(5.0, (float)jacct->accounts[0]->accounts[0]->used);
|
|
TEST_ASSERT_EQUAL_FLOAT(
|
|
5.0, get_worker_resinuse(w, jacct->accounts[0]->accounts[0]->residx));
|
|
}
|
|
|
|
static void
|
|
test_reconcile_job_resource_usage_noop_when_mem_not_a_registered_resource(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_job_account_t* jacct;
|
|
xce_job_rusages_t* rusages = make_mem_rusages(99.0f);
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
setup_core_with_worker(&(sched.core), WORKER_NAME);
|
|
/* Deliberately do NOT register "mem" into sched.core.resmgr -- a
|
|
* misconfigured cluster with no such resource defined at all. */
|
|
|
|
jacct = (xce_job_account_t*)calloc(1, sizeof(*jacct));
|
|
jacct->nhosts = 1;
|
|
jacct->accounts = (xce_host_account_t**)calloc(1, sizeof(*jacct->accounts));
|
|
jacct->accounts[0] = (xce_host_account_t*)calloc(1, sizeof(xce_host_account_t));
|
|
jacct->accounts[0]->host = strdup(WORKER_NAME);
|
|
jacct->accounts[0]->naccount = 0;
|
|
|
|
rc = sched_reconcile_job_resource_usage(&(sched.core), jacct, rusages);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
TEST_ASSERT_EQUAL_INT(0, jacct->accounts[0]->naccount);
|
|
}
|
|
|
|
static void test_reconcile_job_resource_usage_null_args_return_einval(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
setup_core_with_worker(&(sched.core), WORKER_NAME);
|
|
|
|
TEST_ASSERT_EQUAL_INT(-EINVAL,
|
|
sched_reconcile_job_resource_usage(NULL, NULL, NULL));
|
|
}
|
|
|
|
/*
|
|
* test_ctx_uncommit_host_reverses_provisional_debit - Requirement 4.2
|
|
* (scheduler-allocation spec, task 1.4): sched_ctx_uncommit_host()
|
|
* (resource.c, new) reverses exactly the provisional per-host resource
|
|
* debit an earlier successful host-claim made.
|
|
*
|
|
* Debits ctx->uncomitted.hosts[idx] directly via xvec_idval(...,
|
|
* XVEC_ADD_FLOAT) -- the exact call sched_try_alloc_on_host_() makes on a
|
|
* successful host claim (resource.c line ~544) -- rather than driving the
|
|
* full plugin_prepare_allocation()/sched_check_host_satisfy() chain, which
|
|
* needs a real worker/select-expression/resmgr fixture unrelated to what
|
|
* this rollback primitive itself does (this file's own convention: see
|
|
* make_test_account()'s residx-only shortcut above for the same rationale).
|
|
* Then calls the new sched_ctx_uncommit_host() and asserts the per-host
|
|
* uncommitted vector is back to its pre-debit (zero) baseline -- proving
|
|
* debit-then-reverse leaves provisional usage unchanged, this task's
|
|
* Observable.
|
|
*
|
|
* sched_ctx_get_uncomit_host() (the baseline/re-read get-or-create
|
|
* accessor called below) and host_uncommit_killer() (the zarray item
|
|
* killer passed to zarray_init() below) are both the REAL functions
|
|
* white-box #included from schedobj.c above, not reimplementations --
|
|
* host_uncommit_killer() is `static` in schedobj.c, but the #include
|
|
* pastes it into this same translation unit, so it resolves normally.
|
|
* This is the same "exercise the real functions, not a re-implementation
|
|
* of any of them" precedent test_picker_config_fields.c already
|
|
* established for combining core.c + schedobj.c in one white-box test.
|
|
*/
|
|
#define UNCOMMIT_HOST_IDX 3
|
|
#define UNCOMMIT_RESIDX 0
|
|
#define UNCOMMIT_DEBIT 2.5f
|
|
|
|
static void test_ctx_uncommit_host_reverses_provisional_debit(void)
|
|
{
|
|
xce_sched_ctx_t ctx;
|
|
xvec_t* uncomit = NULL;
|
|
xvec_idval_t committed;
|
|
float* data;
|
|
int rc;
|
|
|
|
memset(&ctx, 0, sizeof(ctx));
|
|
rc = zarray_init(&(ctx.uncomitted.hosts), 8,
|
|
(zarray_item_killer_t)host_uncommit_killer);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
rc = xvec_idval_init(&committed, 4);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
rc = xvec_idval_store(&committed, UNCOMMIT_RESIDX, UNCOMMIT_DEBIT);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
|
|
/* Baseline: a freshly created per-host uncommitted vector reads 0. */
|
|
rc = sched_ctx_get_uncomit_host(&uncomit, &ctx, UNCOMMIT_HOST_IDX);
|
|
TEST_ASSERT_EQUAL_INT(1, rc);
|
|
TEST_ASSERT_NOT_NULL(uncomit);
|
|
data = (float*)uncomit->data;
|
|
TEST_ASSERT_EQUAL_FLOAT(0.0f, data[UNCOMMIT_RESIDX]);
|
|
|
|
/* Debit: the exact commit call sched_try_alloc_on_host_() makes on a
|
|
* successful host claim. */
|
|
rc = xvec_idval(uncomit, &committed, XVEC_ADD_FLOAT);
|
|
TEST_ASSERT_EQUAL_INT(committed.count, rc);
|
|
TEST_ASSERT_EQUAL_FLOAT(UNCOMMIT_DEBIT, data[UNCOMMIT_RESIDX]);
|
|
|
|
/* Reverse: the new primitive under test. */
|
|
rc = sched_ctx_uncommit_host(&ctx, UNCOMMIT_HOST_IDX, &committed);
|
|
TEST_ASSERT_EQUAL_INT(committed.count, rc);
|
|
|
|
/* Back to the pre-debit baseline -- same vector instance, re-read. */
|
|
TEST_ASSERT_EQUAL_FLOAT(0.0f, data[UNCOMMIT_RESIDX]);
|
|
|
|
/* Same per-host vector instance is returned again on a second lookup
|
|
* (get-or-create semantics unchanged by the reversal). */
|
|
uncomit = NULL;
|
|
rc = sched_ctx_get_uncomit_host(&uncomit, &ctx, UNCOMMIT_HOST_IDX);
|
|
TEST_ASSERT_EQUAL_INT(1, rc);
|
|
TEST_ASSERT_EQUAL_FLOAT(0.0f, ((float*)uncomit->data)[UNCOMMIT_RESIDX]);
|
|
}
|
|
|
|
/*
|
|
* bug-1789/1790, limit-plugin-ownership refactor: sched_fill_limit_requests()
|
|
* (resource.c) computes, for each matched limit, how many units of the
|
|
* limit's own virtual resource (vres->vres) a job wants -- by reading how
|
|
* many units of the limit's UNDERLYING resource (vres->res) the job already
|
|
* requested. The write destination is one of alloc_req->shared/.host,
|
|
* chosen by vres->vres's own XCE_RESF_GLOBAL flag (set by instantiate_
|
|
* limit()->add_vres() from the same is_host_based_limit() call that put
|
|
* this limit in obj->glimits vs obj->hlimits in the first place).
|
|
*
|
|
* The read source depends on BOTH flags together: three of the four
|
|
* (scope, underlying-resource) combinations read straight out of alloc_req
|
|
* (already populated by whichever allocator plugin ran). This test proves
|
|
* the fourth, different-in-kind combination (bug-1789's actual reported
|
|
* case, generalized in bug-1790): a GLOBAL limit (write target
|
|
* alloc_req->shared) on a per-host underlying resource that IS a normal
|
|
* declared rusage -- e.g. "mem" (a future global mem cap, bug-1790's own
|
|
* regression coverage) -- alloc_req->host never holds a job-WIDE total for
|
|
* this (only per-host/per-bucket-specific data for the real per-host
|
|
* capacity check), so the want is computed directly from the job's own
|
|
* rusages instead, via calc_wanted_job_total_resval() (resreq.c) --
|
|
* PER_TASK: value * nslots, using nslots=3 here specifically to prove the
|
|
* job's WHOLE total is used, not any one host's/bucket's share (this is
|
|
* the limit-evaluation layer, allocator-agnostic -- no seqjob/parjob-
|
|
* specific test needed for this any more). "slots" is deliberately NOT
|
|
* used as the underlying resource here -- see the sibling test below for
|
|
* why it needs its own, separate coverage.
|
|
*/
|
|
static void
|
|
test_fill_limit_requests_global_limit_on_host_resource_reads_host(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_resource_t mem_res_in = { 0 };
|
|
xce_resource_t limit_res_in = { 0 };
|
|
xce_resource_t* mem_res;
|
|
xce_resource_t* limit_res;
|
|
xce_sched_alloc_req_t alloc_req;
|
|
xlist_t limits;
|
|
xce_vres_t vres;
|
|
xlist_t rusages;
|
|
rusage_value_t rv;
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(sched.core)));
|
|
|
|
/* "mem" -- deliberately NOT XCE_RESF_GLOBAL, matching the real
|
|
* makeResource() call every host-capacity resource in this codebase
|
|
* goes through, AND (unlike "slots") a resource that genuinely
|
|
* appears as a normal declared rusage entry when requested via
|
|
* -R "rusage[mem=...]" -- the real production shape this test's
|
|
* on-demand rusages lookup needs to match. */
|
|
mem_res_in.name = strdup(SCHED_MEM_RESOURCE_NAME);
|
|
mem_res = makeResource(sched.core.resmgr, &mem_res_in);
|
|
TEST_ASSERT_NOT_NULL(mem_res);
|
|
|
|
/* the LIMIT's own virtual resource -- GLOBAL, exactly as
|
|
* instantiate_limit()->add_vres() would flag it for a limit like
|
|
* ug1_global_mem (is_host_based_limit() == 0). */
|
|
limit_res_in.name = strdup("limit=ug1_global_mem");
|
|
limit_res_in.flags = XCE_RESF_GLOBAL;
|
|
limit_res = makeResource(sched.core.resmgr, &limit_res_in);
|
|
TEST_ASSERT_NOT_NULL(limit_res);
|
|
|
|
memset(&vres, 0, sizeof(vres));
|
|
vres.res = mem_res;
|
|
vres.vres = limit_res;
|
|
|
|
rc = xlist_init(&limits, 4);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
rc = xlist_store(&limits, &vres);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
|
|
/* the job's own declared "mem" rusage -- 2.0/task, 3 tasks total
|
|
* (nslots below), for an expected job-wide total of 6.0. */
|
|
rc = xlist_init(&rusages, 4);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
memset(&rv, 0, sizeof(rv));
|
|
rv.res = strdup(SCHED_MEM_RESOURCE_NAME);
|
|
rv.value = 2.0f;
|
|
rv.per_flag = PER_TASK;
|
|
rc = xlist_store(&rusages, &rv);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
|
|
rc = xvec_idval_init(&(alloc_req.shared), 8);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
rc = xvec_idval_init(&(alloc_req.host), 8);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
rc = sched_fill_limit_requests(&alloc_req, &limits, &rusages, 1, 3);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
TEST_ASSERT_EQUAL_INT(1, alloc_req.shared.count);
|
|
TEST_ASSERT_EQUAL_INT(limit_res->idx, ((int*)alloc_req.shared.idxs.data)[0]);
|
|
TEST_ASSERT_EQUAL_FLOAT(6.0f, ((float*)alloc_req.shared.vals.data)[0]);
|
|
/* .host is untouched -- the limit's own vres entry went to .shared,
|
|
* and this combination never reads/writes .host at all. */
|
|
TEST_ASSERT_EQUAL_INT(0, alloc_req.host.count);
|
|
|
|
xlist_fini(&rusages);
|
|
FREEUP(rv.res);
|
|
xlist_fini(&limits);
|
|
}
|
|
|
|
/*
|
|
* bug-1790 regression (found live, gitea#46): "slots" is NEVER a declared
|
|
* rusage entry -- seqjob_prepare_allocation()/parjob_prepare_allocation()
|
|
* derive its want directly from the job's own task count (1.0 for seqjob,
|
|
* always maxTasks==1; (float)ctx->nslots for parjob), via their own
|
|
* unconditional key_find("slots", ...) + store into alloc_req->host,
|
|
* regardless of phase, entirely outside the normal rusages loop. The
|
|
* general on-demand path (calc_wanted_job_total_resval(), keyed by
|
|
* scanning rusages for a matching entry) always misses for "slots" as a
|
|
* result -- confirmed live against a fresh deploy of limits.yaml's actual
|
|
* ug1_slots/ug1_normal_slots/limit1 (all global, slots-resname'd):
|
|
* matching worked, but every job's "wanted" silently stayed 0, so the caps
|
|
* never bound at all, reintroducing bug-1789's exact original symptom.
|
|
*
|
|
* sched_fill_limit_requests() (resource.c) now reads alloc_req->host FIRST
|
|
* for this combination -- since the allocator's own unconditional "add
|
|
* slots" step already puts exactly the job-wide total there, no name-based
|
|
* special case is needed, just reading the data that's already staged --
|
|
* and only falls back to the rusages-based computation when nothing is
|
|
* staged (any OTHER per-host resource). This test proves the "slots" path
|
|
* by pre-staging alloc_req->host exactly as the allocator plugins already
|
|
* do (value == the job's whole nslots, matching parjob's own (float)ctx->
|
|
* nslots store) and leaving rusages EMPTY -- the real production shape,
|
|
* unlike this test's own first version, which artificially added a
|
|
* "slots" rusages entry and masked this exact gap.
|
|
*/
|
|
static void test_fill_limit_requests_global_limit_on_slots_reads_host(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_resource_t slots_res_in = { 0 };
|
|
xce_resource_t limit_res_in = { 0 };
|
|
xce_resource_t* slots_res;
|
|
xce_resource_t* limit_res;
|
|
xce_sched_alloc_req_t alloc_req;
|
|
xlist_t limits;
|
|
xce_vres_t vres;
|
|
xlist_t rusages;
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(sched.core)));
|
|
|
|
slots_res_in.name = strdup("slots");
|
|
slots_res = makeResource(sched.core.resmgr, &slots_res_in);
|
|
TEST_ASSERT_NOT_NULL(slots_res);
|
|
|
|
limit_res_in.name = strdup("limit=ug1_normal_slots");
|
|
limit_res_in.flags = XCE_RESF_GLOBAL;
|
|
limit_res = makeResource(sched.core.resmgr, &limit_res_in);
|
|
TEST_ASSERT_NOT_NULL(limit_res);
|
|
|
|
memset(&vres, 0, sizeof(vres));
|
|
vres.res = slots_res;
|
|
vres.vres = limit_res;
|
|
|
|
rc = xlist_init(&limits, 4);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
rc = xlist_store(&limits, &vres);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
|
|
/* deliberately EMPTY -- "slots" never appears here in real production
|
|
* either. */
|
|
rc = xlist_init(&rusages, 4);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
rc = xvec_idval_init(&(alloc_req.shared), 8);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
rc = xvec_idval_init(&(alloc_req.host), 8);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
/* pre-staged into .host, exactly as seqjob/parjob_prepare_allocation()
|
|
* always do for "slots" regardless of phase. */
|
|
rc = xvec_idval_store(&(alloc_req.host), slots_res->idx, 4.0f);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
|
|
rc = sched_fill_limit_requests(&alloc_req, &limits, &rusages, 1, 4);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
TEST_ASSERT_EQUAL_INT(1, alloc_req.shared.count);
|
|
TEST_ASSERT_EQUAL_INT(limit_res->idx, ((int*)alloc_req.shared.idxs.data)[0]);
|
|
TEST_ASSERT_EQUAL_FLOAT(4.0f, ((float*)alloc_req.shared.vals.data)[0]);
|
|
/* .host stays exactly as pre-staged -- the limit's own vres entry went
|
|
* to .shared, nothing new was written to .host. */
|
|
TEST_ASSERT_EQUAL_INT(1, alloc_req.host.count);
|
|
|
|
xlist_fini(&rusages);
|
|
xlist_fini(&limits);
|
|
}
|
|
|
|
/*
|
|
* bug-1789 (already-working sibling case, unaffected by the fix): a
|
|
* HOST-scoped limit (its own vres NOT GLOBAL-flagged, so its write
|
|
* target is alloc_req->host) on a per-host resource (e.g. "mem", also
|
|
* not XCE_RESF_GLOBAL) reads from alloc_req->host too -- same vector as
|
|
* its own write target, exactly as before this fix.
|
|
*/
|
|
static void
|
|
test_fill_limit_requests_host_limit_on_host_resource_reads_host(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_resource_t mem_res_in = { 0 };
|
|
xce_resource_t limit_res_in = { 0 };
|
|
xce_resource_t* mem_res;
|
|
xce_resource_t* limit_res;
|
|
xce_sched_alloc_req_t alloc_req;
|
|
xlist_t limits;
|
|
xce_vres_t vres;
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(sched.core)));
|
|
|
|
mem_res_in.name = strdup(SCHED_MEM_RESOURCE_NAME);
|
|
mem_res = makeResource(sched.core.resmgr, &mem_res_in);
|
|
TEST_ASSERT_NOT_NULL(mem_res);
|
|
|
|
/* the LIMIT's own virtual resource -- NOT GLOBAL (host-scoped limit,
|
|
* is_host_based_limit() == 1); flags left at the struct's zero
|
|
* default is sufficient, mirroring add_vres()'s XCE_RESF_DEFAULT_HOST
|
|
* (not GLOBAL) branch. */
|
|
limit_res_in.name = strdup("limit=mem_cap");
|
|
limit_res = makeResource(sched.core.resmgr, &limit_res_in);
|
|
TEST_ASSERT_NOT_NULL(limit_res);
|
|
|
|
memset(&vres, 0, sizeof(vres));
|
|
vres.res = mem_res;
|
|
vres.vres = limit_res;
|
|
|
|
rc = xlist_init(&limits, 4);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
rc = xlist_store(&limits, &vres);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
|
|
rc = xvec_idval_init(&(alloc_req.shared), 8);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
rc = xvec_idval_init(&(alloc_req.host), 8);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
/* pre-staged, as calc_wanted_host_resvals() would have already done
|
|
* for a real "mem" rusage want. */
|
|
rc = xvec_idval_store(&(alloc_req.host), mem_res->idx, 3.0f);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
|
|
rc = sched_fill_limit_requests(&alloc_req, &limits, NULL, 0, 0);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
TEST_ASSERT_EQUAL_INT(2, alloc_req.host.count);
|
|
TEST_ASSERT_EQUAL_INT(limit_res->idx, ((int*)alloc_req.host.idxs.data)[1]);
|
|
TEST_ASSERT_EQUAL_FLOAT(3.0f, ((float*)alloc_req.host.vals.data)[1]);
|
|
|
|
xlist_fini(&limits);
|
|
}
|
|
|
|
/*
|
|
* bug-1789 (symmetric case, proves the dispatch is a genuine flag-based
|
|
* rule and not a "slots" special case): a HOST-scoped limit (its own
|
|
* vres NOT GLOBAL-flagged, so its write target is alloc_req->host) on a
|
|
* resource that IS XCE_RESF_GLOBAL (e.g. a pooled license count) must
|
|
* read from alloc_req->shared instead -- the opposite vector from its
|
|
* write target, mirroring exactly how calc_wanted_global_resvals() is
|
|
* the only one of the pair that ever populates a GLOBAL-flagged
|
|
* resource's want.
|
|
*/
|
|
static void
|
|
test_fill_limit_requests_host_limit_on_global_resource_reads_shared(void)
|
|
{
|
|
xce_scheduler_t sched;
|
|
xce_resource_t license_res_in = { 0 };
|
|
xce_resource_t limit_res_in = { 0 };
|
|
xce_resource_t* license_res;
|
|
xce_resource_t* limit_res;
|
|
xce_sched_alloc_req_t alloc_req;
|
|
xlist_t limits;
|
|
xce_vres_t vres;
|
|
int rc;
|
|
|
|
memset(&sched, 0, sizeof(sched));
|
|
TEST_ASSERT_EQUAL_INT(0, xce_sched_core_init(&(sched.core)));
|
|
|
|
license_res_in.name = strdup("licenses");
|
|
license_res_in.flags = XCE_RESF_GLOBAL;
|
|
license_res = makeResource(sched.core.resmgr, &license_res_in);
|
|
TEST_ASSERT_NOT_NULL(license_res);
|
|
|
|
/* the LIMIT's own virtual resource -- NOT GLOBAL (host-scoped limit),
|
|
* same rationale as the sibling test above. */
|
|
limit_res_in.name = strdup("limit=license_cap");
|
|
limit_res = makeResource(sched.core.resmgr, &limit_res_in);
|
|
TEST_ASSERT_NOT_NULL(limit_res);
|
|
|
|
memset(&vres, 0, sizeof(vres));
|
|
vres.res = license_res;
|
|
vres.vres = limit_res;
|
|
|
|
rc = xlist_init(&limits, 4);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
rc = xlist_store(&limits, &vres);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
|
|
rc = xvec_idval_init(&(alloc_req.shared), 8);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
rc = xvec_idval_init(&(alloc_req.host), 8);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
/* pre-staged into .shared (this job's earlier global-phase pass), NOT
|
|
* .host -- .host (this limit's own write target, derived from
|
|
* limit_res's non-GLOBAL flag) starts empty. */
|
|
rc = xvec_idval_store(&(alloc_req.shared), license_res->idx, 5.0f);
|
|
TEST_ASSERT_TRUE(rc >= 0);
|
|
|
|
rc = sched_fill_limit_requests(&alloc_req, &limits, NULL, 0, 0);
|
|
TEST_ASSERT_EQUAL_INT(0, rc);
|
|
|
|
TEST_ASSERT_EQUAL_INT(1, alloc_req.host.count);
|
|
TEST_ASSERT_EQUAL_INT(limit_res->idx, ((int*)alloc_req.host.idxs.data)[0]);
|
|
TEST_ASSERT_EQUAL_FLOAT(5.0f, ((float*)alloc_req.host.vals.data)[0]);
|
|
|
|
xlist_fini(&limits);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
UNITY_BEGIN();
|
|
RUN_TEST(test_charge_account_identical_across_core_containers);
|
|
RUN_TEST(test_commit_job_alloc_identical_across_core_containers);
|
|
RUN_TEST(test_charge_account_skips_reconciling_or_unavail_worker_host_charge);
|
|
RUN_TEST(
|
|
test_reconcile_host_resource_usage_below_reservation_keeps_reservation);
|
|
RUN_TEST(
|
|
test_reconcile_host_resource_usage_above_reservation_charges_actual);
|
|
RUN_TEST(
|
|
test_reconcile_host_resource_usage_repeated_growth_applies_only_delta);
|
|
RUN_TEST(test_reconcile_host_resource_usage_null_args_return_einval);
|
|
RUN_TEST(
|
|
test_reconcile_host_resource_usage_skips_reconciling_or_unavail_worker);
|
|
RUN_TEST(test_reconcile_job_resource_usage_maps_memory_rss_to_mem_account);
|
|
RUN_TEST(test_reconcile_job_resource_usage_skips_multihost_jobs);
|
|
RUN_TEST(test_reconcile_job_resource_usage_charges_unreserved_job_full_usage);
|
|
RUN_TEST(test_reconcile_job_resource_usage_noop_when_mem_not_a_registered_resource);
|
|
RUN_TEST(test_reconcile_job_resource_usage_null_args_return_einval);
|
|
RUN_TEST(test_ctx_uncommit_host_reverses_provisional_debit);
|
|
RUN_TEST(test_fill_limit_requests_global_limit_on_host_resource_reads_host);
|
|
RUN_TEST(test_fill_limit_requests_global_limit_on_slots_reads_host);
|
|
RUN_TEST(test_fill_limit_requests_host_limit_on_host_resource_reads_host);
|
|
RUN_TEST(test_fill_limit_requests_host_limit_on_global_resource_reads_shared);
|
|
return UNITY_END();
|
|
}
|