feat(job-array-single-wal-record): build the compact API-server push
Adds apiserver_push_jobarray() (apiserver.c), mirroring task 5.1's xcesched_push_jobarray() exactly: same (base jobid, array_size, one spec) Data.jobArray derivation, same direct xce_request()/OPCode_push transport. apiserver_job_new_fun() branches on l->count > 1; the existing single-job path (apiserver_push_joblist()) is byte-for-byte unchanged. Additionally, and unlike the scheduler (single target), replicates apiserver_push_joblist()'s existing per-connected-server fan-out loop (N_APISERVER_MAX iteration, same lock discipline) -- verified against 2 simulated connected servers, each receiving exactly one push. New standalone apiserver_push_unit_test binary (same link-collision rationale as task 5.1's sched_push_unit_test), correctly omitting egress.c since apiserver_push_joblist() has no WAL-commit filter. Task 5.2 of job-array-single-wal-record (Requirements 6.2, 6.4).
This commit is contained in:
parent
5e7a4aa392
commit
e0cec75284
4 changed files with 449 additions and 2 deletions
|
|
@ -116,7 +116,7 @@
|
|||
- _Requirements: 6.1, 6.3, 6.4_
|
||||
- _Boundary: Compact Scheduler/API-Server Push (controller-side scheduler push)_
|
||||
|
||||
- [ ] 5.2 (P) Build the compact API-server push
|
||||
- [x] 5.2 (P) Build the compact API-server push
|
||||
- Same construction as 5.1, for the API-server push handler and its own existing per-job push
|
||||
- Observable: a synthetic multi-element notification produces exactly one compact push per connected API server, not N individually-encoded job structs; the single-element branch is unaffected
|
||||
- _Requirements: 6.2, 6.4_
|
||||
|
|
@ -179,3 +179,4 @@
|
|||
- **Task 4.1**: `persist_apply_pump_run()` now halts the walk (no `last_applied_lsn` advance) on ANY negative return from `apply_committed_entry()`, not just the new array type — this is a general fix. Investigated whether this newly exposes a hazard for the pre-existing job-state fact entries (`WAL_JOB_START/FINISH/FAIL/UPDATE`), which have their own real `-ENOENT` branch when the target jobid isn't in `c->jobmgr`: confirmed NOT a hazard, because `c->jobmgr` (`xkvmgr_type`) has no `key_del` (jobs are never removed for the process lifetime, corroborated by `job.c:428,455,645`), and this task's own strict sequential-no-skip apply means a fact's LSN is only reached after its causally-prior submit already applied. So a live `-ENOENT` on a fact can now only mean genuine corruption/divergence — halting is correct, not a new footgun. Independent reviewer confirmed this via `test_term_safety_adapter.c:2039-2043`'s synthetic (non-production-representative) trigger. design.md's "Existing Architecture Analysis" table row overclaims ("every existing WAL type... returns 0 or positive in practice") — technically inaccurate re: the fact-ENOENT branch — left as-is since the persist.c code comment at the site already documents the exception correctly; low-severity, non-blocking.
|
||||
- **Task 4.2**: New `_Atomic uint64_t apply_resource_fenced_lsn` field on `persist_t` (0 = clear, nonzero = the stuck LSN) plus `persist_apply_resource_fenced()`/`persist_mark_apply_resource_fenced()`/`persist_clear_apply_resource_fence()`, wired into `persist_apply_pump_run()`'s existing halt branch (engage) and unconditionally after every successful advance (clear). The unconditional clear is provably equivalent to "clear only on the fenced LSN's own successful retry" because `last_applied_lsn` has exactly 3 writers codebase-wide, the other two (`replay_wal`, `persist_recover`) are boot-only and temporally disjoint from the live pump, and a failed walk never advances past the stuck LSN — so the next success by construction IS that LSN's retry. **Known residual gap (out of this task's boundary, flagged by review, NOT yet fixed)**: `replay_wal()` (boot/restart replay, persist.c:3875-3888) still discards `apply_committed_entry()`'s failure return the same way the live pump did before task 4.1, and `persist_init()` unconditionally zeroes the fence field on every restart. A node that crashes while fenced and re-hits the identical resource-exhaustion condition during boot replay will silently pass it and look fully promotable while actually divergent — task 4.1/4.2's protection does not yet extend to the boot-replay path. This is a pre-existing gap (not introduced by this feature) but is now inconsistent with the live path's new guarantee; worth a dedicated follow-up task before anything downstream places full trust in this fence across a restart.
|
||||
- **Task 5.1**: New `xcesched_push_jobarray()` (`sched.c`) sends via direct `xce_request()`/`OPCode_push`/`Request_body_pushmsg` rather than the existing `xce_push_data_req()` helper — `xce_push_data_req()`'s dispatch switch hard-rejects (`-EINVAL`) anything outside jobs/workers/resources/resvals, so it cannot carry a single non-list `Data.jobArray` descriptor. design.md corrected to match (Dependencies + API Contract for the Compact Scheduler/API-Server Push component). Task 5.2's API-server push will hit the identical fork and should follow the same direct-`xce_request()` pattern rather than re-deriving it. One accepted, low-severity trade-off: the direct path does not go through `xce_push_data_req()`'s `checkf`-gated `check_controller`/`c->running` liveness check that every other push gets — a narrow shutdown-race behavioral difference from sibling pushes, not a new safety hazard; not fixed here since threading a liveness check into a single new call site duplicates rather than reuses the shared gate. `sched.c`/`dataex.c`/`egress.c` were never linked into `controller_unit_test` (two existing test files there ship their own fake `on_scheduler_join()`/`notify()`/`collect_confvars()` precisely because of this, and linking the real ones would collide) — task 5.1's tests live in a new standalone `sched_push_unit_test` binary instead; task 5.2 will likely face the same fork for `apiserver.c` and should check for an analogous existing-fakes collision before choosing a test-binary strategy.
|
||||
- **Task 5.2**: `apiserver_push_jobarray()` (`apiserver.c`) mirrors task 5.1's `xcesched_push_jobarray()` exactly — same field derivation, same direct-`xce_request()` transport, same `l->count > 1` discriminator — and additionally, correctly replicates `apiserver_push_joblist()`'s existing per-connected-server fan-out loop (`N_APISERVER_MAX` iteration, same lock discipline), independently verified against 2 simulated connected servers each receiving exactly one push. Confirmed the same `apiserver.c`/`dataex.c` link-collision task 5.1 hit for `sched.c` (a fake `notify()` already exists in `test_commit_barrier_reply.c`) — new standalone `apiserver_push_unit_test` binary added, correctly omitting `egress.c` since `apiserver_push_joblist()`, unlike `sched.c`'s push path, never references `job_submit_committed()`/`collect_jobs_committed()`. One low-severity, non-blocking nit (not fixed): the new test file's comment justifying its `apiserver_init()` idempotency guard is factually inaccurate — it claims a second `apiserver_init()` call would double-register hooks, but `xce_add_hook()` has deduped by `(fn,data)` since bug-845, so that specific claim is wrong; the guard itself is still correct/necessary for a different, real reason (avoids re-`mylock_init()`-ing an already-initialized mutex, UB).
|
||||
|
|
|
|||
|
|
@ -124,6 +124,79 @@ static int apiserver_push_joblist(cluster_t* c, xlist_t* l)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* job-array-single-wal-record task 5.2 (design.md "Compact Scheduler/
|
||||
* API-Server Push", File Structure Plan apiserver.c bullet; Req 6.2, 6.4):
|
||||
* encodes ONE compact (base jobid, array size, one shared spec) Data.jobArray
|
||||
* push per connected API server -- reusing the JobSubmit capnp struct
|
||||
* (already added to the Data union in task 1.2, controller.capnp), never N
|
||||
* individually-encoded Job structs. (base_jobid, array_size) are derived
|
||||
* from the already-constructed xlist_t the HOOK_DETAIL_JOB_NEW fire carries
|
||||
* (apply_committed_entry's WAL_JOB_ARRAY_SUBMIT case, task 3.2): the first
|
||||
* element's jobid is the array base and l->count is the array size; every
|
||||
* element shares the same submit spec by construction (restore_job_array's
|
||||
* own construction discipline, job.c), so any one element's submit pointer
|
||||
* is the one shared spec to encode.
|
||||
*
|
||||
* Sent via xce_request() directly (OPCode_push / Request_body_pushmsg)
|
||||
* rather than xce_push_data_req(), mirroring sched.c's xcesched_push_
|
||||
* jobarray() (task 5.1) for the identical reason: that helper's dispatch
|
||||
* switch has explicit cases only for jobs/workers/resources/resvals and
|
||||
* hard-rejects (-EINVAL) anything else via its default case -- it cannot
|
||||
* carry a single non-list Data.jobArray descriptor. This mirrors xce_push_
|
||||
* req()'s (request.c, static) exact shape for a single non-list xce_data_t
|
||||
* payload.
|
||||
*
|
||||
* Fan-out mirrors apiserver_push_joblist()'s existing per-connected-server
|
||||
* loop exactly (same lock discipline, same name!=NULL && ep!=NULL guard) --
|
||||
* one push sent per connected server, not one push shared/broadcast. */
|
||||
static int apiserver_push_jobarray(cluster_t* c, xlist_t* l)
|
||||
{
|
||||
xce_job_t* first;
|
||||
xce_job_submit_t job_array;
|
||||
int i;
|
||||
|
||||
if ((c == NULL) || (l == NULL) || (l->count <= 0)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
first = (xce_job_t*)xlist_get(l, 0);
|
||||
if ((first == NULL) || (first->submit == NULL)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
memset(&job_array, 0x0, sizeof(job_array));
|
||||
job_array.jobid = first->jobid;
|
||||
job_array.array_size = (uint64_t)l->count;
|
||||
job_array.submit = first->submit;
|
||||
|
||||
for (i = 0; i < N_APISERVER_MAX; i++) {
|
||||
mylock_lock(&(c->apiservers[i].lock));
|
||||
if ((c->apiservers[i].name != NULL) && c->apiservers[i].ep != NULL) {
|
||||
xce_data_t data;
|
||||
xce_request_t request;
|
||||
int rc;
|
||||
|
||||
memset(&data, 0x0, sizeof(data));
|
||||
data.table = Data_data_jobArray;
|
||||
data.data.job_array = &job_array;
|
||||
|
||||
memset(&request, 0x0, sizeof(request));
|
||||
request.opcode = OPCode_push;
|
||||
request.body_which = Request_body_pushmsg;
|
||||
request.body.pushmsg = &data;
|
||||
|
||||
rc = xce_request(c->apiservers[i].ep, &request);
|
||||
if (rc < 0) {
|
||||
WARN("%s push job array to api server %s has problem, rc %d",
|
||||
__func__, c->apiservers[i].name, rc);
|
||||
}
|
||||
}
|
||||
mylock_unlock(&(c->apiservers[i].lock));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int apiserver_job_new_fun(int type, int situation, void* data,
|
||||
va_list ap)
|
||||
{
|
||||
|
|
@ -135,7 +208,17 @@ static int apiserver_job_new_fun(int type, int situation, void* data,
|
|||
case HOOK_AFTER_EVENT:
|
||||
rc = -EINVAL;
|
||||
if (l != NULL) {
|
||||
rc = apiserver_push_joblist(c, l);
|
||||
/* job-array-single-wal-record task 5.2 (Req 6.2, 6.4):
|
||||
* apply_committed_entry()'s WAL_JOB_ARRAY_SUBMIT case is the
|
||||
* ONLY way this hook is ever fired with more than one element
|
||||
* (Req 3.4 -- one HOOK_DETAIL_JOB_NEW fire per committed array
|
||||
* record, carrying every constructed element). Route that case
|
||||
* through the compact descriptor push instead of the per-job
|
||||
* push. The ordinary single-job branch (l->count == 1) below is
|
||||
* the exact, unmodified call this function always made
|
||||
* (Req 8.1). */
|
||||
rc = (l->count > 1) ? apiserver_push_jobarray(c, l)
|
||||
: apiserver_push_joblist(c, l);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -241,3 +241,31 @@ test('sched push test', sched_push_unit_test,
|
|||
timeout: 60,
|
||||
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
||||
|
||||
# job-array-single-wal-record task 5.2: apiserver.c's push logic
|
||||
# (apiserver_job_new_fun / apiserver_push_jobarray) faces the identical
|
||||
# real-vs-fake link collision task 5.1 hit for sched.c (test_commit_barrier_
|
||||
# reply.c / test_worker_join.c already define their OWN fake notify()/
|
||||
# on_scheduler_join(), so linking the real dataex.c into controller_unit_test
|
||||
# collides at link time) -- so this is its own standalone binary too,
|
||||
# mirroring sched_push_unit_test's strategy. UNLIKE sched.c, apiserver.c
|
||||
# never calls job_submit_committed()/collect_jobs_committed() (egress.c), so
|
||||
# this binary does NOT need egress.c -- verified by grep, not assumed
|
||||
# identical to sched.c's dependency set.
|
||||
apiserver_push_unit_test_src = [
|
||||
'testsuite/test_job_array_apiserver_push.c',
|
||||
'apiserver.c',
|
||||
'dataex.c',
|
||||
]
|
||||
|
||||
apiserver_push_unit_test = executable('apiserver_push_unit_test',
|
||||
apiserver_push_unit_test_src,
|
||||
include_directories: [incdir],
|
||||
dependencies: controller_unit_test_deps,
|
||||
c_args: ['-DXCE_TESTING'],
|
||||
install: false)
|
||||
|
||||
test('apiserver push test', apiserver_push_unit_test,
|
||||
suite: 'controller',
|
||||
timeout: 60,
|
||||
env: ['ASAN_OPTIONS=detect_leaks=0'])
|
||||
|
||||
|
|
|
|||
335
src/controller/testsuite/test_job_array_apiserver_push.c
Normal file
335
src/controller/testsuite/test_job_array_apiserver_push.c
Normal file
|
|
@ -0,0 +1,335 @@
|
|||
/* test_job_array_apiserver_push.c — job-array-single-wal-record task 5.2
|
||||
*
|
||||
* Unit tests for the Compact Scheduler/API-Server Push component's
|
||||
* controller-side API-server push (design.md "Compact Scheduler/API-Server
|
||||
* Push"; Req 6.2, 6.4), mirroring task 5.1's test_job_array_sched_push.c
|
||||
* construction for sched.c's xcesched_push_jobarray() -- same discriminator
|
||||
* (l->count > 1), same direct-xce_request() transport -- but for
|
||||
* apiserver.c's apiserver_job_new_fun()/apiserver_push_joblist().
|
||||
*
|
||||
* Fan-out difference from sched.c: sched.c has a single c->sched.ep, so
|
||||
* task 5.1 could only prove "one push per notification". apiserver.c
|
||||
* already fans a push out over an array of connected servers
|
||||
* (c->apiservers[N_APISERVER_MAX]), exactly like apiserver_push_joblist()'s
|
||||
* existing per-server loop -- so this file simulates TWO connected API
|
||||
* servers to prove the new compact push is genuinely per-connected-server,
|
||||
* not just per-notification (task 5.2's explicit observable: "exactly one
|
||||
* compact push PER CONNECTED API SERVER").
|
||||
*
|
||||
* - A synthetic multi-element HOOK_DETAIL_JOB_NEW fire (the exact shape
|
||||
* apply_committed_entry()'s WAL_JOB_ARRAY_SUBMIT case produces, task 3.2
|
||||
* -- one AFTER_EVENT fire, one xlist_t carrying every constructed
|
||||
* element) produces EXACTLY ONE compact Data.jobArray push on EACH of
|
||||
* the two connected servers' endpoints (two pushes total) -- never N
|
||||
* individually-encoded Job structs, and never fewer than one push per
|
||||
* connected server.
|
||||
* - A synthetic single-element fire (ordinary single-job submission) is
|
||||
* byte-for-byte unaffected on every connected server: still the
|
||||
* existing Data.jobs push via apiserver_push_joblist(), unchanged
|
||||
* (Req 8.1).
|
||||
*
|
||||
* Fixture: apiserver_job_new_fun()/apiserver_push_joblist()/
|
||||
* apiserver_push_jobarray() are static to apiserver.c, so this file drives
|
||||
* them through the same public entry point production code uses:
|
||||
* apiserver_init() installs the HOOK_DETAIL_JOB_NEW hook (guarded locally
|
||||
* with a static flag -- unlike sched.c's on_scheduler_join(),
|
||||
* apiserver_init() has no idempotency guard of its own, so calling it twice
|
||||
* would double-register hooks and double-fire pushes) and xce_run_hooks()
|
||||
* fires it exactly as apply_committed_entry() does. Each connected server's
|
||||
* outbound push is observed the same way test_job_array_sched_push.c
|
||||
* observes them: a zeroed, fd=-1 stack endpoint_t queues packets on
|
||||
* ep->outq without touching a real socket (endpoint_update_epoll no-ops,
|
||||
* zeroed pthread mutexes are valid); this test drains and raw-capnp-decodes
|
||||
* those packets.
|
||||
*
|
||||
* Test-binary strategy (verified against this task's own dependency graph,
|
||||
* NOT assumed identical to sched.c's): apiserver.c calls notify(),
|
||||
* collect_resval(), and check_controller() -- all defined in dataex.c, the
|
||||
* same real-vs-fake collision task 5.1 hit (test_commit_barrier_reply.c and
|
||||
* test_worker_join.c already define their OWN fake notify()/
|
||||
* on_scheduler_join(), so linking the real dataex.c into the shared
|
||||
* controller_unit_test binary collides at link time). UNLIKE sched.c,
|
||||
* apiserver.c never calls job_submit_committed()/collect_jobs_committed()
|
||||
* (egress.c) -- apiserver_push_joblist() has no WAL-commit filter, a
|
||||
* known, pre-existing, out-of-scope gap (see apiserver.c's own history) --
|
||||
* so this standalone binary links apiserver.c + dataex.c only, WITHOUT
|
||||
* egress.c (confirmed unnecessary by grep, not assumed). Mirrors
|
||||
* sched_push_unit_test's standalone-binary strategy (meson.build).
|
||||
*/
|
||||
|
||||
#include <capnp_c.h>
|
||||
#include <errno.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <unity/unity.h>
|
||||
|
||||
#include "cluster.h"
|
||||
#include "controller.capnp.h"
|
||||
#include "hook.h"
|
||||
#include "netio.h"
|
||||
#include "request.h"
|
||||
#include "xce/cluster_def.h"
|
||||
#include "xce/types.h"
|
||||
#include "xlist.h"
|
||||
|
||||
extern int apiserver_init(cluster_t*);
|
||||
|
||||
/* apiserver.c's xce_apiserver_push_workers() (reached only through
|
||||
* on_apiserver_join(), which this file's fixture never calls -- see
|
||||
* ap_setup) references opget.c's check_worker_() as a callback function
|
||||
* pointer, so the symbol must still resolve at link time even though it is
|
||||
* never invoked at runtime here. Stubbed locally rather than linking all of
|
||||
* opget.c, mirroring test_job_array_sched_push.c's identical stub for the
|
||||
* identical reason (sched.c's xcesched_push_workers() has the same
|
||||
* reference). */
|
||||
int check_worker_(xce_worker_t* w) { return (w != NULL) ? 1 : 0; }
|
||||
|
||||
#define APISRV_TEST_N 2
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Fixture */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static cluster_t g_ap_c;
|
||||
static endpoint_t g_ap_ep[APISRV_TEST_N];
|
||||
static int g_ap_hook_wired;
|
||||
|
||||
static void ap_drain_ep(endpoint_t* ep)
|
||||
{
|
||||
data_item_t* it;
|
||||
|
||||
while ((it = endpoint_deq(ep)) != NULL) {
|
||||
io_packet_t* pkt = ITPTR_TO_PACKET(it);
|
||||
|
||||
free_packet(pkt);
|
||||
free(pkt);
|
||||
}
|
||||
}
|
||||
|
||||
static void ap_setup(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (!g_ap_hook_wired) {
|
||||
memset(&g_ap_c, 0, sizeof(g_ap_c));
|
||||
TEST_ASSERT_EQUAL_INT(
|
||||
0, xce_hook_init(&g_ap_c.hooksys, XCE_CONTROLLER_HOOK_MAX));
|
||||
/* apiserver_init() mylock_inits every c->apiservers[] slot AND
|
||||
* installs the HOOK_DETAIL_JOB_NEW (+ sibling) hooks. It has no own
|
||||
* idempotency guard (unlike on_scheduler_join()), so this file
|
||||
* guards the call itself: exactly once, process-wide, shared by
|
||||
* every test below. */
|
||||
TEST_ASSERT_EQUAL_INT(0, apiserver_init(&g_ap_c));
|
||||
g_ap_hook_wired = 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < APISRV_TEST_N; i++) {
|
||||
memset(&g_ap_ep[i], 0, sizeof(g_ap_ep[i]));
|
||||
g_ap_ep[i].fd = -1;
|
||||
|
||||
g_ap_c.apiservers[i].name = (char*)"apisrv-jsp";
|
||||
g_ap_c.apiservers[i].ep = &g_ap_ep[i];
|
||||
}
|
||||
for (i = APISRV_TEST_N; i < N_APISERVER_MAX; i++) {
|
||||
g_ap_c.apiservers[i].name = NULL;
|
||||
g_ap_c.apiservers[i].ep = NULL;
|
||||
}
|
||||
|
||||
g_ap_c.running = 1;
|
||||
g_ap_c.myhost = &g_ap_c.myhost_info;
|
||||
g_ap_c.myhost_info.name = (char*)"ctrl-jsp";
|
||||
g_ap_c.myhost_info.key = (char*)"";
|
||||
}
|
||||
|
||||
static void ap_teardown(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < APISRV_TEST_N; i++) {
|
||||
ap_drain_ep(&g_ap_ep[i]);
|
||||
g_ap_c.apiservers[i].name = NULL;
|
||||
g_ap_c.apiservers[i].ep = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static job_submit_data_t g_ap_submit_data;
|
||||
|
||||
/* apiserver_push_joblist()/apiserver_push_jobarray() only ever read
|
||||
* xce_job_t's own .jobid/.submit fields directly (never CONTAINER_OF into a
|
||||
* wrapping job_t, unlike sched.c's job_submit_committed() filter) -- so
|
||||
* plain xce_job_t elements are the correct, minimal fixture here. */
|
||||
static void ap_make_job(xce_job_t* out, uint64_t jobid)
|
||||
{
|
||||
memset(out, 0, sizeof(*out));
|
||||
out->jobid = jobid;
|
||||
out->submit = &g_ap_submit_data;
|
||||
}
|
||||
|
||||
static int ap_fire_job_new(xlist_t* l)
|
||||
{
|
||||
return xce_run_hooks(&g_ap_c.hooksys, XCE_CONTROLLER_HOOK_JOB,
|
||||
HOOK_AFTER_EVENT, HOOK_DETAIL_JOB_NEW, &g_ap_c, l);
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
int n_pushes;
|
||||
int last_data_which;
|
||||
uint64_t last_jobid;
|
||||
uint64_t last_array_size;
|
||||
} ap_push_obs_t;
|
||||
|
||||
/* Raw-capnp-parse every queued push packet on ep->outq (mirrors
|
||||
* test_job_array_sched_push.c's jsp_scan_pushes). Drains the queue. */
|
||||
static void ap_scan_pushes(endpoint_t* ep, ap_push_obs_t* obs)
|
||||
{
|
||||
data_item_t* it;
|
||||
|
||||
memset(obs, 0, sizeof(*obs));
|
||||
while ((it = endpoint_deq(ep)) != NULL) {
|
||||
io_packet_t* pkt = ITPTR_TO_PACKET(it);
|
||||
struct capn c;
|
||||
struct Request rq;
|
||||
Request_ptr rp;
|
||||
|
||||
capn_init_mem(&c, (const uint8_t*)pkt->data, pkt->sz, 0);
|
||||
rp.p = capn_getp(capn_root(&c), 0, 1);
|
||||
read_Request(&rq, rp);
|
||||
if (rq.body_which == Request_body_pushmsg) {
|
||||
struct Data d;
|
||||
|
||||
read_Data(&d, rq.body.pushmsg);
|
||||
obs->n_pushes += 1;
|
||||
obs->last_data_which = (int)d.data_which;
|
||||
if (d.data_which == Data_data_jobArray) {
|
||||
struct JobSubmit js;
|
||||
|
||||
read_JobSubmit(&js, d.data.jobArray);
|
||||
obs->last_jobid = js.jobid;
|
||||
obs->last_array_size = js.arraySize;
|
||||
}
|
||||
}
|
||||
capn_free(&c);
|
||||
free_packet(pkt);
|
||||
free(pkt);
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Test 1: multi-element fire -> one compact jobArray push PER connected */
|
||||
/* API server */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void
|
||||
test_multi_element_job_new_produces_one_compact_jobarray_push_per_server(void)
|
||||
{
|
||||
xce_job_t jobs[5];
|
||||
void* storage[5];
|
||||
xlist_t l = { .count = 5, .caps = 5, .delta = 5, .data = storage };
|
||||
ap_push_obs_t obs;
|
||||
int rc;
|
||||
int i;
|
||||
|
||||
ap_setup();
|
||||
|
||||
for (i = 0; i < 5; i++) {
|
||||
ap_make_job(&jobs[i], 6000 + (uint64_t)i);
|
||||
storage[i] = &jobs[i];
|
||||
}
|
||||
|
||||
rc = ap_fire_job_new(&l);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
for (i = 0; i < APISRV_TEST_N; i++) {
|
||||
ap_scan_pushes(&g_ap_ep[i], &obs);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(
|
||||
1, obs.n_pushes,
|
||||
"a 5-element array notification must produce exactly ONE push "
|
||||
"packet per connected API server, not N individually-encoded "
|
||||
"job structs and not fewer than one per connected server (Req "
|
||||
"6.2, 6.4)");
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(
|
||||
Data_data_jobArray, obs.last_data_which,
|
||||
"the push must encode the compact Data.jobArray descriptor, not "
|
||||
"Data.jobs (N individually-encoded job structs)");
|
||||
TEST_ASSERT_EQUAL_UINT64_MESSAGE(
|
||||
6000, obs.last_jobid,
|
||||
"base jobid must be the array's first (lowest) jobid");
|
||||
TEST_ASSERT_EQUAL_UINT64_MESSAGE(
|
||||
5, obs.last_array_size,
|
||||
"arraySize must be the notification's element count");
|
||||
}
|
||||
|
||||
ap_teardown();
|
||||
printf(
|
||||
"test_multi_element_job_new_produces_one_compact_jobarray_push_per_"
|
||||
"server: PASS\n");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Test 2: single-element fire -> unmodified per-job Data.jobs push, */
|
||||
/* still fanned out to every connected server */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static void test_single_element_job_new_is_unaffected(void)
|
||||
{
|
||||
xce_job_t job;
|
||||
void* storage[1];
|
||||
xlist_t l = { .count = 1, .caps = 1, .delta = 1, .data = storage };
|
||||
ap_push_obs_t obs;
|
||||
int rc;
|
||||
int i;
|
||||
|
||||
ap_setup();
|
||||
|
||||
ap_make_job(&job, 9101);
|
||||
storage[0] = &job;
|
||||
|
||||
rc = ap_fire_job_new(&l);
|
||||
TEST_ASSERT_EQUAL_INT(0, rc);
|
||||
|
||||
for (i = 0; i < APISRV_TEST_N; i++) {
|
||||
ap_scan_pushes(&g_ap_ep[i], &obs);
|
||||
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(
|
||||
1, obs.n_pushes,
|
||||
"an ordinary single-job notification must still produce exactly "
|
||||
"one push packet per connected API server, unchanged");
|
||||
TEST_ASSERT_EQUAL_INT_MESSAGE(
|
||||
Data_data_jobs, obs.last_data_which,
|
||||
"a single-element notification must go through the EXISTING, "
|
||||
"UNMODIFIED apiserver_push_joblist() path (Data.jobs), never "
|
||||
"the new compact jobArray path (Req 8.1)");
|
||||
}
|
||||
|
||||
ap_teardown();
|
||||
printf("test_single_element_job_new_is_unaffected: PASS\n");
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Standalone Unity main (this binary is its own test executable -- see */
|
||||
/* meson.build's apiserver_push_unit_test) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
void setUp(void) { return; }
|
||||
void tearDown(void) { return; }
|
||||
|
||||
int main(void)
|
||||
{
|
||||
printf("\n>> job-array-single-wal-record task 5.2: compact API-server "
|
||||
"push\n\n");
|
||||
|
||||
UNITY_BEGIN();
|
||||
|
||||
RUN_TEST(
|
||||
test_multi_element_job_new_produces_one_compact_jobarray_push_per_server);
|
||||
RUN_TEST(test_single_element_job_new_is_unaffected);
|
||||
|
||||
return UNITY_END();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue