blob: 58ca6dbff67e0165ec1e915ec703fee12e7f527c [file] [log] [blame]
/*****************************************************************************\
* state.c - scrun state handlers
*****************************************************************************
* Copyright (C) SchedMD LLC.
*
* 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 <sys/stat.h>
#include "src/common/env.h"
#include "src/common/oci_config.h"
#include "src/common/read_config.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "src/interfaces/auth.h"
#include "src/scrun/scrun.h"
#define STATE_MAGIC 0x0a0a0b0b
state_t state = {0};
extern void check_state(void)
{
#ifndef NDEBUG
struct stat statbuf;
xassert(oci_conf);
/* general sanity checks that apply in all cases */
xassert(state.magic == STATE_MAGIC);
xassert(state.status != CONTAINER_ST_INVALID);
xassert(state.status >= CONTAINER_ST_UNKNOWN);
xassert(state.status < CONTAINER_ST_MAX);
xassert((state.ptm == -1) || (state.ptm >= 0));
xassert((state.pts == -1) || (state.pts >= 0));
xassert(state.requested_signal >= 0);
xassert(!state.job_env || (xsize(state.job_env) >= sizeof(NULL)));
xassert(!state.spank_job_env ||
(xsize(state.spank_job_env) >= sizeof(NULL)));
xassert(!state.id || xsize(state.id) >= 1);
xassert((state.pid_file_fd == -1) ||
(state.pid_file_fd > STDERR_FILENO));
/* make sure all strings are xmalloc()ed */
xassert(!state.bundle || xsize(state.bundle) >= 1);
xassert(!state.console_socket || xsize(state.console_socket) >= 1);
xassert(!state.pid_file || xsize(state.pid_file) >= 1);
xassert(!state.anchor_socket || xsize(state.anchor_socket) >= 1);
xassert(!state.spool_dir || xsize(state.spool_dir) >= 1);
xassert(!state.config_file || xsize(state.config_file) >= 1);
xassert(!state.root_dir || xsize(state.root_dir) >= 1);
xassert(!state.root_path || xsize(state.root_path) >= 1);
xassert(!state.config || data_get_type(state.config));
xassert(list_count(state.annotations) >= 0);
if (state.ptm >= 0) {
/* this is the anchor pid */
xassert(state.ptm >= 0);
xassert(state.id && state.id[0]);
xassert(state.oci_version && state.oci_version[0]);
xassert(state.bundle && state.bundle[0]);
xassert(!stat(state.root_dir, &statbuf));
switch (state.status) {
case CONTAINER_ST_MAX :
case CONTAINER_ST_INVALID :
fatal("%s: status should never be invalid", __func__);
case CONTAINER_ST_UNKNOWN :
/* not loaded yet */
break;
case CONTAINER_ST_CREATING :
xassert(!state.srun_exited);
xassert(state.root_dir);
xassert(state.anchor_socket && state.anchor_socket[0]);
break;
case CONTAINER_ST_CREATED :
xassert(state.jobid > 0);
xassert(!stat(state.root_path, &statbuf));
xassert(!stat(state.anchor_socket, &statbuf));
xassert(!stat(state.config_file, &statbuf));
xassert(state.anchor_socket && state.anchor_socket[0]);
break;
case CONTAINER_ST_STARTING :
xassert(state.user_id != SLURM_AUTH_NOBODY);
xassert(!state.spool_dir ||
!stat(state.spool_dir, &statbuf));
xassert(!stat(state.anchor_socket, &statbuf));
xassert(!stat(state.config_file, &statbuf));
xassert(state.anchor_socket && state.anchor_socket[0]);
/* fall through */
case CONTAINER_ST_RUNNING :
xassert(state.user_id != SLURM_AUTH_NOBODY);
xassert(state.jobid > 0);
xassert(!state.srun_rc);
xassert(!state.srun_exited);
xassert(!state.spool_dir ||
!stat(state.spool_dir, &statbuf));
xassert(!stat(state.anchor_socket, &statbuf));
xassert(state.pid > 1);
xassert(state.anchor_socket && state.anchor_socket[0]);
break;
case CONTAINER_ST_STOPPING :
xassert(!stat(state.anchor_socket, &statbuf));
break;
case CONTAINER_ST_STOPPED :
xassert(state.job_completed);
break;
}
}
#endif /* !NDEBUG */
}
extern void init_state(void)
{
xassert(state.magic == 0);
state.magic = STATE_MAGIC;
slurm_rwlock_init(&state.lock);
state.oci_version = xstrdup(OCI_VERSION);
state.annotations = list_create(destroy_config_key_pair);
state.ptm = -1;
state.pts = -1;
/*
* Use the running uid and gid until we hear back from slurmctld with
* the resolved uid/gid
*/
state.user_id = getuid();
state.group_id = getgid();
state.status = CONTAINER_ST_UNKNOWN;
state.pid_file_fd = -1;
}
extern void destroy_state(void)
{
check_state();
state.magic = ~STATE_MAGIC;
slurm_rwlock_destroy(&state.lock);
state.status = CONTAINER_ST_INVALID;
if (state.pid_file_fd != -1)
(void) close(state.pid_file_fd);
xfree(state.oci_version);
xfree(state.id);
xfree(state.bundle);
xfree(state.orig_bundle);
FREE_NULL_LIST(state.annotations);
xfree(state.console_socket);
xfree(state.pid_file);
xfree(state.anchor_socket);
xfree(state.spool_dir);
env_array_free(state.job_env);
env_array_free(state.spank_job_env);
xfree(state.config_file);
xfree(state.root_dir);
xfree(state.root_path);
xfree(state.orig_root_path);
FREE_NULL_DATA(state.config);
FREE_NULL_LIST(state.start_requests);
FREE_NULL_LIST(state.delete_requests);
xassert(!state.startup_con); /* conmgr owns this */
}
static int _get_job_step_state(slurm_job_info_t *job)
{
/* job is running so we must see if there is a step */
int rc;
job_step_info_response_msg_t *resp = NULL;
rc = slurm_get_job_steps(0, job->job_id, 0, &resp, 0);
if (rc) {
/* query failed...job may have just died */
change_status(CONTAINER_ST_STOPPED);
} else if (!resp->job_step_count) {
/* job is running but no steps yet */
change_status(CONTAINER_ST_CREATED);
} else {
/* job and step exist */
change_status(CONTAINER_ST_RUNNING);
}
slurm_free_job_step_info_response_msg(resp);
return rc;
}
/* based partially on slurm_load_jobs() */
static int _get_job_state()
{
/*
* For any reason the anchor failed to respond with a state.
* Infer the state of the container from the job.
*/
int rc;
job_info_msg_t *jobs = NULL;
slurm_job_info_t *job;
slurm_step_id_t *step;
list_t *steps = list_create((ListDelF) slurm_free_step_id);
debug2("%s: attempting to query slurmctld for state of %s",
__func__, state.id);
rc = slurm_find_step_ids_by_container_id(SHOW_LOCAL, SLURM_AUTH_NOBODY,
state.id, steps);
if (rc || !list_count(steps)) {
/* container ID is not known */
debug2("%s: query slurmctld for state of %s failed",
__func__, state.id);
change_status(CONTAINER_ST_STOPPED);
goto cleanup;
}
if (list_count(steps) > 1)
info("WARNING: more than one job has same container id:%s. State information may be invalid.",
state.id);
/* there should only ever 1 one job with the same container id */
//TODO: may want to check every job that matches?
step = list_peek(steps);
debug2("%s: query slurmctld for %pS state of %s",
__func__, &step, state.id);
rc = slurm_load_job(&jobs, step->job_id, 0);
if (rc || !jobs || (jobs->record_count <= 0)) {
/* no job with name found */
debug2("%s: query slurmctld for %pS state of %s failed",
__func__, &step, state.id);
change_status(CONTAINER_ST_STOPPED);
goto cleanup;
}
job = jobs->job_array;
if (!job->container) {
/* job with same name but no container */
debug2("%s: query slurmctld for %pS did not have correct container %s",
__func__, &step, state.id);
rc = ESLURM_INVALID_CONTAINER_ID;
goto cleanup;
}
/* sanity check the search is working correctly */
xassert(!xstrcmp(job->name, state.id));
/* note the job id in case we want to kill the job */
state.jobid = job->job_id;
switch (job->job_state)
{
case JOB_PENDING:
change_status(CONTAINER_ST_CREATING);
break;
case JOB_RUNNING:
case JOB_SUSPENDED:
/* need to see status of step now */
rc = _get_job_step_state(job);
break;
case JOB_COMPLETE:
case JOB_CANCELLED:
case JOB_FAILED:
case JOB_TIMEOUT:
case JOB_NODE_FAIL:
case JOB_PREEMPTED:
case JOB_BOOT_FAIL:
case JOB_DEADLINE:
change_status(CONTAINER_ST_STOPPED);
break;
default:
xassert(false);
}
debug2("%s: query slurmctld for %pS for %s found JobId=%u: %s -> %s",
__func__, &step, state.id, job->job_id,
job_state_string(job->job_state),
slurm_container_status_to_str(state.status));
cleanup:
if (jobs)
slurm_free_job_info_msg(jobs);
FREE_NULL_LIST(steps);
return rc;
}
extern int get_anchor_state(void)
{
slurm_msg_t req, *resp = NULL;
int rc;
check_state();
slurm_msg_t_init(&req);
slurm_msg_set_r_uid(&req, SLURM_AUTH_UID_ANY);
req.msg_type = REQUEST_CONTAINER_STATE;
debug2("%s: attempting to query state via %s",
__func__, state.anchor_socket);
rc = send_rpc(&req, &resp, state.id, NULL);
slurm_free_msg_members(&req);
if (rc) {
slurm_free_msg(resp);
debug("%s: send_rpc() failed: %s",
__func__, slurm_strerror(rc));
/* fail back to asking slurmctld about the job */
debug2("%s: failed to query state via %s",
__func__, state.anchor_socket);
return _get_job_state();
}
/*
* TODO: future fail state could be to try to talk to slurmstepd in case
* this request is coming from different node than the anchor
*/
if (resp && resp->data &&
(resp->msg_type == RESPONSE_CONTAINER_STATE)) {
/* take ownership of the state contents */
container_state_msg_t *s = resp->data;
xassert(!xstrcmp(s->id, state.id));
xassert(s->status != CONTAINER_ST_INVALID);
debug2("%s: state via %s: %s",
__func__, state.anchor_socket,
slurm_container_status_to_str(s->status));
SWAP(s->oci_version, state.oci_version);
change_status(s->status);
state.pid = s->pid;
SWAP(s->bundle, state.bundle);
SWAP(s->annotations, state.annotations);
} else {
debug2("%s: failed to query state via %s",
__func__, state.anchor_socket);
rc = _get_job_state();
}
check_state();
slurm_free_msg(resp);
return rc;
}
extern void change_status_funcname(container_state_msg_status_t status,
bool force, const char *src, bool locked)
{
#ifndef NDEBUG
static container_state_msg_status_t last_status = CONTAINER_ST_UNKNOWN;
if (locked) {
xassert(state.locked);
xassert(state.needs_lock);
} else {
xassert(!state.locked);
xassert(!state.needs_lock);
}
/* detect if anything else changed value */
xassert(force || (last_status == state.status));
last_status = status;
debug("%s: changing status from %s to %s",
src, slurm_container_status_to_str(state.status),
slurm_container_status_to_str(status));
xassert(status != CONTAINER_ST_INVALID);
xassert(status >= CONTAINER_ST_CREATING);
xassert(status < CONTAINER_ST_MAX);
/* status can never go backwards */
xassert(force || (status >= state.status));
#endif
state.status = status;
}