fix(controller): close 6 data-safety races + apply-pump O(N^2) regression + boot TOCTOU
Bundles the fixes validated this session across ha.c/persist.c/worker.c: - raft-apply-decoupling Task 7.4: apply-pump O(N^2) throughput regression fixed via a segment-read cache. - bug-1416: boot-time TOCTOU race in persist_verify_apply_correctness() closed with a recovery-complete gate that closes at process-lifecycle start, not function entry. - bug-451: HOOK_DETAIL_JOB_RUSAGE_UPDATE now has its own persist hook arm instead of silently falling through the default job-state case. - bug-1426: trim_wal_segments() also caps its trim floor at the local apply pump's own progress, not just remote standby-acked positions. - bug-1427: persist_verify_apply_correctness() extended with a worker-count check mirroring the existing job-count check (count_jobmgr_entries and count_workermgr_entries now share one count_manager_entries(var) helper). - bug-1428: persist_register_pending_apply's critical section widened to close the actual race instead of patching a symptom. - bug-1472 (remainder): WORKER_DEAD replay in worker_from_capnp() now applies mark_worker_unavail()'s mutations via a shared apply_worker_dead_mutation() helper instead of duplicating them. - bug-1474 (angle-a): on_standby_connected's stale-duplicate detection fixed for promoted vote endpoints via a shared votes_idx_for_peer() index-scale converter. Each fix independently verified via TDD (RED before fix, GREEN after). Full ASAN rebuild + controller_unit_test: 1101 tests, 0 failures. Live single-node HA deployment validated Task 7.4 and bug-1416 across 5 restart/recovery cycles (45/45 job submissions, zero APPLY_DIVERGENT fences). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
3afe3d5549
commit
45400c3a28
18 changed files with 1499 additions and 195 deletions
|
|
@ -36,9 +36,10 @@
|
|||
typedef int (*cmd_func_t)(int, char*[]);
|
||||
|
||||
typedef struct {
|
||||
char* name;
|
||||
cmd_func_t func;
|
||||
char* doc;
|
||||
char* name;
|
||||
cmd_func_t func;
|
||||
char* doc;
|
||||
const char* usage;
|
||||
} command_t;
|
||||
|
||||
typedef struct {
|
||||
|
|
@ -189,20 +190,370 @@ static setting_t settings[] = {
|
|||
{ (char*)NULL, (char*)NULL, (char*)NULL }
|
||||
};
|
||||
|
||||
/* Detailed per-command `help <cmd>` text (design: keep these OUT of the
|
||||
* command_t table initializers below so the tables stay readable). Each
|
||||
* constant's shape: first line = command name + flag/positional syntax,
|
||||
* then one indented line per flag/positional explaining what it does and
|
||||
* whether it's required, then (where useful) an `example:` line. Derived
|
||||
* from actually reading each cmd_* handler's getopt() string and switch
|
||||
* cases (or its positional argv[] handling) -- not guessed from the name.
|
||||
*
|
||||
* Commands that are identical (same func, same semantics) across more than
|
||||
* one of the 4 command tables share ONE constant here, referenced from every
|
||||
* table, so the text can't drift out of sync between contexts. */
|
||||
|
||||
static const char USAGE_HELP[] =
|
||||
"help [<cmd>]\n"
|
||||
" <cmd> optional command name; prints its detailed usage instead of\n"
|
||||
" the one-line summary\n"
|
||||
" With no argument, lists every command in the current context with its\n"
|
||||
" one-line summary (unaffected by this detailed-usage text).\n"
|
||||
"example: help resval\n";
|
||||
|
||||
static const char USAGE_QUIT[] =
|
||||
"quit\n"
|
||||
" (no arguments)\n"
|
||||
" Leaves the current context (if any) and exits xce_test.\n";
|
||||
|
||||
static const char USAGE_ECHO[] =
|
||||
"echo [<message...>]\n"
|
||||
" <message...> zero or more words, printed space-joined followed by a\n"
|
||||
" newline (always succeeds)\n"
|
||||
" Useful to print the $jobid / $jobcount / $? auto-variables in scripts.\n"
|
||||
"example: echo job is $jobid\n";
|
||||
|
||||
static const char USAGE_UP[] =
|
||||
"..\n"
|
||||
" (no arguments)\n"
|
||||
" Leaves the current context and returns to its parent, closing the\n"
|
||||
" underlying client connection for this context.\n";
|
||||
|
||||
static const char USAGE_WALDUMP[] =
|
||||
"waldump <wal_dir|data_dir|seg_file> [more...]\n"
|
||||
" <path> one or more paths: a seg-*.wal file, a wal/ dir, or a\n"
|
||||
" node/data dir\n"
|
||||
" Offline, read-only WAL inspector: lists every entry, decodes\n"
|
||||
" JOB_SUBMIT jobids, and flags LSN gaps and a CRC-broken tail.\n"
|
||||
" Returns nonzero if any path fails to load or any gap/CRC break is\n"
|
||||
" found.\n"
|
||||
"example: waldump /var/xce/ctrl-1/wal\n";
|
||||
|
||||
static const char USAGE_WALCOMPARE[] =
|
||||
"walcompare [--report] [--committed <N>] <memberA> <memberB> [more...]\n"
|
||||
" --report also emit a full per-member (lsn,term,pcrc,status)\n"
|
||||
" machine-checkable table, not just the verdict\n"
|
||||
" --committed <N> use LSN N as the shared committed boundary instead\n"
|
||||
" of deriving it as the minimum CRC-intact tip across\n"
|
||||
" members\n"
|
||||
" <memberX> 2-16 member paths (seg-*.wal file, wal/ dir, or\n"
|
||||
" node/data dir)\n"
|
||||
" Returns 0 if all members' committed WAL prefixes are identical,\n"
|
||||
" nonzero on divergence or load error.\n"
|
||||
"example: walcompare --report /var/xce/ctrl-1/wal /var/xce/ctrl-2/wal\n";
|
||||
|
||||
static const char USAGE_SNAPVERIFY[] =
|
||||
"snapverify <snap_file> [<snap_file> ...]\n"
|
||||
" <snap_file> one or more snap-*.capnp or snap-*.bin checkpoint files\n"
|
||||
" Offline structural validator: walks the same layout\n"
|
||||
" persist_load_snapshot() parses, without restoring anything. Reports\n"
|
||||
" the byte offset of the first structural defect and how many frames\n"
|
||||
" parsed cleanly before it.\n"
|
||||
" Returns nonzero if any file fails to verify.\n"
|
||||
"example: snapverify /var/xce/ctrl-1/snap/snap-000123.capnp\n";
|
||||
|
||||
static const char USAGE_SNAPDIFF[] =
|
||||
"snapdiff <source_file> <received_file>\n"
|
||||
" <source_file> the primary's own locally generated checkpoint\n"
|
||||
" (snap-*.capnp)\n"
|
||||
" <received_file> the copy a standby assembled from the wire\n"
|
||||
" (snap-*.bin)\n"
|
||||
" Byte-level comparator; reports the first differing offset and\n"
|
||||
" whether it lands on an OPCode_snapshotTransfer chunk boundary.\n"
|
||||
" Returns 0 if identical, nonzero otherwise.\n"
|
||||
"example: snapdiff snap-000123.capnp snap-000123.bin\n";
|
||||
|
||||
static const char USAGE_CONFIGCOMPARE[] =
|
||||
"configcompare <member_dir> <member_dir> [<member_dir> ...]\n"
|
||||
" <member_dir> a member's workdir/name conf-root directory (NOT a raw\n"
|
||||
" config file path) -- the dir under which conf/current\n"
|
||||
" resolves\n"
|
||||
" Recomputes each member's active-config sha256 fresh from its own\n"
|
||||
" on-disk bytes and reports whether all members match.\n"
|
||||
" Returns 0 if all members are present and identical, nonzero if any\n"
|
||||
" member is unreadable or divergent.\n"
|
||||
"example: configcompare /var/xce/ctrl-1 /var/xce/ctrl-2 /var/xce/ctrl-3\n";
|
||||
|
||||
static const char USAGE_WORKER[] =
|
||||
"worker\n"
|
||||
" (no arguments)\n"
|
||||
" Enters the worker context: connects to the controller cluster as a\n"
|
||||
" worker client and starts the local delay-manager housekeeping loop.\n";
|
||||
|
||||
static const char USAGE_SCHEDULER[] =
|
||||
"scheduler\n"
|
||||
" (no arguments)\n"
|
||||
" Enters the scheduler context: connects to the controller cluster as\n"
|
||||
" a scheduler client.\n";
|
||||
|
||||
static const char USAGE_CLIENT[] =
|
||||
"client\n"
|
||||
" (no arguments)\n"
|
||||
" Enters the client context: connects to the controller cluster as a\n"
|
||||
" regular API client (job submission, resource/worker queries, etc.).\n";
|
||||
|
||||
static const char USAGE_SET[] =
|
||||
"set\n"
|
||||
"set <key> <value>\n"
|
||||
"set -e | +e | continue-on-error\n"
|
||||
" (no args) list all settings in the current context\n"
|
||||
" (current value + default)\n"
|
||||
" <key> <value> set setting <key> (e.g. masters, secret,\n"
|
||||
" myname) to <value>\n"
|
||||
" -e abort the read loop on the first failing\n"
|
||||
" command (errexit)\n"
|
||||
" +e | continue-on-error keep the read loop running after a\n"
|
||||
" failing command\n"
|
||||
"example: set masters ctrl1=localhost:9991,ctrl2=localhost:9992\n";
|
||||
|
||||
static const char USAGE_JOIN_WORKER[] =
|
||||
"join [-f <cpufactor>] [-c <maxcpus>] [-m <maxmem>] [-s <maxswap>]\n"
|
||||
" [-t <maxtmp>] [-d <ndisks>]\n"
|
||||
" -f <cpufactor> relative CPU speed factor (default 1.0)\n"
|
||||
" -c <maxcpus> max CPU count (default 4)\n"
|
||||
" -m <maxmem> max memory in bytes (default 64)\n"
|
||||
" -s <maxswap> max swap in bytes (default 1024)\n"
|
||||
" -t <maxtmp> max tmp space in bytes (default 2048)\n"
|
||||
" -d <ndisks> number of local disks (default 1)\n"
|
||||
" Joins the cluster as a worker (hosttype LINUX64, hostmodel Intel64).\n"
|
||||
"example: join -c 8 -m 16384\n";
|
||||
|
||||
static const char USAGE_JOBSTART[] =
|
||||
"jstart -j <jobid> [-p <pid>]\n"
|
||||
" -j <jobid> jobid to report a synthetic job-start fact for\n"
|
||||
" (required)\n"
|
||||
" -p <pid> pid to report (default: this xce_test process's own\n"
|
||||
" pid)\n"
|
||||
"example: jstart -j 12345 -p 4242\n";
|
||||
|
||||
static const char USAGE_JOBFINISH[] =
|
||||
"jfinish -j <jobid> [-c <exitcode>]\n"
|
||||
" -j <jobid> jobid to report a synthetic job-finish fact for\n"
|
||||
" (required)\n"
|
||||
" -c <exitcode> exit code to report (default 0)\n"
|
||||
"example: jfinish -j 12345 -c 0\n";
|
||||
|
||||
static const char USAGE_JOBFAIL[] =
|
||||
"jfail -j <jobid> [-c <code>] [-s <stage>]\n"
|
||||
" -j <jobid> jobid to report a synthetic job-fail fact for\n"
|
||||
" (required)\n"
|
||||
" -c <code> failure code to report (default 0)\n"
|
||||
" -s <stage> exec-stage code to report (default\n"
|
||||
" JOB_EXEC_STAGE_POST_EXEC)\n"
|
||||
"example: jfail -j 12345 -c 1\n";
|
||||
|
||||
static const char USAGE_RESUPDATE[] =
|
||||
"resupdate -n <resname> -v <value> [-n <resname> -v <value> ...]\n"
|
||||
" -n <resname> resource name to update (e.g. slots, mem, cpuf,\n"
|
||||
" maxswap, maxtmp, maxcpus, ndisks, type, model,\n"
|
||||
" dynres1, dynres2, r1m, r15s); may repeat for\n"
|
||||
" multiple resources\n"
|
||||
" -v <value> value for the immediately preceding -n (numeric for\n"
|
||||
" most resources, string for type/model)\n"
|
||||
" Order matters: pair each -n with its -v right after it.\n"
|
||||
"example: resupdate -n slots -v 4 -n mem -v 8192\n";
|
||||
|
||||
static const char USAGE_HELLO[] =
|
||||
"hello [-c <count>] [-m <message>]\n"
|
||||
" -c <count> number of times to send the hello (default 1)\n"
|
||||
" -m <message> greeting text to send (default \"Greeting from xce\n"
|
||||
" client\")\n"
|
||||
"example: hello -c 3 -m \"ping\"\n";
|
||||
|
||||
static const char USAGE_RESLIST[] =
|
||||
"reslist\n"
|
||||
" (no arguments)\n"
|
||||
" Prints all cluster-wide resources (name, flags, interval,\n"
|
||||
" description).\n";
|
||||
|
||||
static const char USAGE_WORKERLIST[] =
|
||||
"workerlist\n"
|
||||
" (no arguments)\n"
|
||||
" Prints all joined workers (host, status, host type/model, cpu\n"
|
||||
" factor, max cpu/mem/swap/tmp, ndisks).\n";
|
||||
|
||||
static const char USAGE_QCTRL[] =
|
||||
"qctrl -q <queue> [-a] [-i] [-o] [-c]\n"
|
||||
" -q <queue> queue name to control (required)\n"
|
||||
" -a set queue ACTIVE (accepts dispatch)\n"
|
||||
" -i set queue INACTIVE (rejects dispatch)\n"
|
||||
" -o set queue OPEN (accepts new submissions)\n"
|
||||
" -c set queue CLOSE (rejects new submissions)\n"
|
||||
"example: qctrl -q normal -i\n";
|
||||
|
||||
static const char USAGE_HOSTCTRL[] =
|
||||
"hostctrl -h <hostname> (-o|-c)\n"
|
||||
" -h <hostname> host to administratively control (required)\n"
|
||||
" -o open the host (allow scheduling)\n"
|
||||
" -c close the host (admin-close; shows CLOSED in\n"
|
||||
" workerlist)\n"
|
||||
"example: hostctrl -h node01 -c\n";
|
||||
|
||||
static const char USAGE_UPGRADECONFIG[] =
|
||||
"upgradeconfig <cluster.bin>\n"
|
||||
" <cluster.bin> path to a capnp-encoded ClusterConfig blob (same\n"
|
||||
" format `xceadmin config build` produces and\n"
|
||||
" deploy-ha.sh installs)\n"
|
||||
" Computes its SHA-256 and submits it as a configuration upgrade via\n"
|
||||
" OP_upgradeConfig.\n"
|
||||
"example: upgradeconfig /var/xce/cluster.bin\n";
|
||||
|
||||
static const char USAGE_JOBLIST[] =
|
||||
"joblist\n"
|
||||
" (no arguments)\n"
|
||||
" Prints all jobs in the cluster (JobId, user, queue, state, times,\n"
|
||||
" ntasks, command) and captures the count into $jobcount.\n";
|
||||
|
||||
static const char USAGE_EXPECT_JOBS[] =
|
||||
"expect-jobs <N>\n"
|
||||
" <N> non-negative integer; asserts the current job count equals N\n"
|
||||
" Fails (nonzero) on a malformed N, a failed joblist query, or a count\n"
|
||||
" mismatch.\n"
|
||||
"example: expect-jobs 3\n";
|
||||
|
||||
static const char USAGE_EXPECT_JOB[] =
|
||||
"expect-job <jobid>\n"
|
||||
" <jobid> a JobId string (as printed by joblist / captured into\n"
|
||||
" $jobid); asserts a job with this id is present in the\n"
|
||||
" current job list\n"
|
||||
" Fails (nonzero) on a missing argument, a failed joblist query, or if\n"
|
||||
" the job is not found.\n"
|
||||
"example: expect-job $jobid\n";
|
||||
|
||||
static const char USAGE_WAIT_PRIMARY[] =
|
||||
"wait-primary [<timeout_ms>]\n"
|
||||
" <timeout_ms> optional max wait in milliseconds (default 30000);\n"
|
||||
" must be a non-negative integer\n"
|
||||
" Blocks, polling every 200 ms, until the AUTHORITATIVE controller\n"
|
||||
" status probe reaches the serving primary (a standby/hint answer does\n"
|
||||
" not count), or the timeout elapses.\n"
|
||||
" Returns 0 once a primary answers, nonzero (ETIMEDOUT) on timeout.\n"
|
||||
"example: wait-primary 10000\n";
|
||||
|
||||
static const char USAGE_CTRLSTATUS[] =
|
||||
"ctrlstatus\n"
|
||||
" (no arguments)\n"
|
||||
" Prints the responding controller's identity/role, cluster name,\n"
|
||||
" peer topology, config version/checksum, and HA health (term,\n"
|
||||
" committedLsn, appliedLsn, fenced) from a single any-node status\n"
|
||||
" read.\n";
|
||||
|
||||
static const char USAGE_SHUTDOWN[] =
|
||||
"shutdown\n"
|
||||
" (no arguments)\n"
|
||||
" Shuts down the xce controller and, on success, leaves the client\n"
|
||||
" context (same as running `..`).\n";
|
||||
|
||||
static const char USAGE_JOBRUN[] =
|
||||
"jobrun -j <jobid> [-m <host>[:<ntasks>]] [-m ...]\n"
|
||||
" -j <jobid> jobid to dispatch (required, must be > 0)\n"
|
||||
" -m <host>[:<n>] host to run tasks on, optional :ntasks count\n"
|
||||
" (default 1); may be repeated up to 5 times\n"
|
||||
"example: jobrun -j 12345 -m node01:2 -m node02\n";
|
||||
|
||||
static const char USAGE_JOBSUB[] =
|
||||
"jobsub -u <user> -q <queue> -c <cmdline> [-R <resreq>]\n"
|
||||
" [-n <min>[,<max>]] [-x <exdata>] [-a <arraysize>]\n"
|
||||
" -u <user> submitting user (required)\n"
|
||||
" -q <queue> target queue (required)\n"
|
||||
" -c <cmdline> command line to run (required)\n"
|
||||
" -R <resreq> resource requirement expression (default\n"
|
||||
" \"type == any\")\n"
|
||||
" -n <min>[,<max>] task count, or a min,max range (default 1)\n"
|
||||
" -x <exdata> raw exdata JSON string (overrides the built-in\n"
|
||||
" PATH env)\n"
|
||||
" -a <arraysize> job array size (default 1)\n"
|
||||
" On success, captures the submitted JobId into the $jobid\n"
|
||||
" auto-variable.\n"
|
||||
"example: jobsub -u alice -q normal -c \"sleep 10\" -n 1,4\n";
|
||||
|
||||
static const char USAGE_RESVAL[] =
|
||||
"resval -n <resname> [-m <host>]\n"
|
||||
" -n <resname> resource name to query (required)\n"
|
||||
" -m <host> host to query (required unless the resource is a\n"
|
||||
" shared/cluster-wide resource, not per-host)\n"
|
||||
"example: resval -n slots -m myhost\n";
|
||||
|
||||
static const char USAGE_JOBKILL[] =
|
||||
"jobkill -j <jobid> [-s <signo>]\n"
|
||||
" -j <jobid> jobid to signal (required)\n"
|
||||
" -s <signo> signal number to send (default SIGTERM)\n"
|
||||
"example: jobkill -j 12345 -s 9\n";
|
||||
|
||||
static const char USAGE_JOBCKPT[] =
|
||||
"jobckpt -j <jobid> [-k] [-m <method>]\n"
|
||||
" -j <jobid> jobid to checkpoint (required)\n"
|
||||
" -k kill the job immediately after checkpointing\n"
|
||||
" -m <method> checkpoint method name (default \"default\")\n"
|
||||
"example: jobckpt -j 12345 -m default\n";
|
||||
|
||||
static const char USAGE_JOBINFO[] =
|
||||
"jobinfo -j <jobid> [-j <jobid> ...]\n"
|
||||
" -j <jobid> jobid to show info for; may be repeated to show\n"
|
||||
" several jobs in one call\n"
|
||||
"example: jobinfo -j 12345\n";
|
||||
|
||||
static const char USAGE_TASKRUN[] =
|
||||
"taskrun [-j <jobid>] [-m <host>[:<n>]]... [-u <user>] [-c <cmdline>]\n"
|
||||
" [-x <exdata>] [-d <dir>] [-t <launch_type>]\n"
|
||||
" -j <jobid> jobid this task run belongs to\n"
|
||||
" -m <host>[:<n>] host to run on, optional :ntasks count\n"
|
||||
" (default 1); may be repeated up to 5 times\n"
|
||||
" -u <user> exec user, applied to every host\n"
|
||||
" -c <cmdline> command line, applied to every host\n"
|
||||
" -x <exdata> exdata string, applied to every host\n"
|
||||
" -d <dir> exec home/cwd directory, applied to every host\n"
|
||||
" -t <launch_type> launch type (default \"mpi\" if the total task\n"
|
||||
" count across all -m hosts is > 1, else a\n"
|
||||
" single-task default)\n"
|
||||
"example: taskrun -j 12345 -m node01:2 -c \"hostname\" -u alice\n";
|
||||
|
||||
static const char USAGE_JOIN_SCHED[] =
|
||||
"join\n"
|
||||
" (no arguments)\n"
|
||||
" Joins the cluster as a scheduler.\n";
|
||||
|
||||
static const char USAGE_GETCONF[] =
|
||||
"getconf\n"
|
||||
" (no arguments)\n"
|
||||
" Prints the full cluster configuration (name, resources, queues,\n"
|
||||
" workers, user/host groups, configs) known to the scheduler.\n";
|
||||
|
||||
static const char USAGE_SETPEND[] =
|
||||
"setpend -j <jobid> -p <reason> [-p <reason> ...]\n"
|
||||
" -j <jobid> jobid to set the pend reason for (required)\n"
|
||||
" -p <reason> pend reason spec, may repeat: `<code>` sets the main\n"
|
||||
" reason; `<host>:<code>` adds a host-based reason;\n"
|
||||
" `<res>:<host>:<code>` adds a resource-based reason\n"
|
||||
"example: setpend -j 12345 -p 5\n";
|
||||
|
||||
static command_t top_commands[] = {
|
||||
{ "help", cmd_help, "Display this text" },
|
||||
{ "worker", cmd_worker, "In worker context" },
|
||||
{ "scheduler", cmd_scheduler, "In scheduler context" },
|
||||
{ "client", cmd_client, "In client context" },
|
||||
{ "set", cmd_set, "Show/Set settings" },
|
||||
{ "echo", cmd_echo, "Print a message" },
|
||||
{ "waldump", cmd_waldump, "Inspect WAL segments" },
|
||||
{ "walcompare", cmd_walcompare, "Diff member committed WAL prefixes" },
|
||||
{ "snapverify", cmd_snapverify, "Verify checkpoint file structure" },
|
||||
{ "snapdiff", cmd_snapdiff, "Byte-diff two checkpoint files" },
|
||||
{ "configcompare", cmd_configcompare, "Compare active config hash across members" },
|
||||
{ "quit", cmd_quit, "Quit xce test" },
|
||||
{ (char*)NULL, (cmd_func_t)NULL, (char*)NULL }
|
||||
{ "help", cmd_help, "Display this text", USAGE_HELP },
|
||||
{ "worker", cmd_worker, "In worker context", USAGE_WORKER },
|
||||
{ "scheduler", cmd_scheduler, "In scheduler context", USAGE_SCHEDULER },
|
||||
{ "client", cmd_client, "In client context", USAGE_CLIENT },
|
||||
{ "set", cmd_set, "Show/Set settings", USAGE_SET },
|
||||
{ "echo", cmd_echo, "Print a message", USAGE_ECHO },
|
||||
{ "waldump", cmd_waldump, "Inspect WAL segments", USAGE_WALDUMP },
|
||||
{ "walcompare", cmd_walcompare, "Diff member committed WAL prefixes",
|
||||
USAGE_WALCOMPARE },
|
||||
{ "snapverify", cmd_snapverify, "Verify checkpoint file structure",
|
||||
USAGE_SNAPVERIFY },
|
||||
{ "snapdiff", cmd_snapdiff, "Byte-diff two checkpoint files",
|
||||
USAGE_SNAPDIFF },
|
||||
{ "configcompare", cmd_configcompare, "Compare active config hash across members",
|
||||
USAGE_CONFIGCOMPARE },
|
||||
{ "quit", cmd_quit, "Quit xce test", USAGE_QUIT },
|
||||
{ (char*)NULL, (cmd_func_t)NULL, (char*)NULL, (const char*)NULL }
|
||||
};
|
||||
|
||||
static command_ctx_t topctx = { .parent = NULL,
|
||||
|
|
@ -211,16 +562,16 @@ static command_ctx_t topctx = { .parent = NULL,
|
|||
.commands = top_commands };
|
||||
|
||||
static command_t worker_commands[] = {
|
||||
{ "..", cmd_up, "Goto upper context" },
|
||||
{ "help", cmd_help, "Display this text" },
|
||||
{ "quit", cmd_quit, "Quit xce test" },
|
||||
{ "join", cmd_join_worker, "Join the cluster" },
|
||||
{ "jstart", cmd_jobstart, "tell controller jobstart" },
|
||||
{ "jfinish", cmd_jobfinish, "tell controller jobfinish" },
|
||||
{ "jfail", cmd_jobfail, "tell controller jobfail" },
|
||||
{ "resupdate", cmd_resupdate, "update res vals" },
|
||||
{ "echo", cmd_echo, "Print a message" },
|
||||
{ (char*)NULL, (cmd_func_t)NULL, (char*)NULL }
|
||||
{ "..", cmd_up, "Goto upper context", USAGE_UP },
|
||||
{ "help", cmd_help, "Display this text", USAGE_HELP },
|
||||
{ "quit", cmd_quit, "Quit xce test", USAGE_QUIT },
|
||||
{ "join", cmd_join_worker, "Join the cluster", USAGE_JOIN_WORKER },
|
||||
{ "jstart", cmd_jobstart, "tell controller jobstart", USAGE_JOBSTART },
|
||||
{ "jfinish", cmd_jobfinish, "tell controller jobfinish", USAGE_JOBFINISH },
|
||||
{ "jfail", cmd_jobfail, "tell controller jobfail", USAGE_JOBFAIL },
|
||||
{ "resupdate", cmd_resupdate, "update res vals", USAGE_RESUPDATE },
|
||||
{ "echo", cmd_echo, "Print a message", USAGE_ECHO },
|
||||
{ (char*)NULL, (cmd_func_t)NULL, (char*)NULL, (const char*)NULL }
|
||||
};
|
||||
|
||||
static command_ctx_t workerctx = { .parent = &topctx,
|
||||
|
|
@ -229,37 +580,41 @@ static command_ctx_t workerctx = { .parent = &topctx,
|
|||
.commands = worker_commands };
|
||||
|
||||
static command_t client_commands[] = {
|
||||
{ "..", cmd_up, "Goto upper context" },
|
||||
{ "hello", cmd_hello, "Say hello to xce controller" },
|
||||
{ "reslist", cmd_reslist, "Display cluster resources" },
|
||||
{ "workerlist", cmd_workerlist, "list workers" },
|
||||
{ "qctrl", cmd_qctrl, "queue control" },
|
||||
{ "hostctrl", cmd_hostctrl, "host control" },
|
||||
{ "..", cmd_up, "Goto upper context", USAGE_UP },
|
||||
{ "hello", cmd_hello, "Say hello to xce controller", USAGE_HELLO },
|
||||
{ "reslist", cmd_reslist, "Display cluster resources", USAGE_RESLIST },
|
||||
{ "workerlist", cmd_workerlist, "list workers", USAGE_WORKERLIST },
|
||||
{ "qctrl", cmd_qctrl, "queue control", USAGE_QCTRL },
|
||||
{ "hostctrl", cmd_hostctrl, "host control", USAGE_HOSTCTRL },
|
||||
{ "upgradeconfig", cmd_upgradeconfig,
|
||||
"submit a config upgrade (cluster.bin file)" },
|
||||
{ "joblist", cmd_joblist, "list jobs" },
|
||||
{ "expect-jobs", cmd_expect_jobs, "assert current job count" },
|
||||
{ "expect-job", cmd_expect_job, "assert a JobId is present" },
|
||||
"submit a config upgrade (cluster.bin file)", USAGE_UPGRADECONFIG },
|
||||
{ "joblist", cmd_joblist, "list jobs", USAGE_JOBLIST },
|
||||
{ "expect-jobs", cmd_expect_jobs, "assert current job count", USAGE_EXPECT_JOBS },
|
||||
{ "expect-job", cmd_expect_job, "assert a JobId is present", USAGE_EXPECT_JOB },
|
||||
{ "wait-primary", cmd_wait_primary,
|
||||
"wait until a primary serves [timeout_ms]" },
|
||||
{ "ctrlstatus", cmd_ctrlstatus, "show controller/cluster status" },
|
||||
{ "waldump", cmd_waldump, "Inspect WAL segments" },
|
||||
{ "walcompare", cmd_walcompare, "Diff member committed WAL prefixes" },
|
||||
{ "snapverify", cmd_snapverify, "Verify checkpoint file structure" },
|
||||
{ "snapdiff", cmd_snapdiff, "Byte-diff two checkpoint files" },
|
||||
{ "configcompare", cmd_configcompare, "Compare active config hash across members" },
|
||||
{ "shutdown", cmd_shutdown, "shutdown xce controller" },
|
||||
{ "jobrun", cmd_jobrun, "run a job" },
|
||||
{ "jobsub", cmd_jobsub, "submit a job" },
|
||||
{ "jobkill", cmd_jobkill, "signal a job" },
|
||||
{ "jobckpt", cmd_jobckpt, "checkpoint a job" },
|
||||
{ "jobinfo", cmd_jobinfo, "job information" },
|
||||
{ "resval", cmd_resval, "get value of res" },
|
||||
{ "taskrun", cmd_runtasks, "run tasks" },
|
||||
{ "help", cmd_help, "Display this text" },
|
||||
{ "echo", cmd_echo, "Print a message" },
|
||||
{ "quit", cmd_quit, "Quit xce test" },
|
||||
{ (char*)NULL, (cmd_func_t)NULL, (char*)NULL }
|
||||
"wait until a primary serves [timeout_ms]", USAGE_WAIT_PRIMARY },
|
||||
{ "ctrlstatus", cmd_ctrlstatus, "show controller/cluster status", USAGE_CTRLSTATUS },
|
||||
{ "waldump", cmd_waldump, "Inspect WAL segments", USAGE_WALDUMP },
|
||||
{ "walcompare", cmd_walcompare, "Diff member committed WAL prefixes",
|
||||
USAGE_WALCOMPARE },
|
||||
{ "snapverify", cmd_snapverify, "Verify checkpoint file structure",
|
||||
USAGE_SNAPVERIFY },
|
||||
{ "snapdiff", cmd_snapdiff, "Byte-diff two checkpoint files",
|
||||
USAGE_SNAPDIFF },
|
||||
{ "configcompare", cmd_configcompare, "Compare active config hash across members",
|
||||
USAGE_CONFIGCOMPARE },
|
||||
{ "shutdown", cmd_shutdown, "shutdown xce controller", USAGE_SHUTDOWN },
|
||||
{ "jobrun", cmd_jobrun, "run a job", USAGE_JOBRUN },
|
||||
{ "jobsub", cmd_jobsub, "submit a job", USAGE_JOBSUB },
|
||||
{ "jobkill", cmd_jobkill, "signal a job", USAGE_JOBKILL },
|
||||
{ "jobckpt", cmd_jobckpt, "checkpoint a job", USAGE_JOBCKPT },
|
||||
{ "jobinfo", cmd_jobinfo, "job information", USAGE_JOBINFO },
|
||||
{ "resval", cmd_resval, "get value of res", USAGE_RESVAL },
|
||||
{ "taskrun", cmd_runtasks, "run tasks", USAGE_TASKRUN },
|
||||
{ "help", cmd_help, "Display this text", USAGE_HELP },
|
||||
{ "echo", cmd_echo, "Print a message", USAGE_ECHO },
|
||||
{ "quit", cmd_quit, "Quit xce test", USAGE_QUIT },
|
||||
{ (char*)NULL, (cmd_func_t)NULL, (char*)NULL, (const char*)NULL }
|
||||
};
|
||||
|
||||
static command_ctx_t clientctx = { .parent = &topctx,
|
||||
|
|
@ -268,14 +623,14 @@ static command_ctx_t clientctx = { .parent = &topctx,
|
|||
.commands = client_commands };
|
||||
|
||||
static command_t scheduler_commands[] = {
|
||||
{ "..", cmd_up, "Goto upper context" },
|
||||
{ "help", cmd_help, "Display this text" },
|
||||
{ "quit", cmd_quit, "Quit xce test" },
|
||||
{ "join", cmd_join_sched, "Join the cluster" },
|
||||
{ "getconf", cmd_getconf, "Get conf of cluster" },
|
||||
{ "setpend", cmd_setpend, "set job pend reason" },
|
||||
{ "echo", cmd_echo, "Print a message" },
|
||||
{ (char*)NULL, (cmd_func_t)NULL, (char*)NULL }
|
||||
{ "..", cmd_up, "Goto upper context", USAGE_UP },
|
||||
{ "help", cmd_help, "Display this text", USAGE_HELP },
|
||||
{ "quit", cmd_quit, "Quit xce test", USAGE_QUIT },
|
||||
{ "join", cmd_join_sched, "Join the cluster", USAGE_JOIN_SCHED },
|
||||
{ "getconf", cmd_getconf, "Get conf of cluster", USAGE_GETCONF },
|
||||
{ "setpend", cmd_setpend, "set job pend reason", USAGE_SETPEND },
|
||||
{ "echo", cmd_echo, "Print a message", USAGE_ECHO },
|
||||
{ (char*)NULL, (cmd_func_t)NULL, (char*)NULL, (const char*)NULL }
|
||||
};
|
||||
|
||||
static command_ctx_t schedulerctx = { .parent = &topctx,
|
||||
|
|
@ -678,8 +1033,18 @@ int cmd_help(int argc, char* argv[])
|
|||
|
||||
for (i = 0; active_ctx->commands[i].name != NULL; i++) {
|
||||
if (strcmp(argv[1], active_ctx->commands[i].name) == 0) {
|
||||
printf("%s\t\t%s.\n", active_ctx->commands[i].name,
|
||||
active_ctx->commands[i].doc);
|
||||
/* `help <cmd>` prints the detailed per-command usage (flags,
|
||||
* positionals, example) -- distinct from the compact one-line
|
||||
* `doc` the bare `help` listing below still uses unchanged.
|
||||
* Every table entry is expected to carry a non-NULL `usage`
|
||||
* now; the `doc` fallback stays only as a defensive guard
|
||||
* against a future entry that forgets to set it. */
|
||||
if (active_ctx->commands[i].usage != NULL) {
|
||||
fputs(active_ctx->commands[i].usage, stdout);
|
||||
} else {
|
||||
printf("%s\t\t%s.\n", active_ctx->commands[i].name,
|
||||
active_ctx->commands[i].doc);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -972,7 +1337,10 @@ int cmd_hello(int argc, char* argv[])
|
|||
msg = optarg;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, " hello [-c count] [-w wait_usec] [-m message]\n");
|
||||
/* -w was never implemented (getopt string above is "c:m:" only)
|
||||
* -- this hint previously advertised a nonexistent -w wait_usec
|
||||
* flag; corrected to match the actual accepted options. */
|
||||
fprintf(stderr, " hello [-c count] [-m message]\n");
|
||||
return -1;
|
||||
break;
|
||||
}
|
||||
|
|
@ -1071,20 +1439,26 @@ int cmd_workerlist(int argc, char* argv[])
|
|||
"MaxSwap", "MaxTmp", "NDisks");
|
||||
|
||||
for (i = 0; i < rc; i++) {
|
||||
/* MaxMem is stored/wired as raw bytes (xce_resource_t.unit="bytes"
|
||||
* for the maxmem resource definition) -- humanize for display only,
|
||||
* every other column stays as-is. */
|
||||
/* MaxMem/MaxSwap/MaxTmp are stored/wired as raw bytes (see
|
||||
* is_bytes_valued_resource) -- humanize for display only, every
|
||||
* other column stays as-is. */
|
||||
char maxmem_buf[64];
|
||||
char maxswap_buf[64];
|
||||
char maxtmp_buf[64];
|
||||
|
||||
format_bytes_human(workers[i]->attrs->maxmem, maxmem_buf,
|
||||
sizeof(maxmem_buf));
|
||||
format_bytes_human(workers[i]->attrs->maxswap, maxswap_buf,
|
||||
sizeof(maxswap_buf));
|
||||
format_bytes_human(workers[i]->attrs->maxtmp, maxtmp_buf,
|
||||
sizeof(maxtmp_buf));
|
||||
|
||||
printf("%-20s%-8s%-25s%-25s%12f%12d%12s%12f%12f%12d\n",
|
||||
printf("%-20s%-8s%-25s%-25s%12f%12d%12s%12s%12s%12d\n",
|
||||
workers[i]->name, worker_status_str(workers[i]->status),
|
||||
workers[i]->attrs->hosttype, workers[i]->attrs->hostmodel,
|
||||
workers[i]->attrs->cpufactor, workers[i]->attrs->maxcpus,
|
||||
maxmem_buf, workers[i]->attrs->maxswap,
|
||||
workers[i]->attrs->maxtmp, workers[i]->attrs->ndisks);
|
||||
maxmem_buf, maxswap_buf, maxtmp_buf,
|
||||
workers[i]->attrs->ndisks);
|
||||
}
|
||||
|
||||
free_Worker_list(rc, workers);
|
||||
|
|
@ -2038,15 +2412,19 @@ cleanup:
|
|||
/*
|
||||
* Resource names whose canonical stored/wire value is raw bytes (not a
|
||||
* generic schema-driven unit system -- deliberately kept as a small,
|
||||
* explicit, client-side list rather than a wire field, since exactly one
|
||||
* resource needs this today and a wire-level unit hint would have grown
|
||||
* every Resource/ResourceVal message regardless of whether it's used).
|
||||
* CLI display picks the best-fit human unit adaptively (format_bytes_human);
|
||||
* the stored value is never converted.
|
||||
* explicit, client-side list rather than a wire field, since only a
|
||||
* handful of resources need this and a wire-level unit hint would have
|
||||
* grown every Resource/ResourceVal message regardless of whether it's
|
||||
* used). CLI display picks the best-fit human unit adaptively
|
||||
* (format_bytes_human); the stored value is never converted. Covers every
|
||||
* memory/disk-scale built-in host attribute and load index (maxmem, mem,
|
||||
* tmp, maxswap, maxtmp) -- deliberately NOT maxcpus/ndisks (plain counts)
|
||||
* or cpuf/r1m/r15s (unitless factors/averages).
|
||||
*/
|
||||
static int is_bytes_valued_resource(const char* name)
|
||||
{
|
||||
static const char* const BYTES_VALUED_RESOURCES[] = { "maxmem" };
|
||||
static const char* const BYTES_VALUED_RESOURCES[]
|
||||
= { "maxmem", "mem", "tmp", "maxswap", "maxtmp" };
|
||||
const int nres = (int)(sizeof(BYTES_VALUED_RESOURCES)
|
||||
/ sizeof(BYTES_VALUED_RESOURCES[0]));
|
||||
int i;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue