blob: c41975410b2f48dd6d230f02cc55631339d442c6 [file] [edit]
/*****************************************************************************\
* hres_lua.c - Hierarchical resource plugin backed by a lua script
*****************************************************************************
* Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
* This file is part of Slurm, a resource management program.
* For details, see <https://slurm.schedmd.com/>.
* Please also read the included file: DISCLAIMER.
*
* Slurm is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* In addition, as a special exception, the copyright holders give permission
* to link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two. You must obey the GNU
* General Public License in all respects for all of the code used other than
* OpenSSL. If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not obligated to do
* so. If you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files in
* the program, then also delete it here.
*
* Slurm is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along
* with Slurm; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\*****************************************************************************/
#define _GNU_SOURCE
#include <lauxlib.h>
#include <lua.h>
#include <lualib.h>
#include "slurm/slurm.h"
#include "src/common/env.h"
#include "src/common/job_record.h"
#include "src/common/log.h"
#include "src/common/sluid.h"
#include "src/common/threadpool.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "src/interfaces/hres.h"
#include "src/lua/slurm_lua.h"
#include "src/slurmctld/job_scheduler.h"
#include "src/slurmctld/locks.h"
#include "src/slurmctld/slurmctld.h"
/*
* These variables are required by the generic plugin interface. See slurm.h.
*/
const uint32_t plugin_id = HRES_PLUGIN_LUA;
const char plugin_name[] = "hres lua plugin";
const char plugin_type[] = "hres/lua";
const uint32_t plugin_version = SLURM_VERSION_NUMBER;
/*
* Functions the script must define. hres_controller_job_async_prerun is
* optional, only the state built to run it requires it.
*/
/* clang-format off */
static const char *async_req_fxns[] = {
"hres_controller_job_async_prerun",
NULL
};
static const char *req_fxns[] = {
"hres_create_context",
"hres_destroy_context",
"hres_maintenance",
"hres_controller_job_prerun",
"hres_controller_job_revoke",
"hres_controller_job_epilog",
"hres_node_job_prolog",
"hres_node_job_epilog",
"hres_node_task_prolog",
"hres_node_task_epilog",
"hres_node_user_task_prolog",
NULL
};
/* clang-format on */
/*
* Per-context lua state, owned through hres_ctx_t->plugin_ctx. Node callbacks
* build a transient one per call instead.
*/
typedef struct {
lua_State *L;
time_t last_loaded; /* mtime of the loaded script */
pthread_mutex_t lock; /* guards L, callers hold different locks */
lua_State *maint_L;
time_t maint_last_loaded; /* mtime of the script loaded in maint_L */
char *script_path;
} hres_lua_ctx_t;
/*
* Arguments for one async pre-run thread, owned and freed by it. L is an
* independent state with the function and its arguments already pushed.
*/
typedef struct {
bool cancelled; /* revoked, the result is dropped */
uint16_t hres_id; /* HRES whose prep the result is stored on */
lua_State *L; /* pre-loaded async state, closed by the thread */
slurm_step_id_t step_id; /* job to re-find, sluid detects a requeue */
} async_prerun_t;
/*
* Detached async pre-run threads still running, so fini() can wait for them
* before closing lua. async_lock is never held across a lua call or a
* slurmctld lock. Arming is never refused: the sync pre-run has already run,
* so skipping the async half would launch the job half configured.
* async_list holds the threads whose result is not applied yet, so a revoke
* can cancel them.
*/
static int async_running = 0;
static list_t *async_list = NULL;
static pthread_cond_t async_cond = PTHREAD_COND_INITIALIZER;
static pthread_mutex_t async_lock = PTHREAD_MUTEX_INITIALIZER;
typedef struct {
int idx; /* 1-based lua array index of the next layer */
lua_State *st;
} foreach_push_layer_t;
/* Push a "{name = value, ...}" table built from a list of hres_config_t. */
static int _foreach_push_config(void *x, void *arg)
{
hres_config_t *config = x;
lua_State *st = arg;
lua_pushstring(st, config->value);
lua_setfield(st, -2, config->name);
return 0;
}
static void _push_config(lua_State *L, list_t *config)
{
lua_newtable(L);
if (config)
list_for_each_ro(config, _foreach_push_config, L);
}
/* Push one layer as "{count = N, name = "...", nodes = "..."}". */
static int _foreach_push_layer(void *x, void *arg)
{
hres_prep_layer_t *layer = x;
foreach_push_layer_t *args = arg;
lua_newtable(args->st);
lua_pushinteger(args->st, layer->count);
lua_setfield(args->st, -2, "count");
lua_pushstring(args->st, layer->layer_name);
lua_setfield(args->st, -2, "name");
lua_pushstring(args->st, layer->nodes);
lua_setfield(args->st, -2, "nodes");
lua_rawseti(args->st, -2, args->idx++);
return 0;
}
static void _push_layers(lua_State *L, list_t *layers)
{
foreach_push_layer_t args = {
.idx = 1,
.st = L,
};
lua_newtable(L);
if (layers)
list_for_each_ro(layers, _foreach_push_layer, &args);
}
/* Push one cluster layer as {disable_layer, name, nodes, total, used}. */
static int _foreach_push_hres_layer(void *x, void *arg)
{
hres_layer_t *layer = x;
foreach_push_layer_t *args = arg;
lua_newtable(args->st);
lua_pushboolean(args->st, layer->disable_layer);
lua_setfield(args->st, -2, "disable_layer");
lua_pushstring(args->st, layer->name);
lua_setfield(args->st, -2, "name");
lua_pushstring(args->st, layer->nodes);
lua_setfield(args->st, -2, "nodes");
lua_pushinteger(args->st, layer->total);
lua_setfield(args->st, -2, "total");
lua_pushinteger(args->st, layer->used);
lua_setfield(args->st, -2, "used");
lua_rawseti(args->st, -2, args->idx++);
return 0;
}
static void _push_hres_layers(lua_State *L, list_t *layers)
{
foreach_push_layer_t args = {
.idx = 1,
.st = L,
};
lua_newtable(L);
if (layers)
list_for_each_ro(layers, _foreach_push_hres_layer, &args);
}
/* Copy down and disable_layer back from the layer table at the stack top. */
static int _foreach_read_hres_layer(void *x, void *arg)
{
hres_layer_t *layer = x;
foreach_push_layer_t *args = arg;
lua_rawgeti(args->st, -1, args->idx++);
if (lua_istable(args->st, -1)) {
lua_getfield(args->st, -1, "down");
layer->down = lua_toboolean(args->st, -1);
lua_getfield(args->st, -2, "disable_layer");
layer->disable_layer = lua_toboolean(args->st, -1);
lua_pop(args->st, 2);
}
lua_pop(args->st, 1);
return 0;
}
/*
* Push what a node callback receives about the resource, as a single table:
* { name, payload, config = {...}, plugin_args = {...},
* layers = {{count, name, nodes}, ...} }
*/
static void _push_prep(lua_State *L, hres_prep_t *prep)
{
lua_newtable(L);
lua_pushstring(L, prep->name);
lua_setfield(L, -2, "name");
/* Opaque bytes from the pre-run, if any. */
if (prep->payload) {
lua_pushlstring(L, prep->payload, prep->payload_size);
lua_setfield(L, -2, "payload");
}
_push_config(L, prep->config);
lua_setfield(L, -2, "config");
_push_config(L, prep->plugin_args);
lua_setfield(L, -2, "plugin_args");
_push_layers(L, prep->layers);
lua_setfield(L, -2, "layers");
lua_pushboolean(L, prep->async_running);
lua_setfield(L, -2, "async_running");
}
/*
* Push the job identity: { job_id, sluid, user_id, user_name, group_id,
* account, partition, nodes, end_time }.
*/
static void _push_job(lua_State *L, hres_job_t *job)
{
char sluid[SLUID_STR_BYTES];
lua_newtable(L);
lua_pushinteger(L, job->step_id.job_id);
lua_setfield(L, -2, "job_id");
print_sluid(job->step_id.sluid, sluid, sizeof(sluid));
lua_pushstring(L, sluid);
lua_setfield(L, -2, "sluid");
lua_pushinteger(L, job->user_id);
lua_setfield(L, -2, "user_id");
lua_pushstring(L, job->user_name);
lua_setfield(L, -2, "user_name");
lua_pushinteger(L, job->group_id);
lua_setfield(L, -2, "group_id");
lua_pushstring(L, job->account);
lua_setfield(L, -2, "account");
lua_pushstring(L, job->partition);
lua_setfield(L, -2, "partition");
lua_pushstring(L, job->nodes);
lua_setfield(L, -2, "nodes");
lua_pushinteger(L, job->end_time);
lua_setfield(L, -2, "end_time");
}
/* Push what a context callback receives: { name, config = {...} }. */
static void _push_ctx(lua_State *L, hres_ctx_t *ctx)
{
lua_newtable(L);
lua_pushstring(L, ctx->name);
lua_setfield(L, -2, "name");
_push_config(L, ctx->config);
lua_setfield(L, -2, "config");
}
/*
* (Re)load script_path into *L and push the named function. Returns false with
* a clean stack when the script or the function is absent.
*
* Callers needing their own state pass a NULL *L and a zeroed last_loaded on
* every call: slurm_lua_loadscript() keys off the script mtime alone, so a
* retained mtime skips the load and leaves *L NULL. Costs one compile a call.
*/
static bool _start_call(lua_State **L, const char *script_path,
time_t *last_loaded, const char **fxns,
const char *func)
{
if (!script_path)
return false;
if (slurm_lua_loadscript(L, "hres/lua", script_path, fxns, last_loaded,
NULL, NULL))
return false;
lua_getglobal(*L, func);
if (lua_isnil(*L, -1)) {
lua_pop(*L, 1);
return false;
}
return true;
}
/*
* Call the function with nargs already on the stack and return its numeric
* result as a Slurm rc. A non-numeric or missing result is treated as success.
*/
static int _finish_call(lua_State *L, const char *func, int nargs,
char **err_msg)
{
int rc;
char *err_str = NULL;
if ((rc = slurm_lua_pcall(L, nargs, 1, &err_str, func))) {
error("hres/lua: %s: %s", func,
err_str ? err_str : slurm_strerror(rc));
if (err_msg)
SWAP(*err_msg, err_str);
xfree(err_str);
return rc;
}
if (lua_isnumber(L, -1))
rc = lua_tonumber(L, -1);
else
rc = SLURM_SUCCESS;
lua_pop(L, 1);
return rc;
}
/*
* _finish_call() for the pre-run: a second return value, a byte string,
* becomes the payload sent to the nodes. Anything else leaves no payload.
*/
static int _finish_prerun(lua_State *L, hres_prep_t *prep, char **err_msg)
{
int rc;
char *err_str = NULL;
if ((rc = slurm_lua_pcall(L, 3, 2, &err_str,
"hres_controller_job_prerun"))) {
error("hres/lua: hres_controller_job_prerun: %s",
err_str ? err_str : slurm_strerror(rc));
if (err_msg)
SWAP(*err_msg, err_str);
xfree(err_str);
return rc;
}
/* Stack: rc at -2, payload at -1. */
if (lua_isstring(L, -1)) {
size_t len;
const char *payload = lua_tolstring(L, -1, &len);
if (len) {
prep->payload = xmalloc(len);
memcpy(prep->payload, payload, len);
prep->payload_size = len;
}
}
rc = lua_isnumber(L, -2) ? lua_tonumber(L, -2) : SLURM_SUCCESS;
lua_pop(L, 2);
return rc;
}
/* Only stops Lua code, not a blocking C call such as os.execute(). */
static void _deadline_hook(lua_State *L, lua_Debug *ar)
{
time_t deadline;
lua_getfield(L, LUA_REGISTRYINDEX, "hres_deadline");
deadline = lua_tointeger(L, -1);
lua_pop(L, 1);
if (time(NULL) >= deadline) {
/* A pcall() in the script catches the error, so remember it. */
lua_pushboolean(L, true);
lua_setfield(L, LUA_REGISTRYINDEX, "hres_timed_out");
luaL_error(L, "timed out");
}
}
static int _env_index(lua_State *L)
{
char **env = lua_touserdata(L, lua_upvalueindex(1));
lua_pushstring(L, getenvp(env, luaL_checkstring(L, 2)));
return 1;
}
/* Push env as a table that looks names up in it on access, no copy. */
static void _push_env(lua_State *L, char **env)
{
lua_newtable(L);
lua_newtable(L);
lua_pushlightuserdata(L, env);
lua_pushcclosure(L, _env_index, 1);
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
}
/* Run a node callback in a transient state built from the credential. */
static int _node_call(const char *func, hres_prep_t *prep, hres_job_t *job,
uint32_t node_tid, uint32_t global_tid, char **env,
int nargs, uint16_t timeout)
{
int rc = SLURM_SUCCESS;
lua_State *L = NULL;
time_t last_loaded = 0;
if (_start_call(&L, hres_config_value(prep->config, "path"),
&last_loaded, req_fxns, func)) {
_push_prep(L, prep);
_push_job(L, job);
if (nargs == 5) {
lua_pushinteger(L, node_tid);
lua_pushinteger(L, global_tid);
_push_env(L, env);
}
if (timeout && (timeout != NO_VAL16)) {
lua_pushinteger(L, time(NULL) + timeout);
lua_setfield(L, LUA_REGISTRYINDEX, "hres_deadline");
lua_sethook(L, _deadline_hook, LUA_MASKCOUNT, 1000);
}
rc = _finish_call(L, func, nargs, NULL);
lua_getfield(L, LUA_REGISTRYINDEX, "hres_timed_out");
if (lua_toboolean(L, -1) && (rc == SLURM_SUCCESS)) {
error("hres/lua: %s: timed out", func);
rc = SLURM_ERROR;
}
lua_pop(L, 1);
} else {
rc = SLURM_ERROR;
}
if (L)
lua_close(L);
return rc;
}
extern int hres_p_create_context(hres_ctx_t *ctx, list_t *layers)
{
hres_lua_ctx_t *lua_ctx = xmalloc(sizeof(*lua_ctx));
slurm_mutex_init(&lua_ctx->lock);
lua_ctx->script_path = xstrdup(hres_config_value(ctx->config, "path"));
ctx->plugin_ctx = lua_ctx;
if (!lua_ctx->script_path) {
error("hres/lua: HRES %s has no 'path' in its plugin config",
ctx->name);
return SLURM_ERROR;
}
if (_start_call(&lua_ctx->L, lua_ctx->script_path,
&lua_ctx->last_loaded, req_fxns,
"hres_create_context")) {
_push_ctx(lua_ctx->L, ctx);
_push_hres_layers(lua_ctx->L, layers);
return _finish_call(lua_ctx->L, "hres_create_context", 2, NULL);
}
return SLURM_ERROR;
}
extern int hres_p_destroy_context(hres_ctx_t *ctx)
{
hres_lua_ctx_t *lua_ctx = ctx->plugin_ctx;
int rc = SLURM_SUCCESS;
if (!lua_ctx)
return SLURM_SUCCESS;
if (_start_call(&lua_ctx->L, lua_ctx->script_path,
&lua_ctx->last_loaded, req_fxns,
"hres_destroy_context")) {
_push_ctx(lua_ctx->L, ctx);
rc = _finish_call(lua_ctx->L, "hres_destroy_context", 1, NULL);
}
if (lua_ctx->L)
lua_close(lua_ctx->L);
if (lua_ctx->maint_L)
lua_close(lua_ctx->maint_L);
slurm_mutex_destroy(&lua_ctx->lock);
xfree(lua_ctx->script_path);
xfree(lua_ctx);
ctx->plugin_ctx = NULL;
return rc;
}
extern int hres_p_maintenance(hres_ctx_t *ctx, list_t *layers)
{
hres_lua_ctx_t *lua_ctx = ctx->plugin_ctx;
int rc = SLURM_SUCCESS;
if (!lua_ctx)
return SLURM_SUCCESS;
if (_start_call(&lua_ctx->maint_L, lua_ctx->script_path,
&lua_ctx->maint_last_loaded, req_fxns,
"hres_maintenance")) {
foreach_push_layer_t args = {
.idx = 1,
.st = lua_ctx->maint_L,
};
_push_ctx(lua_ctx->maint_L, ctx);
_push_hres_layers(lua_ctx->maint_L, layers);
/* Keep the layers table below the function to read it back. */
lua_pushvalue(lua_ctx->maint_L, -1);
lua_insert(lua_ctx->maint_L, -4);
rc = _finish_call(lua_ctx->maint_L, "hres_maintenance", 2,
NULL);
if (layers)
list_for_each_ro(layers, _foreach_read_hres_layer,
&args);
lua_pop(lua_ctx->maint_L, 1);
}
return rc;
}
static int _find_prep_by_hres_id(void *x, void *key)
{
hres_prep_t *prep = x;
uint16_t *hres_id = key;
return (prep->hres_id == *hres_id);
}
/*
* Run the async pre-run, then under the job write lock store its payload and
* release the launch deferral.
*/
static void *_async_prerun_thread(void *x)
{
async_prerun_t *args = x;
slurmctld_lock_t job_write_lock = {
.job = WRITE_LOCK,
.node = WRITE_LOCK,
.fed = READ_LOCK,
};
int rc;
char *err_str = NULL;
char *payload = NULL;
size_t payload_size = 0;
job_record_t *job_ptr;
hres_prep_t *prep;
bool cancelled;
/* Unlocked, the state belongs to this thread. */
if (!args->L) /* Failed to load, logged by slurm_lua_loadscript(). */
rc = SLURM_ERROR;
else
rc = slurm_lua_pcall(args->L, 3, 2, &err_str,
"hres_controller_job_async_prerun");
/* Already logged by slurm_lua_pcall(). */
xfree(err_str);
if (!rc && lua_isstring(args->L, -1)) {
size_t len;
const char *buf = lua_tolstring(args->L, -1, &len);
if (len) {
payload = xmalloc(len);
memcpy(payload, buf, len);
payload_size = len;
}
}
if (!rc)
rc = lua_isnumber(args->L, -2) ? lua_tonumber(args->L, -2) :
SLURM_SUCCESS;
lock_slurmctld(job_write_lock);
/* A revoke runs under the job write lock too, it cannot race this. */
slurm_mutex_lock(&async_lock);
cancelled = args->cancelled;
list_delete_ptr(async_list, args);
slurm_mutex_unlock(&async_lock);
/* By job_id, a requeue gives the job a new sluid. */
if ((job_ptr = find_job_record(args->step_id.job_id))) {
/* Revoked, requeued or ended meanwhile, the result is stale. */
bool stale = cancelled || IS_JOB_COMPLETED(job_ptr) ||
(job_ptr->step_id.sluid != args->step_id.sluid);
if (!stale && (prep = list_find_first(job_ptr->hres_prep,
_find_prep_by_hres_id,
&args->hres_id))) {
prep->async_running = false;
if (!rc && payload) {
xfree(prep->payload);
prep->payload = payload;
prep->payload_size = payload_size;
payload = NULL;
}
}
if (!stale && rc) {
error("hres/lua: async pre-run failed for %pJ: %s",
job_ptr, slurm_strerror(rc));
job_requeue_or_kill(job_ptr);
}
prolog_running_decr(job_ptr);
}
unlock_slurmctld(job_write_lock);
xfree(payload);
if (args->L)
lua_close(args->L);
xfree(args);
/* Last, fini() waits on this before closing lua. */
slurm_mutex_lock(&async_lock);
async_running--;
slurm_cond_broadcast(&async_cond);
slurm_mutex_unlock(&async_lock);
return NULL;
}
extern int hres_p_validate_args(hres_ctx_t *ctx, list_t *plugin_args)
{
hres_lua_ctx_t *lua_ctx = ctx->plugin_ctx;
int rc = SLURM_SUCCESS;
if (!lua_ctx)
return SLURM_SUCCESS;
/* Optional, every argument is accepted without it. */
slurm_mutex_lock(&lua_ctx->lock);
if (_start_call(&lua_ctx->L, lua_ctx->script_path,
&lua_ctx->last_loaded, req_fxns,
"hres_validate_args")) {
_push_ctx(lua_ctx->L, ctx);
_push_config(lua_ctx->L, plugin_args);
rc = _finish_call(lua_ctx->L, "hres_validate_args", 2, NULL);
}
slurm_mutex_unlock(&lua_ctx->lock);
return rc;
}
extern int hres_p_controller_job_prerun(job_record_t *job_ptr, hres_ctx_t *ctx,
hres_prep_t *prep, char **err_msg)
{
hres_lua_ctx_t *lua_ctx = ctx->plugin_ctx;
hres_job_t job = {
.account = job_ptr->account,
.end_time = job_ptr->end_time,
.group_id = job_ptr->group_id,
.nodes = job_ptr->nodes,
.partition = job_ptr->part_ptr ? job_ptr->part_ptr->name : NULL,
.step_id = STEP_ID_FROM_JOB_RECORD(job_ptr),
.user_id = job_ptr->user_id,
.user_name = job_ptr->user_name,
};
lua_State *async_L = NULL;
time_t async_last_loaded = 0;
int rc = SLURM_SUCCESS;
bool async;
async_prerun_t *args;
if (!lua_ctx)
return SLURM_SUCCESS;
slurm_mutex_lock(&lua_ctx->lock);
if (_start_call(&lua_ctx->L, lua_ctx->script_path,
&lua_ctx->last_loaded, req_fxns,
"hres_controller_job_prerun")) {
_push_ctx(lua_ctx->L, ctx);
_push_job(lua_ctx->L, &job);
_push_prep(lua_ctx->L, prep);
rc = _finish_prerun(lua_ctx->L, prep, err_msg);
}
/* Only build a second state when the script has an async pre-run. */
async = lua_ctx->L &&
slurm_lua_is_function_defined(
lua_ctx->L, "hres_controller_job_async_prerun");
slurm_mutex_unlock(&lua_ctx->lock);
if (rc || !async)
return rc;
/*
* Push the arguments while ctx/job/prep are still valid and hand the
* state to a detached thread, which frees it and args. Without a state
* the thread fails the async pre-run.
*/
if (_start_call(&async_L, lua_ctx->script_path, &async_last_loaded,
async_req_fxns, "hres_controller_job_async_prerun")) {
_push_ctx(async_L, ctx);
_push_job(async_L, &job);
_push_prep(async_L, prep);
}
args = xmalloc(sizeof(*args));
args->hres_id = prep->hres_id;
args->step_id = job.step_id;
args->L = async_L;
/* Count before arming so fini() cannot miss a new thread. */
slurm_mutex_lock(&async_lock);
async_running++;
list_append(async_list, args);
/* Defer launch until the thread completes. */
job_ptr->details->prolog_running++;
job_state_set_flag(job_ptr, JOB_CONFIGURING);
prep->async_running = true;
slurm_thread_create_detached(NULL, _async_prerun_thread, args);
slurm_mutex_unlock(&async_lock);
return rc;
}
static int _find_async_prerun(void *x, void *key)
{
async_prerun_t *args = x;
async_prerun_t *target = key;
return ((args->hres_id == target->hres_id) &&
(args->step_id.job_id == target->step_id.job_id) &&
(args->step_id.sluid == target->step_id.sluid));
}
extern int hres_p_controller_job_revoke(job_record_t *job_ptr, hres_ctx_t *ctx,
hres_prep_t *prep)
{
hres_lua_ctx_t *lua_ctx = ctx->plugin_ctx;
hres_job_t job = {
.account = job_ptr->account,
.end_time = job_ptr->end_time,
.group_id = job_ptr->group_id,
.nodes = job_ptr->nodes,
.partition = job_ptr->part_ptr ? job_ptr->part_ptr->name : NULL,
.step_id = STEP_ID_FROM_JOB_RECORD(job_ptr),
.user_id = job_ptr->user_id,
.user_name = job_ptr->user_name,
};
async_prerun_t key = {
.hres_id = prep->hres_id,
.step_id = job.step_id,
};
async_prerun_t *args;
int rc = SLURM_SUCCESS;
/*
* Drop the async pre-run's result. Its thread keeps prolog_running
* until it ends, so the job is not scheduled again before that.
*/
slurm_mutex_lock(&async_lock);
if ((args = list_find_first(async_list, _find_async_prerun, &key))) {
args->cancelled = true;
job_state_unset_flag(job_ptr, JOB_CONFIGURING);
}
slurm_mutex_unlock(&async_lock);
if (!lua_ctx)
return SLURM_SUCCESS;
slurm_mutex_lock(&lua_ctx->lock);
if (_start_call(&lua_ctx->L, lua_ctx->script_path,
&lua_ctx->last_loaded, req_fxns,
"hres_controller_job_revoke")) {
_push_ctx(lua_ctx->L, ctx);
_push_job(lua_ctx->L, &job);
_push_prep(lua_ctx->L, prep);
rc = _finish_call(lua_ctx->L, "hres_controller_job_revoke", 3,
NULL);
}
slurm_mutex_unlock(&lua_ctx->lock);
return rc;
}
extern int hres_p_controller_job_epilog(job_record_t *job_ptr, hres_ctx_t *ctx,
hres_prep_t *prep)
{
hres_lua_ctx_t *lua_ctx = ctx->plugin_ctx;
hres_job_t job = {
.account = job_ptr->account,
.end_time = job_ptr->end_time,
.group_id = job_ptr->group_id,
.nodes = job_ptr->nodes,
.partition = job_ptr->part_ptr ? job_ptr->part_ptr->name : NULL,
.step_id = STEP_ID_FROM_JOB_RECORD(job_ptr),
.user_id = job_ptr->user_id,
.user_name = job_ptr->user_name,
};
int rc = SLURM_SUCCESS;
if (!lua_ctx)
return SLURM_SUCCESS;
slurm_mutex_lock(&lua_ctx->lock);
if (_start_call(&lua_ctx->L, lua_ctx->script_path,
&lua_ctx->last_loaded, req_fxns,
"hres_controller_job_epilog")) {
_push_ctx(lua_ctx->L, ctx);
_push_job(lua_ctx->L, &job);
_push_prep(lua_ctx->L, prep);
rc = _finish_call(lua_ctx->L, "hres_controller_job_epilog", 3,
NULL);
}
slurm_mutex_unlock(&lua_ctx->lock);
return rc;
}
extern int hres_p_node_job_prolog(hres_prep_t *prep, hres_job_t *job)
{
return _node_call("hres_node_job_prolog", prep, job, 0, 0, NULL, 2,
slurm_conf.prolog_timeout);
}
extern int hres_p_node_job_epilog(hres_prep_t *prep, hres_job_t *job)
{
return _node_call("hres_node_job_epilog", prep, job, 0, 0, NULL, 2,
slurm_conf.epilog_timeout);
}
extern int hres_p_node_task_prolog(hres_prep_t *prep, hres_job_t *job,
uint32_t node_tid, uint32_t global_tid,
char **env)
{
return _node_call("hres_node_task_prolog", prep, job, node_tid,
global_tid, env, 5, slurm_conf.prolog_timeout);
}
extern int hres_p_node_task_epilog(hres_prep_t *prep, hres_job_t *job,
uint32_t node_tid, uint32_t global_tid,
char **env)
{
return _node_call("hres_node_task_epilog", prep, job, node_tid,
global_tid, env, 5, slurm_conf.epilog_timeout);
}
extern int hres_p_node_user_task_prolog(hres_prep_t *prep, hres_job_t *job,
uint32_t node_tid, uint32_t global_tid,
char **env)
{
return _node_call("hres_node_user_task_prolog", prep, job, node_tid,
global_tid, env, 5, slurm_conf.prolog_timeout);
}
extern int init(void)
{
async_list = list_create(NULL);
/* Only dlopen liblua, states are built lazily per context. */
return slurm_lua_init();
}
/*
* A script blocked in a network call delays this, it cannot be interrupted in
* process.
*/
static void _wait_async(void)
{
slurm_mutex_lock(&async_lock);
while (async_running)
slurm_cond_wait(&async_cond, &async_lock);
slurm_mutex_unlock(&async_lock);
}
extern int hres_p_controller_stop(void)
{
/* The async pre-runs store their result under the job write lock. */
_wait_async();
return SLURM_SUCCESS;
}
extern void fini(void)
{
/* Threads close their own state, before slurm_lua_fini(). */
_wait_async();
FREE_NULL_LIST(async_list);
slurm_lua_fini();
}