blob: ba793735f398519e2d718376380c1e4c2ff04ee3 [file] [edit]
/*****************************************************************************\
* runtime_lua.c - lua job runtime plugin.
*****************************************************************************
* 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 "config.h"
#include "slurm/slurm.h"
#include "src/common/read_config.h"
#include "src/common/slurm_protocol_defs.h"
#include "src/common/xmalloc.h"
#include "src/interfaces/runtime.h"
#include "src/lua/slurm_lua.h"
/*
* These variables are required by the generic plugin interface. See plugin.h
* for more information.
*/
const char plugin_name[] = "lua runtime plugin";
const char plugin_type[] = "runtime/lua";
const uint32_t plugin_version = SLURM_VERSION_NUMBER;
static char *lua_script_path = NULL;
static bool lua_inited = false;
/* Functions that runtime.lua must provide */
static const char *req_fxns[] = {
"slurm_runtime_setup",
"slurm_runtime_cleanup",
"slurm_runtime_task_init",
"slurm_runtime_run",
NULL,
};
/* Pushes step id as string and numeric task id or NIL */
static int _push_step_id(lua_State *L, stepd_step_rec_t *step,
stepd_step_task_info_t *task)
{
slurm_selected_step_t id = SLURM_SELECTED_STEP_INITIALIZER;
char *id_str = NULL;
int rc;
id.step_id = step->step_id;
id.array_task_id = step->array_task_id;
/* fmt_job_id_string() appends the suffix to the master id, not ours. */
if (step->array_task_id != NO_VAL) {
id.step_id.job_id = step->array_job_id;
} else if (step->het_job_id && (step->het_job_id != NO_VAL)) {
id.het_job_offset = step->het_job_offset;
id.step_id.job_id = step->het_job_id;
}
if (!(rc = fmt_job_id_string(&id, &id_str)))
lua_pushstring(L, id_str);
xfree(id_str);
if (task)
lua_pushinteger(L, task->id);
else
lua_pushnil(L);
return rc;
}
/*
* Call fxn in the script.
* IN fxn - name of the function to call, which must be in req_fxns
* IN step - step record pushed as the first argument
* IN task - task record pushed as the second argument, or NULL for none
* OUT script_rc - value the script returned, only set on SLURM_SUCCESS
* RET SLURM_SUCCESS if the script ran, or an error if it could not be called
*
* The lua state is created here and closed again before returning. Nothing is
* shared between calls, so callers in separate threads cannot corrupt each
* other's state and no locking is needed. The cost is that the script is
* re-read and re-compiled every time, which is the same trade burst_buffer/lua
* makes for the same reason.
*/
static int _call_lua(const char *fxn, stepd_step_rec_t *step,
stepd_step_task_info_t *task, int *script_rc)
{
int rc = EINVAL;
char *err = NULL;
lua_State *L = NULL;
time_t load_time = 0;
if ((rc = slurm_lua_loadscript(&L, plugin_type, lua_script_path,
req_fxns, &load_time, NULL, NULL)))
return rc;
lua_getglobal(L, fxn);
if (lua_isnil(L, -1)) {
error("%s: %s: %s is not defined", plugin_type, lua_script_path,
fxn);
rc = ESLURM_LUA_FUNC_NOT_FOUND;
goto cleanup;
}
if ((rc = _push_step_id(L, step, task)))
goto cleanup;
slurm_lua_stack_dump(plugin_type, "before lua_pcall", L);
if ((rc = slurm_lua_pcall(L, 2, 1, &err, __func__))) {
error("%s: %s: %s failed: %s", plugin_type, lua_script_path,
fxn, err);
xfree(err);
lua_close(L);
return rc;
}
slurm_lua_stack_dump(plugin_type, "after lua_pcall", L);
if (lua_isnumber(L, -1)) {
*script_rc = lua_tonumber(L, -1);
} else {
error("%s: %s: %s returned a non-numeric value", plugin_type,
lua_script_path, fxn);
rc = SLURM_ERROR;
goto cleanup;
}
lua_pop(L, 1);
cleanup:
lua_close(L);
return rc;
}
/*
* Call a client-side hook in the script, if the script defines it.
* IN fxn - name of the function to call
* IN id - step id, or just the job id, the op is for, in the same form
* _push_step_id() pushes, or NULL when not even the job is known yet.
* Pushed as the first argument, or NIL. The task id is always NIL.
* OUT script_rc - value the script returned, only set on SLURM_SUCCESS
* RET SLURM_SUCCESS if the script ran or does not define fxn, or an error if
* it could not be called
*/
static int _call_lua_hook(const char *fxn, const char *id, int *script_rc)
{
int rc = EINVAL;
char *err = NULL;
lua_State *L = NULL;
time_t load_time = 0;
*script_rc = SLURM_SUCCESS;
if ((rc = slurm_lua_loadscript(&L, plugin_type, lua_script_path,
req_fxns, &load_time, NULL, NULL)))
return rc;
if (!slurm_lua_is_function_defined(L, fxn)) {
lua_close(L);
return SLURM_SUCCESS;
}
lua_getglobal(L, fxn);
if (id)
lua_pushstring(L, id);
else
lua_pushnil(L);
lua_pushnil(L);
slurm_lua_stack_dump(plugin_type, "before lua_pcall", L);
if ((rc = slurm_lua_pcall(L, 2, 1, &err, __func__))) {
error("%s: %s: %s failed: %s", plugin_type, lua_script_path,
fxn, err);
xfree(err);
lua_close(L);
return rc;
}
slurm_lua_stack_dump(plugin_type, "after lua_pcall", L);
if (lua_isnumber(L, -1)) {
*script_rc = lua_tonumber(L, -1);
} else {
error("%s: %s: %s returned a non-numeric value", plugin_type,
lua_script_path, fxn);
rc = SLURM_ERROR;
}
lua_pop(L, 1);
lua_close(L);
return rc;
}
extern int runtime_p_init(const int idx, runtime_context_t context)
{
int rc = SLURM_SUCCESS;
lua_State *L = NULL;
time_t load_time = (time_t) 0;
xfree(lua_script_path);
lua_script_path = get_extra_conf_path("runtime.lua");
if ((rc = slurm_lua_init()))
return rc;
lua_inited = true;
/*
* Load the script once here so that a missing or broken runtime.lua is
* reported when the plugin is loaded rather than mid-step. The state is
* closed again: each call builds its own.
*/
if (!(rc = slurm_lua_loadscript(&L, plugin_type, lua_script_path,
req_fxns, &load_time, NULL, NULL)))
lua_close(L);
return rc;
}
extern int runtime_p_fini(void)
{
xfree(lua_script_path);
/* slurm_lua_fini() may resolve to the host process's own copy */
if (lua_inited) {
slurm_lua_fini();
lua_inited = false;
}
return SLURM_SUCCESS;
}
extern int runtime_p_setup(slurmd_conf_t *conf, stepd_step_rec_t *step,
slurm_addr_t *cli, slurm_msg_t *msg)
{
int rc = EINVAL, script_rc = EINVAL;
if (!(rc = _call_lua("slurm_runtime_setup", step, NULL, &script_rc)))
rc = script_rc;
return rc;
}
extern void runtime_p_cleanup(slurmd_conf_t *conf, stepd_step_rec_t *step)
{
int rc = EINVAL, script_rc = EINVAL;
if (!(rc = _call_lua("slurm_runtime_cleanup", step, NULL, &script_rc)))
rc = script_rc;
/* The interface has no way to report this to the caller. */
if (rc)
error("%s: %s: slurm_runtime_cleanup() failed: %s", plugin_type,
lua_script_path, slurm_strerror(rc));
}
extern void runtime_p_task_init(slurmd_conf_t *conf, stepd_step_rec_t *step,
stepd_step_task_info_t *task)
{
int rc = EINVAL, script_rc = EINVAL;
/*
* This runs in the task child after fork() and after privileges have
* been dropped, so runtime.lua is read here as the job user and must
* be readable by them.
*/
if (!(rc = _call_lua("slurm_runtime_task_init", step, task,
&script_rc)))
rc = script_rc;
/* The interface has no way to report this to the caller. */
if (rc)
error("%s: %s: slurm_runtime_task_init() failed: %s",
plugin_type, lua_script_path, slurm_strerror(rc));
}
extern int runtime_p_run(slurmd_conf_t *conf, stepd_step_rec_t *step,
stepd_step_task_info_t *task)
{
int rc = EINVAL, script_rc = EINVAL;
/* Runs in the task child as the job user, see runtime_p_task_init(). */
if ((rc = _call_lua("slurm_runtime_run", step, task, &script_rc))) {
/* Broken script: fail rather than exec the task. */
return SLURM_ERROR;
}
/*
* A script that execs the task never returns here, so a successful
* return means it declined to run it and the caller has to.
* runtime_g_run() asserts that this never returns SLURM_SUCCESS, and
* treats anything else as the errno of a failed exec.
*/
if (script_rc == SLURM_SUCCESS)
return ESLURM_NOT_SUPPORTED;
return script_rc;
}
extern int runtime_p_cli_apply_launch_params(const uint32_t het_job_inx,
runtime_context_t context,
slurm_step_launch_params_t
*launch_params)
{
int rc = EINVAL, script_rc = SLURM_SUCCESS;
/* launch_params does not carry the step id */
if (!(rc = _call_lua_hook("slurm_runtime_apply_launch_params", NULL,
&script_rc)))
rc = script_rc;
return rc;
}
extern void runtime_p_cli_reset_args(const uint32_t het_job_inx)
{
/* The runtime registers no arguments, so it holds none. */
}
extern int runtime_p_cli_apply_step_req(const uint32_t het_job_inx,
runtime_context_t context,
step_create_req_msg_t *step_req)
{
int rc = EINVAL, script_rc = SLURM_SUCCESS;
slurm_selected_step_t id = SLURM_SELECTED_STEP_INITIALIZER;
char *id_str = NULL;
id.step_id = step_req->step_id;
/* A step not created yet has no step id for its component to join */
if (id.step_id.step_id == NO_VAL)
id.step_id.step_het_comp = NO_VAL;
/* Leaves id_str NULL when not even the job is known */
(void) fmt_job_id_string(&id, &id_str);
if (!(rc = _call_lua_hook("slurm_runtime_apply_step_req", id_str,
&script_rc)))
rc = script_rc;
xfree(id_str);
return rc;
}
extern int runtime_p_cli_apply_job_desc(const uint32_t het_job_inx,
runtime_context_t context,
job_desc_msg_t *job_desc)
{
int rc = EINVAL, script_rc = SLURM_SUCCESS;
/* The job has no id until it is submitted */
if (!(rc = _call_lua_hook("slurm_runtime_apply_job_desc", NULL,
&script_rc)))
rc = script_rc;
return rc;
}
extern int runtime_p_cli_apply_env(const uint32_t het_job_inx,
const uint32_t het_job_offset,
runtime_context_t context, bool child,
char ***envp)
{
int rc = EINVAL, script_rc = SLURM_SUCCESS;
if (!(rc = _call_lua_hook("slurm_runtime_apply_env", NULL, &script_rc)))
rc = script_rc;
return rc;
}
extern int runtime_p_cli_apply_interactive_step(const uint32_t het_job_inx,
runtime_context_t context,
char **command)
{
int rc = EINVAL, script_rc = SLURM_SUCCESS;
if (!(rc = _call_lua_hook("slurm_runtime_apply_interactive_step", NULL,
&script_rc)))
rc = script_rc;
return rc;
}