myxce/src/controller/opupd.c
Rongsong Shen 70360ccfe8
fix(controller): resource hooks only carry values the controller accepted (bug-1583)
do_update_resvals() fired the AFTER_EVENT HOOK_DETAIL_RESOURCE_UPDATE with
the raw client xce_resval_list_t. update_resval_list() rejects unknown
resource names (-ENOENT) but only logged it, so both listeners -- the
scheduler push and the apiserver store (apiserver_resource_hook_fun ->
handler.go) -- received names the controller had rejected, and the
apiserver stored them.

- update_resval_list() takes an optional `applied` view (caller-sized,
  borrowed pointers) and fills it with just the accepted items.
- do_update_resvals() hands that view to the AFTER_EVENT hook, and skips
  it when nothing was applied. The request still owns every item.

New test_update_resval_list_reports_only_applied_items (RED before: both
items reported). Controller suite 1237/0. Live /xce-test-resources with
mem=5 totally-fake-resource-xyz=1: mem MATCH at controller, scheduler and
apiserver; the fake name is rejected at injection and appears 0 times in
scheduler.log / apiserver.log (verdict FAIL by design, for the rejected
name).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 03:19:17 +00:00

109 lines
3.4 KiB
C

#include "config.h"
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "cluster.h"
#include "xce/cluster_def.h"
static int do_update_resvals(cluster_t* c, endpoint_t* ep,
xce_request_t* request)
{
ep_private_t* priv = NULL;
int rc = -EPERM;
xce_reply_t reply;
priv = (ep_private_t*)ep->data;
TRACE("endpoint %p priv data %p", ep, priv);
init_reply_by_req(&reply, request);
if (priv != NULL) {
int idx = priv->u.idx;
if ((priv->role == HostRole_worker) && (idx >= 0)) {
worker_t* w = NULL;
rc = vget(c->workermgr, idx, (void**)&w);
if (w == NULL) {
rc = -E_XCE_BAD_WORKER;
} else {
xce_update_data_t* updatemsg = request->body.updatemsg;
xce_resval_list_t* resvals = updatemsg->data.resvals;
xce_resval_list_t applied = { .count = 0, .vals = NULL };
/* bug-1583: the AFTER_EVENT listeners (scheduler push,
* apiserver store) must only hear about the values this
* controller actually accepted -- not the raw client list,
* which may name resources it rejected. `applied` borrows
* pointers into `resvals`; nothing changes ownership. */
if ((resvals != NULL) && (resvals->count > 0)) {
applied.vals = (xce_resval_t**)calloc(
(size_t)resvals->count, sizeof(xce_resval_t*));
}
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_RESOURCE,
HOOK_BEFORE_EVENT, HOOK_DETAIL_RESOURCE_UPDATE, c,
resvals);
rc = update_resval_list(c, w, resvals,
(applied.vals != NULL) ? &applied
: NULL);
if (applied.count > 0) {
xce_run_hooks(&(c->hooksys), XCE_CONTROLLER_HOOK_RESOURCE,
HOOK_AFTER_EVENT,
HOOK_DETAIL_RESOURCE_UPDATE, c, &applied);
}
free(applied.vals);
}
}
}
reply.status = rc;
reply.data_which = RequestReply_data_nodata;
xce_reply(ep, c->myhost, &reply);
return 0;
}
static int do_update_jobrusage(cluster_t* c, endpoint_t* ep,
xce_request_t* request)
{
int rc = 0;
xce_reply_t reply;
init_reply_by_req(&reply, request);
rc = update_job_rusages(c, ep, request);
reply.status = rc;
reply.data_which = RequestReply_data_nodata;
xce_reply(ep, c->myhost, &reply);
return rc;
}
int do_update_op(cluster_t* c, endpoint_t* ep, xce_request_t* request)
{
int rc = 0;
xce_reply_t reply;
init_reply_by_req(&reply, request);
switch (request->body.updatemsg->table) {
case Update_data_resvals:
rc = do_update_resvals(c, ep, request);
break;
case Update_data_jobrusages:
rc = do_update_jobrusage(c, ep, request);
break;
default:
rc = -ENOENT;
reply.status = rc;
reply.data_which = RequestReply_data_nodata;
xce_reply(ep, c->myhost, &reply);
break;
}
return rc;
}