blob: 63e4fe8757032f5ac6f5ec4d2802e74d725d8293 [file] [edit]
/***************************************************************************
* 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.
***************************************************************************/
#include <errno.h>
#include "slurm/slurm.h"
#include "src/common/data.h"
#include "src/common/log.h"
#include "src/common/pack.h"
#include "src/common/pack_data.h"
#include "src/common/sluid.h"
#include "src/common/slurm_protocol_pack.h"
#include "src/common/xbase64.h"
#include "src/common/xstring.h"
#include "src/interfaces/switch.h"
#include "src/lua/slurm_lua.h"
#include "src/slurmctld/node_scheduler.h"
#include "src/slurmctld/slurmscriptd.h"
/* Required Slurm plugin symbols: */
const char plugin_name[] = "switch lua plugin";
const char plugin_type[] = "switch/lua";
const uint32_t plugin_version = SLURM_VERSION_NUMBER;
/* Push the arguments and return how many, or -1 to abandon the call. */
typedef int (*lua_push_f)(lua_State *L, void *ctx);
/* Consume the results, which _run_lua() pops afterwards. */
typedef int (*lua_handle_f)(lua_State *L, void *ctx);
/* One job's call, in slurmctld or in the slurmscriptd child */
typedef struct {
switch_complete_cb_t complete_cb;
uint32_t job_id;
buf_t *job_info_buf;
job_info_msg_t *job_info_msg;
data_t *jobinfo;
int rc;
sluid_t sluid;
switch_start_cb_t start_cb;
bool teardown_needed;
} switch_lua_job_t;
static char *lua_script_path;
static time_t lua_script_last_loaded = (time_t) 0;
static lua_State *L = NULL;
static bool block_job_complete = false;
static bool setup_special_steps = false;
static const char *req_fxns[] = { NULL };
static const char log_ctx_key[] = "switch/lua log context";
/* Prefix log lines from the script with lua_function and job_id. */
static void _set_log_ctx(lua_State *L, const char *lua_function,
uint32_t job_id)
{
char *ctx = NULL;
if (job_id)
ctx = xstrdup_printf("%s JobId=%u", lua_function, job_id);
else
ctx = xstrdup(lua_function);
lua_pushstring(L, ctx);
lua_setfield(L, LUA_REGISTRYINDEX, log_ctx_key);
xfree(ctx);
}
static void _clear_log_ctx(lua_State *L)
{
lua_pushnil(L);
lua_setfield(L, LUA_REGISTRYINDEX, log_ctx_key);
}
static char *_log_prefix(lua_State *L)
{
char *prefix = NULL;
const char *ctx;
lua_getfield(L, LUA_REGISTRYINDEX, log_ctx_key);
if ((ctx = lua_tostring(L, -1)))
prefix = xstrdup_printf("%s: %s", plugin_type, ctx);
else
prefix = xstrdup(plugin_type);
lua_pop(L, 1);
return prefix;
}
/* slurm.log([level,] msg), with the levels of the generic slurm.log */
static int _log_lua_msg(lua_State *L)
{
int level = 0;
const char *msg;
char *prefix;
if (lua_gettop(L) > 1) {
level = (int) lua_tonumber(L, 1);
msg = lua_tostring(L, 2);
} else {
msg = lua_tostring(L, 1);
}
prefix = _log_prefix(L);
if (level > 4)
debug4("%s: %s", prefix, msg);
else if (level == 4)
debug3("%s: %s", prefix, msg);
else if (level == 3)
debug2("%s: %s", prefix, msg);
else if (level == 2)
debug("%s: %s", prefix, msg);
else if (level == 1)
verbose("%s: %s", prefix, msg);
else if (level == 0)
info("%s: %s", prefix, msg);
xfree(prefix);
return 0;
}
/* slurm.error(msg) */
static int _log_lua_error(lua_State *L)
{
const char *msg = lua_tostring(L, 1);
char *prefix = _log_prefix(L);
error("%s: %s", prefix, msg);
xfree(prefix);
return 0;
}
/* slurm.user_msg() has no user to reach here, so it logs instead. */
static int _log_lua_user_msg(lua_State *L)
{
const char *msg = lua_tostring(L, 1);
char *prefix = _log_prefix(L);
info("%s: user message: %s", prefix, msg);
xfree(prefix);
return 0;
}
static const struct luaL_Reg slurm_functions[] = {
{ "log", _log_lua_msg },
{ "error", _log_lua_error },
{ "user_msg", _log_lua_user_msg },
{ NULL, NULL },
};
static void _loadscript_extra(lua_State *L)
{
slurm_lua_table_register(L, NULL, slurm_functions);
/* slurm_switch_job_complete() returns this to retry with nodes held */
lua_pushnumber(L, EAGAIN);
lua_setfield(L, -2, "EAGAIN");
/* Must be done after the slurm_functions are registered */
lua_setglobal(L, "slurm");
/* print() as the Lua manual describes it, but into the log */
luaL_loadstring(L, "local t = {} "
"for i = 1, select('#', ...) do "
"t[i] = tostring((select(i, ...))) "
"end "
"slurm.log(0, table.concat(t, '\\t'))");
lua_setglobal(L, "print");
}
/* Required for switch plugins: */
const uint32_t plugin_id = SWITCH_PLUGIN_LUA;
/* Call a no-argument Lua function; undefined or failing counts as false. */
static bool _call_bool_function(const char *lua_function)
{
bool ret = false;
int rc = SLURM_SUCCESS;
char *err = NULL;
if ((rc = slurm_lua_loadscript(&L, "switch/lua", lua_script_path,
req_fxns, &lua_script_last_loaded,
_loadscript_extra, NULL))) {
error("switch/lua: %s: failed to load script", __func__);
goto out;
}
lua_getglobal(L, lua_function);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
goto out;
}
_set_log_ctx(L, lua_function, 0);
rc = slurm_lua_pcall(L, 0, 1, &err, __func__);
_clear_log_ctx(L);
if (rc) {
error("%s: %s: %s failed: %s", plugin_type, lua_script_path,
lua_function, err);
xfree(err);
goto out;
}
ret = lua_toboolean(L, -1);
lua_pop(L, 1);
out:
return ret;
}
extern int init(void)
{
int rc = SLURM_SUCCESS;
if (!running_in_slurmctld() && !running_in_slurmstepd() &&
!run_in_daemon(IS_SLURMSCRIPTD))
return SLURM_SUCCESS;
lua_script_path = get_extra_conf_path("switch.lua");
if ((rc = slurm_lua_init()) != SLURM_SUCCESS)
return rc;
/* A slurmscriptd child only runs switch_lua_child_run() */
if (run_in_daemon(IS_SLURMSCRIPTD))
return SLURM_SUCCESS;
rc = slurm_lua_loadscript(&L, "switch/lua", lua_script_path, req_fxns,
&lua_script_last_loaded, _loadscript_extra,
NULL);
if (rc != SLURM_SUCCESS)
return rc;
block_job_complete =
_call_bool_function("slurm_switch_block_job_complete");
setup_special_steps =
_call_bool_function("slurm_switch_setup_special_steps");
return SLURM_SUCCESS;
}
extern void fini(void)
{
if (L) {
lua_close(L);
L = NULL;
}
slurm_lua_fini();
xfree(lua_script_path);
}
static int _lua_step_rec_field(lua_State *L, const stepd_step_rec_t *step,
const char *name)
{
if (!step) {
error("%s: step is NULL", __func__);
lua_pushnil(L);
} else if (!xstrcmp(name, "job_id")) {
lua_pushinteger(L, step->step_id.job_id);
} else if (!xstrcmp(name, "partition")) {
if (step->msg && step->msg->cred && step->msg->cred->arg)
lua_pushstring(L, step->msg->cred->arg->job_partition);
else
lua_pushnil(L);
} else if (!xstrcmp(name, "sluid")) {
char sluid_str[SLUID_STR_BYTES];
print_sluid(step->step_id.sluid, sluid_str, sizeof(sluid_str));
lua_pushstring(L, sluid_str);
} else {
lua_pushnil(L);
}
return 1;
}
static int _lua_step_rec_field_index(lua_State *L)
{
const char *name = luaL_checkstring(L, 2);
stepd_step_rec_t *step;
lua_getmetatable(L, -2);
lua_getfield(L, -1, "_step_rec_ptr");
step = lua_touserdata(L, -1);
return _lua_step_rec_field(L, step, name);
}
static void _lua_push_step_rec(stepd_step_rec_t *step, lua_State *L)
{
/* A table whose fields _lua_step_rec_field_index() reads from step */
lua_newtable(L);
lua_newtable(L);
lua_pushcfunction(L, _lua_step_rec_field_index);
lua_setfield(L, -2, "__index");
lua_pushlightuserdata(L, step);
lua_setfield(L, -2, "_step_rec_ptr");
lua_setmetatable(L, -2);
}
extern int switch_p_save(void)
{
return SLURM_SUCCESS;
}
extern int switch_p_restore(bool recover)
{
return SLURM_SUCCESS;
}
/* jobinfo is a data_t, packed as an exists flag followed by pack_data(). */
extern void switch_p_jobinfo_pack(void *job_info, buf_t *buffer,
uint16_t protocol_version)
{
uint32_t start;
if (!job_info) {
packbool(false, buffer);
return;
}
start = get_buf_offset(buffer);
packbool(true, buffer);
if (pack_data(job_info, protocol_version, buffer)) {
error("switch/lua: %s: failed to pack jobinfo, dropping it",
__func__);
set_buf_offset(buffer, start);
packbool(false, buffer);
}
}
extern int switch_p_jobinfo_unpack(void **job_info, buf_t *buffer,
uint16_t protocol_version)
{
bool exists;
data_t *data = NULL;
xassert(job_info);
*job_info = NULL;
safe_unpackbool(&exists, buffer);
if (!exists)
return SLURM_SUCCESS;
data = data_new();
if (unpack_data(data, protocol_version, buffer))
goto unpack_error;
*job_info = data;
return SLURM_SUCCESS;
unpack_error:
FREE_NULL_DATA(data);
return SLURM_ERROR;
}
extern void switch_p_jobinfo_free(job_record_t *job_ptr)
{
xassert(job_ptr);
FREE_NULL_DATA(job_ptr->switch_jobinfo);
}
/* switch/lua stepinfo is just a copy of the jobinfo data_t */
extern int switch_p_stepinfo_build(void **step_info, void *switch_jobinfo,
step_record_t *step_ptr)
{
data_t *data_step = NULL;
if (!step_info || !switch_jobinfo)
return SLURM_SUCCESS;
data_step = data_new();
if (!data_copy(data_step, switch_jobinfo)) {
error("switch/lua: %s: failed to copy jobinfo to stepinfo",
__func__);
FREE_NULL_DATA(data_step);
return SLURM_ERROR;
}
*step_info = data_step;
return SLURM_SUCCESS;
}
extern void switch_p_stepinfo_duplicate(void *source, void **dest)
{
data_t *data_step = NULL;
if (!source || !dest)
return;
data_step = data_new();
if (!data_copy(data_step, source)) {
error("switch/lua: %s: failed to duplicate stepinfo", __func__);
FREE_NULL_DATA(data_step);
return;
}
*dest = data_step;
}
extern void switch_p_stepinfo_free(void *step_info)
{
FREE_NULL_DATA(step_info);
}
extern void switch_p_stepinfo_pack(void *step_info, buf_t *buffer,
uint16_t protocol_version)
{
switch_p_jobinfo_pack(step_info, buffer, protocol_version);
}
extern int switch_p_stepinfo_unpack(void **step_info, buf_t *buffer,
uint16_t protocol_version)
{
return switch_p_jobinfo_unpack(step_info, buffer, protocol_version);
}
/*
* Whether slurm_switch_step_init() also runs for the batch and interactive
* steps, as slurm_switch_setup_special_steps() answered at load.
*/
extern bool switch_p_setup_special_steps(void)
{
return setup_special_steps;
}
/* Interpret numeric response from a lua function. */
static int _get_lua_rc(lua_State *L, const char *lua_function, int index)
{
int rc;
if (lua_isnumber(L, index)) {
rc = lua_tonumber(L, index);
} else {
error("switch/lua: %s: non-numeric return code, assuming success",
lua_function);
return SLURM_SUCCESS;
}
if (rc) {
const char *msg = rc > 0 ? "Not enforced" : "Enforced";
error("switch/lua: %s: reported failure (%d), %s",
lua_function, rc, msg);
}
if (rc > 0)
rc = SLURM_SUCCESS;
return rc;
}
static data_t *_get_data_stepinfo(stepd_step_rec_t *step_ptr)
{
if (!step_ptr || !step_ptr->switch_step || !step_ptr->switch_step->data)
return NULL;
return (data_t *) step_ptr->switch_step->data;
}
/* Runs in slurmstepd once per node for every normal step. */
extern int switch_p_job_preinit(stepd_step_rec_t *step)
{
int rc = SLURM_SUCCESS;
const char *lua_function = "slurm_switch_step_preinit";
data_t *data = NULL;
char *err = NULL;
int top;
/* The job was not set up by slurm_switch_job_start() */
if (!(data = _get_data_stepinfo(step)))
return SLURM_SUCCESS;
if ((rc = slurm_lua_loadscript(&L, "switch/lua", lua_script_path,
req_fxns, &lua_script_last_loaded,
_loadscript_extra, NULL))) {
error("switch/lua: %s: failed to load script", __func__);
goto end;
}
lua_getglobal(L, lua_function);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
rc = SLURM_SUCCESS;
goto end;
}
_lua_push_step_rec(step, L);
top = lua_gettop(L);
if (slurm_lua_from_data(L, data)) {
error("switch/lua: %s: failed to push switch_stepinfo",
__func__);
lua_settop(L, top);
lua_pushnil(L);
}
_set_log_ctx(L, lua_function, step->step_id.job_id);
rc = slurm_lua_pcall(L, 2, 1, &err, __func__);
_clear_log_ctx(L);
if (rc) {
error("%s: %s: %s failed: %s", plugin_type, lua_script_path,
lua_function, err);
xfree(err);
/* if a script fails, don't blow up the job start */
rc = SLURM_SUCCESS;
} else {
rc = _get_lua_rc(L, lua_function, -1);
lua_pop(L, 1);
}
end:
return rc;
}
static int _apply_env_table(lua_State *L, char ***env)
{
int rc = SLURM_SUCCESS;
int table;
if (lua_isnoneornil(L, -1)) {
lua_pop(L, 1);
return SLURM_SUCCESS;
}
if (!lua_istable(L, -1)) {
error("switch/lua: expected a table of env vars, got: %s",
luaL_typename(L, -1));
lua_pop(L, 1);
return SLURM_ERROR;
}
table = lua_gettop(L);
lua_pushnil(L);
while (lua_next(L, table)) {
/* stack: -1 value, -2 name */
const char *name, *value = NULL;
if (lua_type(L, -2) != LUA_TSTRING) {
error("switch/lua: %s: ignoring non-string name of type %s",
__func__, luaL_typename(L, -2));
lua_pop(L, 1);
continue;
}
name = lua_tostring(L, -2);
switch (lua_type(L, -1)) {
case LUA_TSTRING:
case LUA_TNUMBER:
value = lua_tostring(L, -1);
break;
case LUA_TBOOLEAN:
if (lua_toboolean(L, -1))
value = "1";
else
unsetenvp(*env, name);
break;
default:
error("switch/lua: %s: skipping %s, unsupported value or type: %s",
__func__, name, luaL_typename(L, -1));
rc = SLURM_ERROR;
break;
}
if (value && !env_array_overwrite(env, name, value)) {
error("switch/lua: %s: failed to set: %s", __func__,
name);
rc = SLURM_ERROR;
}
/* Pop the value, leave the key for the next lua_next(). */
lua_pop(L, 1);
}
lua_pop(L, 1);
return rc;
}
/*
* Runs in slurmstepd once per node for every normal step, and for the batch
* and interactive steps if slurm_switch_setup_special_steps() returned true.
* The script returns a code and a table of environment variables to set.
*/
extern int switch_p_job_init(stepd_step_rec_t *step)
{
int rc = SLURM_SUCCESS;
const char *lua_function = "slurm_switch_step_init";
data_t *data = NULL;
char *err = NULL;
int top;
/* The job was not set up by slurm_switch_job_start() */
if (!(data = _get_data_stepinfo(step)))
return SLURM_SUCCESS;
if ((rc = slurm_lua_loadscript(&L, "switch/lua", lua_script_path,
req_fxns, &lua_script_last_loaded,
_loadscript_extra, NULL))) {
error("switch/lua: %s: failed to load script", __func__);
goto end;
}
lua_getglobal(L, lua_function);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
rc = SLURM_SUCCESS;
goto end;
}
_lua_push_step_rec(step, L);
top = lua_gettop(L);
if (slurm_lua_from_data(L, data)) {
error("switch/lua: %s: failed to push switch_stepinfo",
__func__);
lua_settop(L, top);
lua_pushnil(L);
}
_set_log_ctx(L, lua_function, step->step_id.job_id);
rc = slurm_lua_pcall(L, 2, 2, &err, __func__);
_clear_log_ctx(L);
if (rc) {
error("%s: %s: %s failed: %s", plugin_type, lua_script_path,
lua_function, err);
xfree(err);
/* if a script fails, don't blow up the job start */
rc = SLURM_SUCCESS;
goto end;
} else {
rc = _get_lua_rc(L, lua_function, -2);
/* apply env changes, side effect is popping the table */
if (_apply_env_table(L, &step->env)) {
error("switch/lua: %s: failed to apply env updates",
lua_function);
}
lua_pop(L, 1);
}
end:
return rc;
}
/*
* Teardown counterpart of switch_p_job_preinit(). A negative return drains the
* node; a positive one or a script error is only logged.
*/
extern int switch_p_job_postfini(stepd_step_rec_t *step)
{
int rc = SLURM_SUCCESS;
const char *lua_function = "slurm_switch_step_postfini";
data_t *data = NULL;
char *err = NULL;
int top;
/* The job was not set up by slurm_switch_job_start() */
if (!(data = _get_data_stepinfo(step)))
return SLURM_SUCCESS;
if ((rc = slurm_lua_loadscript(&L, "switch/lua", lua_script_path,
req_fxns, &lua_script_last_loaded,
_loadscript_extra, NULL))) {
error("switch/lua: %s: failed to load script", __func__);
goto end;
}
lua_getglobal(L, lua_function);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
rc = SLURM_SUCCESS;
goto end;
}
_lua_push_step_rec(step, L);
top = lua_gettop(L);
if (slurm_lua_from_data(L, data)) {
error("switch/lua: %s: failed to push switch_stepinfo",
__func__);
lua_settop(L, top);
lua_pushnil(L);
}
_set_log_ctx(L, lua_function, step->step_id.job_id);
rc = slurm_lua_pcall(L, 2, 1, &err, __func__);
_clear_log_ctx(L);
if (rc) {
error("%s: %s: %s failed: %s", plugin_type, lua_script_path,
lua_function, err);
xfree(err);
/* if a script fails, don't drain all the nodes */
rc = SLURM_SUCCESS;
} else {
rc = _get_lua_rc(L, lua_function, -1);
lua_pop(L, 1);
}
end:
return rc;
}
extern int switch_p_job_attach(void *stepinfo, char ***env, uint32_t nodeid,
uint32_t procid, uint32_t nnodes,
uint32_t nprocs, uint32_t rank)
{
return SLURM_SUCCESS;
}
extern int switch_p_job_step_complete(void *stepinfo, char *nodelist)
{
return SLURM_SUCCESS;
}
/*
* Snapshot the job for a slurmscriptd child. Needs the caller's locks; the
* buffer outlives the job record.
*/
static buf_t *_pack_job_buf(job_record_t *job_ptr)
{
slurm_msg_t msg = {
.auth = SLURM_MSG_AUTH_INIT_AS_INTERNAL,
.protocol_version = SLURM_PROTOCOL_VERSION,
};
list_t *job_ids = list_create(NULL);
buf_t *info_buf;
list_append(job_ids, &job_ptr->job_id);
info_buf = pack_spec_jobs(&msg, job_ids, SHOW_DETAIL);
FREE_NULL_LIST(job_ids);
return info_buf;
}
/*
* Load the script into a new lua_State and call lua_function, once per
* slurmscriptd child. An undefined function returns SLURM_SUCCESS and leaves
* ctx untouched.
*/
static int _run_lua(const char *lua_function, uint32_t job_id, int nresults,
lua_push_f push, lua_handle_f handle, void *ctx)
{
lua_State *L = NULL;
time_t load_time = 0;
int rc, nargs;
char *err = NULL;
if ((rc = slurm_lua_loadscript(&L, "switch/lua", lua_script_path,
req_fxns, &load_time, _loadscript_extra,
NULL))) {
error("switch/lua: %s: failed to load script", __func__);
goto end;
}
lua_getglobal(L, lua_function);
if (lua_isnil(L, -1)) {
lua_pop(L, 1);
rc = SLURM_SUCCESS;
goto end;
}
if ((nargs = push(L, ctx)) < 0) {
lua_settop(L, 0);
rc = SLURM_ERROR;
goto end;
}
_set_log_ctx(L, lua_function, job_id);
rc = slurm_lua_pcall(L, nargs, nresults, &err, __func__);
_clear_log_ctx(L);
if (rc) {
error("%s: %s: %s failed: %s", plugin_type, lua_script_path,
lua_function, err);
xfree(err);
rc = SLURM_ERROR;
goto end;
}
rc = handle(L, ctx);
lua_pop(L, nresults);
end:
if (L)
lua_close(L);
return rc;
}
static int _push_job_info(lua_State *L, void *ctx)
{
switch_lua_job_t *job = ctx;
slurm_lua_push_job_info(&job->job_info_msg->job_array[0], L);
return 1;
}
/* Push the job, and its jobinfo or nil */
static int _push_job_complete_args(lua_State *L, void *ctx)
{
switch_lua_job_t *job = ctx;
_push_job_info(L, ctx);
if (!job->jobinfo) {
lua_pushnil(L);
} else if (slurm_lua_from_data(L, job->jobinfo)) {
error("switch/lua: slurm_switch_job_complete(): failed to push jobinfo for JobId=%u",
job->job_id);
return -1;
}
return 2;
}
static int _handle_job_start(lua_State *L, void *ctx)
{
switch_lua_job_t *job = ctx;
/* stack: -2 return code, -1 jobinfo dictionary or nil */
if (lua_isnumber(L, -2)) {
job->rc = lua_tonumber(L, -2);
} else {
error("switch/lua: slurm_switch_job_start(): non-numeric return code");
job->rc = SLURM_ERROR;
}
if (!lua_isnoneornil(L, -1)) {
bool refused = false;
if (slurm_lua_to_data(L, &job->jobinfo)) {
error("switch/lua: slurm_switch_job_start(): failed to parse return");
refused = true;
} else {
/* Fail setup if returned data is un-packable. */
buf_t *buf = init_buf(BUF_SIZE);
if (pack_data(job->jobinfo, SLURM_PROTOCOL_VERSION,
buf)) {
error("switch/lua: slurm_switch_job_start(): jobinfo is too large or too deeply nested to be saved");
refused = true;
}
FREE_NULL_BUFFER(buf);
}
if (refused) {
/* A successful setup still needs its teardown. */
job->teardown_needed = (job->rc == SLURM_SUCCESS);
FREE_NULL_DATA(job->jobinfo);
job->rc = SLURM_ERROR;
}
}
return job->rc;
}
static int _handle_job_complete(lua_State *L, void *ctx)
{
switch_lua_job_t *job = ctx;
/* Pass EAGAIN through; _get_lua_rc() maps other positives to success */
if (lua_isnumber(L, -1) && ((int) lua_tonumber(L, -1) == EAGAIN))
job->rc = EAGAIN;
else
job->rc = _get_lua_rc(L, "slurm_switch_job_complete", -1);
return job->rc;
}
/* A job that cannot be packed fails in the slurmscriptd child. */
static switch_lua_job_t *_job_args_create(job_record_t *job_ptr)
{
switch_lua_job_t *job = xmalloc(sizeof(*job));
if (!(job->job_info_buf = _pack_job_buf(job_ptr)))
error("switch/lua: %s: failed to pack %pJ", __func__, job_ptr);
job->sluid = job_ptr->step_id.sluid;
job->job_id = job_ptr->job_id;
job->rc = SLURM_SUCCESS;
return job;
}
static void _job_args_destroy(switch_lua_job_t *job)
{
if (!job)
return;
FREE_NULL_BUFFER(job->job_info_buf);
xfree(job);
}
/*
* Run a script function in the slurmscriptd child, and return its exit code.
* job_buf holds what slurmctld packed after the job info.
*/
extern int switch_lua_child_run(char *function, uint32_t job_id,
job_info_msg_t *job_info, buf_t *job_buf,
char **resp)
{
switch_lua_job_t job = {
.job_id = job_id,
.job_info_msg = job_info,
.rc = SLURM_SUCCESS,
};
buf_t *buf = NULL;
int rc;
if (!job_info || !job_info->record_count) {
error("switch/lua: %s(): no job info for JobId=%u", function,
job_id);
return 1;
}
if (!xstrcmp(function, "slurm_switch_job_complete")) {
if (!job_buf ||
switch_p_jobinfo_unpack((void **) &job.jobinfo, job_buf,
SLURM_PROTOCOL_VERSION)) {
error("switch/lua: %s(): failed to unpack jobinfo for JobId=%u",
function, job_id);
return 1;
}
rc = _run_lua(function, job_id, 1, _push_job_complete_args,
_handle_job_complete, &job);
FREE_NULL_DATA(job.jobinfo);
if (rc == EAGAIN)
return EAGAIN;
return rc ? 1 : 0;
}
if (xstrcmp(function, "slurm_switch_job_start")) {
error("switch/lua: %s: unknown function %s", __func__,
function);
return 1;
}
rc = _run_lua(function, job_id, 2, _push_job_info, _handle_job_start,
&job);
/* Return teardown_needed and the jobinfo in resp */
buf = init_buf(BUF_SIZE);
pack8(job.teardown_needed, buf);
pack8(job.jobinfo ? 1 : 0, buf);
if (job.jobinfo &&
pack_data(job.jobinfo, SLURM_PROTOCOL_VERSION, buf)) {
error("switch/lua: %s(): failed to pack jobinfo for JobId=%u",
function, job_id);
rc = SLURM_ERROR;
}
*resp = xbase64_encode((uint8_t *) get_buf_data(buf),
get_buf_offset(buf));
FREE_NULL_BUFFER(buf);
FREE_NULL_DATA(job.jobinfo);
return rc ? 1 : 0;
}
/* Read what switch_lua_child_run() returned for a setup */
static int _unpack_start_resp(switch_lua_job_t *job, char *resp)
{
uint8_t *data = NULL;
uint8_t teardown_needed = 0, has_jobinfo = 0;
buf_t *buf;
int len;
if (!resp || ((len = xbase64_decode(&data, resp)) < 0))
return SLURM_ERROR;
buf = create_buf((char *) data, len);
safe_unpack8(&teardown_needed, buf);
safe_unpack8(&has_jobinfo, buf);
job->teardown_needed = teardown_needed;
if (has_jobinfo) {
job->jobinfo = data_new();
if (unpack_data(job->jobinfo, SLURM_PROTOCOL_VERSION, buf))
goto unpack_error;
}
FREE_NULL_BUFFER(buf);
return SLURM_SUCCESS;
unpack_error:
FREE_NULL_DATA(job->jobinfo);
FREE_NULL_BUFFER(buf);
return SLURM_ERROR;
}
static void _undo_done(void *arg, int status, char *resp, bool lost)
{
switch_lua_job_t *job = arg;
if (lost)
error("switch/lua: slurm_switch_job_complete(): teardown of the late setup of JobId=%u was cut off",
job->job_id);
else if (WEXITSTATUS(status))
error("switch/lua: slurm_switch_job_complete(): teardown of the late setup of JobId=%u failed, exit status %u",
job->job_id, WEXITSTATUS(status));
else
info("switch/lua: slurm_switch_job_complete(): tore down the late setup of JobId=%u",
job->job_id);
_job_args_destroy(job);
}
/*
* A setup that was killed, or did not exit, reports nothing. slurmctld then
* treats it as one that timed out.
*/
static void _start_done(void *arg, int status, char *resp, bool lost)
{
switch_lua_job_t *job = arg;
switch_start_ret_t ret;
bool undo;
if (lost) {
info("switch/lua: slurm_switch_job_start(): setup of JobId=%u was cut off",
job->job_id);
_job_args_destroy(job);
return;
}
job->rc = WEXITSTATUS(status) ? SLURM_ERROR : SLURM_SUCCESS;
if (_unpack_start_resp(job, resp)) {
error("switch/lua: slurm_switch_job_start(): bad setup response for JobId=%u, exit status %u",
job->job_id, WEXITSTATUS(status));
job->teardown_needed = true;
job->rc = SLURM_ERROR;
}
/* start_cb() takes the slurmctld locks */
ret = job->start_cb(job->sluid, job->rc, job->jobinfo,
job->teardown_needed);
undo = ((ret == SWITCH_START_UNDO) && job->job_info_buf &&
(((job->rc == SLURM_SUCCESS) && job->jobinfo) ||
job->teardown_needed));
/* Hand the teardown the jobinfo of the late setup, if any */
if (undo)
switch_p_jobinfo_pack(job->jobinfo, job->job_info_buf,
SLURM_PROTOCOL_VERSION);
if (ret != SWITCH_START_KEPT)
FREE_NULL_DATA(job->jobinfo);
if (undo) {
slurmscriptd_run_switch_lua(job->job_id,
"slurm_switch_job_complete",
switch_get_timeout(),
job->job_info_buf, _undo_done, job);
return;
}
_job_args_destroy(job);
}
/* A teardown that was killed, or did not exit, is left to switch_timeout */
static void _complete_done(void *arg, int status, char *resp, bool lost)
{
switch_lua_job_t *job = arg;
int rc;
if (lost) {
info("switch/lua: slurm_switch_job_complete(): teardown of JobId=%u was cut off",
job->job_id);
_job_args_destroy(job);
return;
}
if (!WEXITSTATUS(status))
rc = SLURM_SUCCESS;
else if (WEXITSTATUS(status) == EAGAIN)
rc = EAGAIN;
else
rc = SLURM_ERROR;
job->complete_cb(job->sluid, rc);
_job_args_destroy(job);
}
/*
* Run slurm_switch_job_start() in a slurmscriptd child. A nil jobinfo leaves
* the job unmanaged. A failure is not torn down, so the script must undo its
* own work, unless it succeeded and only its jobinfo was refused, or the call
* exited without a usable response.
*/
extern int switch_p_job_start(job_record_t *job_ptr, bool test_only,
switch_start_cb_t start, bool *async)
{
switch_lua_job_t *job;
if (test_only)
return SLURM_SUCCESS;
/* Only jobs that request --network are set up */
if (!job_ptr->network || !job_ptr->network[0])
return SLURM_SUCCESS;
job = _job_args_create(job_ptr);
job->start_cb = start;
slurmscriptd_run_switch_lua(job->job_id, "slurm_switch_job_start",
switch_get_timeout(), job->job_info_buf,
_start_done, job);
*async = true;
return SLURM_SUCCESS;
}
/*
* Run slurm_switch_job_complete() with the job's jobinfo in a slurmscriptd
* child as slurmctld deallocates the nodes. Success releases them, slurm.EAGAIN
* retries later with them held, and any other failure drains them.
*/
extern int switch_p_job_complete(job_record_t *job_ptr,
switch_complete_cb_t complete,
switch_complete_mode_t *mode)
{
switch_lua_job_t *job;
/* Without jobinfo, only an abandoned setup is torn down or retried. */
if (!job_ptr->switch_jobinfo &&
(job_ptr->switch_st.state != SWITCH_STATE_START_ABANDONED) &&
(job_ptr->switch_st.state != SWITCH_STATE_COMPLETE_RETRY_WAIT))
return SLURM_SUCCESS;
job = _job_args_create(job_ptr);
job->complete_cb = complete;
if (job->job_info_buf)
switch_p_jobinfo_pack(job_ptr->switch_jobinfo,
job->job_info_buf,
SLURM_PROTOCOL_VERSION);
slurmscriptd_run_switch_lua(job->job_id, "slurm_switch_job_complete",
switch_get_timeout(), job->job_info_buf,
_complete_done, job);
if (block_job_complete)
*mode = SWITCH_COMPLETE_MODE_ASYNC_BLOCK_NODES;
else
*mode = SWITCH_COMPLETE_MODE_ASYNC;
return SLURM_SUCCESS;
}
extern uint32_t switch_p_job_channel(job_record_t *job_ptr, char *node_name)
{
return NO_VAL;
}
extern int switch_p_fs_init(stepd_step_rec_t *step)
{
return SLURM_SUCCESS;
}
extern void switch_p_stepmgr_fini(uint32_t job_id) {}
extern void switch_p_stepmgr_init(void) {}