/*****************************************************************************\
 *  jobs.c - Slurm REST API jobs http operations 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 <signal.h>

#include "src/common/env.h"
#include "src/common/list.h"
#include "src/common/log.h"
#include "src/common/parse_time.h"
#include "src/common/read_config.h"
#include "src/common/slurm_protocol_defs.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"

#include "src/interfaces/serializer.h"

#include "src/slurmrestd/operations.h"

#include "api.h"

#define FOREACH_ALLOC_JOB_ARGS_MAGIC 0x1f133335
typedef struct {
	int magic; /* FOREACH_ALLOC_JOB_ARGS_MAGIC */
	ctxt_t *ctxt;
	openapi_job_alloc_response_t *oas_resp;
	int component; /* index or NO_VAL */
} foreach_alloc_job_args_t;

/* list from job_allocate() */
static const slurm_err_t nonfatal_errors[] = {
	ESLURM_NODES_BUSY,
	ESLURM_RESERVATION_BUSY,
	ESLURM_JOB_HELD,
	ESLURM_NODE_NOT_AVAIL,
	ESLURM_QOS_THRES,
	ESLURM_ACCOUNTING_POLICY,
	ESLURM_RESERVATION_NOT_USABLE,
	ESLURM_REQUESTED_PART_CONFIG_UNAVAILABLE,
	ESLURM_BURST_BUFFER_WAIT,
	ESLURM_PARTITION_DOWN,
	ESLURM_LICENSES_UNAVAILABLE,
	ESLURM_PORTS_BUSY,
};

static int _signal_jobs(openapi_ctxt_t *ctxt)
{
	int rc;
	kill_jobs_msg_t *req = NULL;
	kill_jobs_resp_msg_t *resp = NULL;

	if (!(rc = DATA_PARSE(ctxt->parser, KILL_JOBS_MSG_PTR, req, ctxt->query,
			      ctxt->parent_path))) {
		if (req->user_name && (req->user_id == SLURM_AUTH_NOBODY) &&
		    (rc = uid_from_string(req->user_name, &req->user_id)))
			resp_error(ctxt, rc, "uid_from_string()",
				   "Unable to resolve %s to numeric user id",
				   req->user_name);

		if (!rc && (rc = slurm_kill_jobs(req, &resp)))
			resp_error(ctxt, rc, "slurm_kill_jobs()",
				   "Signal request failed");
	}

	DUMP_OPENAPI_RESP_SINGLE(OPENAPI_KILL_JOBS_RESP, resp, ctxt);

	slurm_free_kill_jobs_msg(req);
	slurm_free_kill_jobs_response_msg(resp);

	return rc;
}

extern int op_handler_jobs(openapi_ctxt_t *ctxt)
{
	openapi_job_info_query_t query = {0};
	job_info_msg_t *job_info_ptr = NULL;
	openapi_resp_job_info_msg_t resp = {0};
	int rc;

	if (ctxt->method == HTTP_REQUEST_DELETE)
		return _signal_jobs(ctxt);

	if (ctxt->method != HTTP_REQUEST_GET) {
		return resp_error(ctxt, (rc = ESLURM_REST_INVALID_QUERY),
				  __func__,
				  "Unsupported HTTP method requested: %s",
				  get_http_method_string(ctxt->method));
	}

	if (DATA_PARSE(ctxt->parser, OPENAPI_JOB_INFO_QUERY, query, ctxt->query,
		       ctxt->parent_path)) {
		return resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
				  "Rejecting request. Failure parsing query.");
	}

	if (!query.show_flags)
		query.show_flags = SHOW_ALL | SHOW_DETAIL;

	rc = slurm_load_jobs(query.update_time, &job_info_ptr,
			     query.show_flags);

	if (rc == SLURM_NO_CHANGE_IN_DATA) {
		char ts[32] = {0};
		slurm_make_time_str(&query.update_time, ts, sizeof(ts));
		rc = SLURM_SUCCESS;
		resp_warn(ctxt, __func__,
			  "No job changes since update_time[%ld]=%s",
			  query.update_time, ts);
	} else if (rc) {
		if ((rc == SLURM_ERROR) && errno)
			rc = errno;
		resp_error(ctxt, rc, "slurm_load_jobs()",
			   "Unable to query jobs");
	} else if (job_info_ptr) {
		resp.last_backfill = job_info_ptr->last_backfill;
		resp.last_update = job_info_ptr->last_update;
		resp.jobs = job_info_ptr;
	}

	DATA_DUMP(ctxt->parser, OPENAPI_JOB_INFO_RESP, resp, ctxt->resp);

	slurm_free_job_info_msg(job_info_ptr);
	return rc;
}

static void _handle_job_get(ctxt_t *ctxt, slurm_selected_step_t *job_id)
{
	openapi_job_info_query_t query = {0};
	int rc = SLURM_SUCCESS;
	job_info_msg_t *job_info_ptr = NULL;
	slurm_step_id_t step_id = SLURM_STEP_ID_INITIALIZER;
	openapi_resp_job_info_msg_t resp = {0};

	if (DATA_PARSE(ctxt->parser, OPENAPI_JOB_INFO_QUERY, query, ctxt->query,
		       ctxt->parent_path)) {
		resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
			   "Rejecting request. Failure parsing query.");
		return;
	}

	step_id = job_id->step_id;

	if (job_id->het_job_offset != NO_VAL)
		step_id.job_id += job_id->het_job_offset;

	if (job_id->array_task_id != NO_VAL)
		resp_warn(ctxt, __func__, "Job array Ids are not currently supported for job searches. Showing all jobs in array instead.");
	if (job_id->step_id.step_id != NO_VAL)
		resp_warn(ctxt, __func__,
			  "Job steps are not supported for job searches. Showing whole job instead.");

	if (!query.show_flags)
		query.show_flags = SHOW_ALL | SHOW_DETAIL;

	if ((rc = slurm_load_job(&job_info_ptr, step_id, query.show_flags))) {
		char *id = NULL;

		fmt_job_id_string(job_id, &id);
		resp_error(ctxt, rc, __func__, "Unable to query JobId=%s",
			   id);

		xfree(id);
	}

	if (job_info_ptr) {
		resp.last_backfill = job_info_ptr->last_backfill;
		resp.last_update = job_info_ptr->last_update;
		resp.jobs = job_info_ptr;
	}

	DATA_DUMP(ctxt->parser, OPENAPI_JOB_INFO_RESP, resp, ctxt->resp);

	slurm_free_job_info_msg(job_info_ptr);
}

static int _parse_job_delete(ctxt_t *ctxt, slurm_selected_step_t *job_id,
			     kill_jobs_msg_t *req)
{
	openapi_job_info_delete_query_t query = {0};
	int rc;

	if ((rc = DATA_PARSE(ctxt->parser, OPENAPI_JOB_INFO_DELETE_QUERY, query,
			     ctxt->query, ctxt->parent_path)))
		return rc;

	if (!(req->flags = query.flags))
		req->flags = KILL_FULL_JOB;

	if (!(req->signal = query.signal))
		req->signal = SIGKILL;

	req->jobs_array = xcalloc(2, sizeof(*req->jobs_array));
	req->jobs_cnt = 1;

	return fmt_job_id_string(job_id, &req->jobs_array[0]);
}

static int _signal_job(ctxt_t *ctxt, kill_jobs_msg_t *req,
		       kill_jobs_resp_msg_t **resp_ptr)
{
	int rc;

	if (!(rc = slurm_kill_jobs(req, resp_ptr))) {
		if ((req->flags & KILL_JOBS_VERBOSE) && !(*resp_ptr)->jobs_cnt)
			resp_warn(ctxt, __func__, "Zero jobs sent signal %s",
				  strsignal(req->signal));
		return rc;
	}

	/* Already signaled jobs are considered a success */
	if (rc == ESLURM_ALREADY_DONE) {
		resp_warn(ctxt, __func__, "Job was already sent signal %s",
			  strsignal(req->signal));
		return SLURM_SUCCESS;
	}

	resp_error(ctxt, rc, "slurm_kill_jobs()", "Signal request failed");
	return rc;
}

static void _handle_job_delete(ctxt_t *ctxt, slurm_selected_step_t *job_id)
{
	int rc;
	kill_jobs_resp_msg_t *resp = NULL;
	kill_jobs_msg_t *req = xmalloc(sizeof(*req));

	*req = (kill_jobs_msg_t) KILL_JOB_MSG_INITIALIZER;

	if (!(rc = _parse_job_delete(ctxt, job_id, req)))
		rc = _signal_job(ctxt, req, &resp);

	DUMP_OPENAPI_RESP_SINGLE(OPENAPI_KILL_JOB_RESP, resp, ctxt);

	slurm_free_kill_jobs_msg(req);
	slurm_free_kill_jobs_response_msg(resp);
}

static void _job_post_update(ctxt_t *ctxt, slurm_selected_step_t *job_id)
{
	job_array_resp_msg_t *resp = NULL;
	job_desc_msg_t *job = NULL;
	openapi_job_post_response_t oas_resp = {0};

	if (DATA_PARSE(ctxt->parser, JOB_DESC_MSG_PTR, job, ctxt->query,
		       ctxt->parent_path)) {
		resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
			"Rejecting request. Failure parsing job update request.");
		goto cleanup;
	}

	if (job_id->step_id.job_id != NO_VAL)
		job->step_id.job_id = job_id->step_id.job_id;

	if (job_id->het_job_offset != NO_VAL)
		job->het_job_offset = job_id->het_job_offset;

	if (job_id->array_task_id != NO_VAL) {
		resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
			"Rejecting request. Submit job update against Array Job's JobID instead of array task id.");
		goto cleanup;
	}

	if ((job_id->step_id.step_id != NO_VAL) ||
	    (job_id->step_id.step_het_comp != NO_VAL)) {
		resp_warn(ctxt, __func__,
			  "Job step information ignored. Job update requests apply to whole job and can not be targeted to specific steps.");
	}

	if (slurm_update_job2(job, &resp)) {
		resp_error(ctxt, errno, "slurm_update_job2()",
			   "Job update requested failed");
		goto cleanup;
	} else if (resp) {
		oas_resp.results = resp;

		if (resp->job_array_count > 0) {
			oas_resp.job_id = resp->job_array_id[0];
			oas_resp.step_id = NULL; /* not provided by RPC */
			oas_resp.job_submit_user_msg = resp->err_msg[0];
		}

		for (int i = 0; i < resp->job_array_count; i++) {
			if (resp->error_code[i]) {
				resp_warn(ctxt, "slurm_update_job2()",
					   "Job update resulted in non-zero return-code[%d]: %s",
					   resp->error_code[i],
					   slurm_strerror(resp->error_code[i]));
			}
		}
	}

cleanup:
	DATA_DUMP(ctxt->parser, OPENAPI_JOB_POST_RESPONSE, oas_resp,
		  ctxt->resp);

	slurm_free_job_desc_msg(job);
	slurm_free_job_array_resp(resp);
}

static void _job_submit_rc(ctxt_t *ctxt, submit_response_msg_t *resp,
			   const char *src)
{
	int rc;

	if (!resp || !(rc = resp->error_code))
		return;

	for (int i = 0; i < ARRAY_SIZE(nonfatal_errors); i++) {
		if (rc == nonfatal_errors[i]) {
			resp_warn(ctxt, "slurm_submit_batch_job()",
				"%s", slurm_strerror(rc));
			return;
		}
	}

	resp_error(ctxt, rc, src, NULL);
}

static void _job_post_submit(ctxt_t *ctxt, job_desc_msg_t *job, char *script)
{
	submit_response_msg_t *resp = NULL;

	if (script) {
		xfree(job->script);
		job->script = xstrdup(script);
	}

	if ((!job->script || !job->script[0]) &&
	    !(job->bitflags & EXTERNAL_JOB)) {
		resp_error(ctxt, ESLURM_JOB_SCRIPT_MISSING, "script",
			   "Batch job script empty or missing");
	} else if (slurm_submit_batch_job(job, &resp) || !resp) {
		resp_error(ctxt, errno, "slurm_submit_batch_job()",
			   "Batch job submission failed");
	} else {
		openapi_job_submit_response_t oas_resp = {
			.resp = *resp,
		};

		debug3("%s:[%s] job submitted -> job_id:%d step_id:%d rc:%d message:%s",
		       __func__, ctxt->id, resp->step_id.job_id,
		       resp->step_id.step_id, resp->error_code,
		       resp->job_submit_user_msg);

		if (resp->error_code)
			resp_warn(ctxt, "slurm_submit_batch_job()",
				   "Job submission resulted in non-zero return code: %s",
				   slurm_strerror(resp->error_code));

		DATA_DUMP(ctxt->parser, OPENAPI_JOB_SUBMIT_RESPONSE, oas_resp,
			  ctxt->resp);
	}

	_job_submit_rc(ctxt, resp, "slurm_submit_batch_job()");
	slurm_free_submit_response_response_msg(resp);
}

static void _job_post_het_submit(ctxt_t *ctxt, list_t *jobs, char *script)
{
	submit_response_msg_t *resp = NULL;

	if (!jobs || !list_count(jobs)) {
		resp_error(ctxt, errno, __func__,
			   "Refusing HetJob submission without any components");
		goto cleanup;
	}

	if (list_count(jobs) > MAX_HET_JOB_COMPONENTS) {
		resp_error(ctxt, errno, __func__,
			   "Refusing HetJob submission too many components: %d > %u",
			   list_count(jobs), MAX_HET_JOB_COMPONENTS);
		goto cleanup;
	}

	if (script) {
		job_desc_msg_t *j = list_peek(jobs);

		xfree(j->script);
		j->script = xstrdup(script);
	}

	{
		/*
		 * Verify the first Het Component has a batch script, unless it
		 * is an external job (external jobs must not have a script).
		 */
		job_desc_msg_t *jdesc = list_peek(jobs);

		if ((!jdesc->script || !jdesc->script[0]) &&
		    !(jdesc->bitflags & EXTERNAL_JOB)) {
			resp_error(ctxt, ESLURM_JOB_SCRIPT_MISSING, __func__,
				   "Refusing HetJob submission without batch script or empty batch script for first component");
			goto cleanup;
		}
	}

	if (slurm_submit_batch_het_job(jobs, &resp) || !resp) {
		resp_error(ctxt, errno, "slurm_submit_batch_het_job()",
			   "HetJob submission failed");
	} else {
		openapi_job_submit_response_t oas_resp = {
			.resp = *resp,
		};

		debug3("%s:[%s] HetJob submitted -> job_id:%d step_id:%d rc:%d message:%s",
		       __func__, ctxt->id, resp->step_id.job_id,
		       resp->step_id.step_id, resp->error_code,
		       resp->job_submit_user_msg);

		if (resp->error_code)
			resp_warn(ctxt, "slurm_submit_batch_het_job()",
				   "HetJob submission resulted in non-zero return code: %s",
				   slurm_strerror(resp->error_code));

		DATA_DUMP(ctxt->parser, OPENAPI_JOB_SUBMIT_RESPONSE, oas_resp,
			  ctxt->resp);
	}

	_job_submit_rc(ctxt, resp, "slurm_submit_batch_het_job()");

cleanup:
	slurm_free_submit_response_response_msg(resp);
}

static void _job_post(ctxt_t *ctxt)
{
	openapi_job_submit_request_t req = {0};

	if ((slurm_conf.debug_flags & DEBUG_FLAG_NET_RAW) && ctxt->query) {
		char *buffer = NULL;

		serialize_g_data_to_string(&buffer, NULL, ctxt->query,
					   MIME_TYPE_JSON, SER_FLAGS_COMPACT);

		log_flag(NET_RAW, "%s:[%s] job POST: %s",
		       __func__, ctxt->id, buffer);

		xfree(buffer);
	}

	if (!ctxt->query) {
		resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
			   "unexpected empty query for job");
		goto cleanup;
	}

	if (DATA_PARSE(ctxt->parser, JOB_SUBMIT_REQ, req, ctxt->query,
		       ctxt->parent_path))
		goto cleanup;

	if (!req.jobs && (!req.script || !req.script[0]) &&
	    (!req.job ||
	     (!req.job->script && !(req.job->bitflags & EXTERNAL_JOB)))) {
		resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
			   "Populated \"script\" field is required for job submission");
		goto cleanup;
	}
	if (req.job && req.jobs) {
		resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
			   "Specify only one \"job\" or \"jobs\" fields but never both");
		goto cleanup;
	}
	if (!req.job && !req.jobs) {
		resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
			   "Specifying either \"job\" or \"jobs\" fields are required to submit job");
		goto cleanup;
	}

	if (req.job) {
		_job_post_submit(ctxt, req.job, req.script);
	} else {
		_job_post_het_submit(ctxt, req.jobs, req.script);
	}

cleanup:
	slurm_free_job_desc_msg(req.job);
	FREE_NULL_LIST(req.jobs);
	xfree(req.script);
}

extern int op_handler_job(openapi_ctxt_t *ctxt)
{
	openapi_job_info_param_t params = {{ 0 }};
	slurm_selected_step_t *job_id;

	if (DATA_PARSE(ctxt->parser, OPENAPI_JOB_INFO_PARAM, params,
		       ctxt->parameters, ctxt->parent_path)) {
		return resp_error(
			ctxt, ESLURM_REST_INVALID_QUERY, __func__,
			"Rejecting request. Failure parsing parameters");
	}

	job_id = &params.job_id;

	if (!job_id->step_id.sluid &&
	    ((job_id->step_id.job_id == NO_VAL) ||
	     (job_id->step_id.job_id <= 0) ||
	     (job_id->step_id.job_id >= MAX_JOB_ID))) {
		return resp_error(ctxt, ESLURM_INVALID_JOB_ID, __func__,
				  "Invalid JobID=%u rejected",
				  job_id->step_id.job_id);
	}

	if (ctxt->method == HTTP_REQUEST_GET) {
		_handle_job_get(ctxt, job_id);
	} else if (ctxt->method == HTTP_REQUEST_DELETE) {
		_handle_job_delete(ctxt, job_id);
	} else if (ctxt->method == HTTP_REQUEST_POST) {
		_job_post_update(ctxt, job_id);
	} else {
		return resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
				  "Unsupported HTTP method requested: %s",
				  get_http_method_string(ctxt->method));
	}

	return SLURM_SUCCESS;
}

extern int op_handler_submit_job(openapi_ctxt_t *ctxt)
{
	if (ctxt->method == HTTP_REQUEST_POST) {
		_job_post(ctxt);
	} else {
		resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
			   "Unsupported HTTP method requested: %s",
			   get_http_method_string(ctxt->method));
	}

	return ctxt->rc;
}

static void _job_alloc_rc(ctxt_t *ctxt,
			  resource_allocation_response_msg_t *resp,
			  const char *src)
{
	int rc;

	if (!resp || !(rc = resp->error_code))
		return;

	for (int i = 0; i < ARRAY_SIZE(nonfatal_errors); i++) {
		if (rc == nonfatal_errors[i]) {
			resp_warn(ctxt, "slurm_submit_batch_job()",
				"%s", slurm_strerror(rc));
			return;
		}
	}

	resp_error(ctxt, rc, src, NULL);
}

static int _foreach_alloc_job(void *x, void *arg)
{
	job_desc_msg_t *job = x;
	xassert(!arg);

	/* Force user/group to be determined by auth */
	job->user_id = SLURM_AUTH_NOBODY;
	job->group_id = SLURM_AUTH_NOBODY;

	/* Force disable status updates */
	job->other_port = 0;

	/* force at least 1 node for job */
	if (!job->min_nodes || (job->min_nodes >= NO_VAL))
		job->min_nodes = 1;

	return SLURM_SUCCESS;
}

static int _foreach_alloc_job_resp(void *x, void *arg)
{
	resource_allocation_response_msg_t *resp = x;
	foreach_alloc_job_args_t *args = arg;
	openapi_job_alloc_response_t *oas_resp = args->oas_resp;
	ctxt_t *ctxt = args->ctxt;

	xassert(args->magic == FOREACH_ALLOC_JOB_ARGS_MAGIC);

	xassert(!oas_resp->job_id ||
		(oas_resp->job_id == resp->step_id.job_id) ||
		(oas_resp->job_id == (resp->step_id.job_id - args->component)));
	oas_resp->job_id = resp->step_id.job_id;

	if (!oas_resp->job_submit_user_msg)
		oas_resp->job_submit_user_msg = resp->job_submit_user_msg;

	if (args->component == NO_VAL) {
		debug3("%s:[%s] Job submitted -> JobId=%d rc:%d message:%s",
		       __func__, ctxt->id, resp->step_id.job_id,
		       resp->error_code, resp->job_submit_user_msg);
	} else {
		debug3("%s:[%s] HetJob submitted -> JobId=%d+%d rc:%d message:%s",
		       __func__, ctxt->id, resp->step_id.job_id, args->component,
		       resp->error_code, resp->job_submit_user_msg);
		args->component++;
	}

	_job_alloc_rc(ctxt, resp,
		      "slurm_allocate_resources_blocking()");

	return SLURM_SUCCESS;
}

static void _job_post_allocate(ctxt_t *ctxt, job_desc_msg_t *job)
{
	resource_allocation_response_msg_t *resp = NULL;

	(void) _foreach_alloc_job(job, NULL);

	if (!(resp = slurm_allocate_resources_blocking(job, 0, NULL, -1))) {
		resp_error(ctxt, errno, "slurm_allocate_resources_blocking()",
			   "Job allocation request failed");
	} else {
		openapi_job_alloc_response_t oas_resp = {0};
		foreach_alloc_job_args_t args = {
			.magic = FOREACH_ALLOC_JOB_ARGS_MAGIC,
			.ctxt = ctxt,
			.oas_resp = &oas_resp,
			.component = NO_VAL,
		};

		(void) _foreach_alloc_job_resp(resp, &args);

		DATA_DUMP(ctxt->parser, OPENAPI_JOB_ALLOC_RESP, oas_resp,
			  ctxt->resp);
	}

	slurm_free_resource_allocation_response_msg(resp);
}

static void _job_post_het_allocate(ctxt_t *ctxt, list_t *hetjob)
{
	list_t *resp = NULL;

	if (!hetjob || !list_count(hetjob)) {
		resp_error(ctxt, errno, __func__,
			   "Refusing HetJob submission without any components");
		goto cleanup;
	}

	if (list_count(hetjob) > MAX_HET_JOB_COMPONENTS) {
		resp_error(ctxt, errno, __func__,
			   "Refusing HetJob submission too many components: %d > %u",
			   list_count(hetjob), MAX_HET_JOB_COMPONENTS);
		goto cleanup;
	}

	(void) list_for_each(hetjob, _foreach_alloc_job, NULL);

	if (!(resp = slurm_allocate_het_job_blocking(hetjob, 0, NULL, -1))) {
		resp_error(ctxt, errno, "slurm_allocate_het_job_blocking()",
			   "Job allocation request failed");
	} else {
		openapi_job_alloc_response_t oas_resp = {0};
		foreach_alloc_job_args_t args = {
			.magic = FOREACH_ALLOC_JOB_ARGS_MAGIC,
			.ctxt = ctxt,
			.oas_resp = &oas_resp,
			.component = 0,
		};

		(void) list_for_each(resp, _foreach_alloc_job_resp, &args);

		DATA_DUMP(ctxt->parser, OPENAPI_JOB_ALLOC_RESP, oas_resp,
			  ctxt->resp);
	}

cleanup:
	FREE_NULL_LIST(resp);
}

extern int op_handler_alloc_job(openapi_ctxt_t *ctxt)
{
	int rc = SLURM_SUCCESS;
	openapi_job_alloc_request_t req = {0};

	if (ctxt->method != HTTP_REQUEST_POST)
		return resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
				  "Unsupported HTTP method requested: %s",
				  get_http_method_string(ctxt->method));

	if ((slurm_conf.debug_flags & DEBUG_FLAG_NET_RAW) && ctxt->query) {
		char *buffer = NULL;

		serialize_g_data_to_string(&buffer, NULL, ctxt->query,
					   MIME_TYPE_JSON, SER_FLAGS_COMPACT);

		log_flag(NET_RAW, "%s:[%s] alloc job POST: %s",
		       __func__, ctxt->id, buffer);

		xfree(buffer);
	}

	if (!ctxt->query) {
		return resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
				  "unexpected empty query for job");
	}

	if ((rc = DATA_PARSE(ctxt->parser, JOB_ALLOC_REQ, req, ctxt->query,
		       ctxt->parent_path)))
		goto cleanup;

	if (req.job && req.hetjob) {
		rc = resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
				"Specify only one \"job\" or \"hetjob\" fields but never both");
		goto cleanup;
	}
	if (!req.job && !req.hetjob) {
		rc = resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
				"Specifying either \"job\" or \"hetjob\" fields are required to allocate job");
		goto cleanup;
	}

	if (req.job)
		_job_post_allocate(ctxt, req.job);
	else
		_job_post_het_allocate(ctxt, req.hetjob);

cleanup:
	slurm_free_job_desc_msg(req.job);
	FREE_NULL_LIST(req.hetjob);

	return rc;
}

extern int op_handler_job_states(openapi_ctxt_t *ctxt)
{
	openapi_job_state_query_t query = { 0 };
	openapi_resp_job_state_t resp = { 0 };
	int job_id_count = 0;
	slurm_selected_step_t *job_ids = NULL;
	int rc;

	if (ctxt->method != HTTP_REQUEST_GET) {
		return resp_error(ctxt, (rc = ESLURM_REST_INVALID_QUERY),
				  __func__,
				  "Unsupported HTTP method requested: %s",
				  get_http_method_string(ctxt->method));
	}

	if (DATA_PARSE(ctxt->parser, OPENAPI_JOB_STATE_QUERY, query,
		       ctxt->query, ctxt->parent_path)) {
		return resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
				  "Rejecting request. Failure parsing query");
	}

	if (query.job_id_list && !list_is_empty(query.job_id_list)) {
		slurm_selected_step_t *id = NULL;

		job_ids = xcalloc(list_count(query.job_id_list),
				  sizeof(*job_ids));

		while ((id = list_pop(query.job_id_list))) {
			job_ids[job_id_count] = *id;
			xfree(id);
			job_id_count++;
		}
	}

	if ((rc = slurm_load_job_state(job_id_count, job_ids, &resp.jobs))) {
		resp_error(ctxt, rc, "slurm_load_job_state()",
			   "Unable to query job states");
	}

	rc = DATA_DUMP(ctxt->parser, OPENAPI_JOB_STATE_RESP, resp, ctxt->resp);

	slurm_free_job_state_response_msg(resp.jobs);
	FREE_NULL_LIST(query.job_id_list);
	xfree(job_ids);
	return rc;
}

extern int op_handler_job_requeue(openapi_ctxt_t *ctxt)
{
	openapi_job_requeue_query_t query = { 0 };
	job_array_resp_msg_t *resp = NULL;
	openapi_job_info_param_t params = { { 0 } };
	char *job_str = NULL;
	int rc = EINVAL;

	if (ctxt->method != HTTP_REQUEST_GET) {
		return resp_error(ctxt, (rc = ESLURM_REST_INVALID_QUERY),
				  __func__,
				  "Unsupported HTTP method requested: %s",
				  get_http_method_string(ctxt->method));
	}

	if (DATA_PARSE(ctxt->parser, OPENAPI_JOB_INFO_PARAM, params,
		       ctxt->parameters, ctxt->parent_path)) {
		rc = ESLURM_REST_INVALID_QUERY;
		resp_error(ctxt, rc, __func__,
			   "Rejecting request. Failure parsing parameters");
		goto cleanup;
	}

	if (DATA_PARSE(ctxt->parser, OPENAPI_JOB_REQUEUE_QUERY, query,
		       ctxt->query, ctxt->parent_path)) {
		rc = ESLURM_REST_INVALID_QUERY;
		resp_error(ctxt, rc, __func__,
			   "Rejecting request. Failure parsing query");
		goto cleanup;
	}

	if ((rc = fmt_job_id_string(&params.job_id, &job_str))) {
		resp_error(ctxt, rc, __func__, "Invalid JobID");
		goto cleanup;
	}

	if ((query.flags & JOB_SPECIAL_EXIT) &&
	    !(query.flags & JOB_REQUEUE_HOLD)) {
		rc = ESLURM_REST_BAD_REQUEST;
		resp_error(ctxt, rc, __func__,
			   "SpecialExit requires Hold mode");
		goto cleanup;
	}

	errno = SLURM_SUCCESS;
	if ((rc = slurm_requeue2(job_str, query.flags, &resp))) {
		if ((rc == SLURM_ERROR) && errno)
			rc = errno;

		(void) resp_error(ctxt, rc, __func__, "Requeue JobID=%s failed",
				  job_str);
	}

	DUMP_OPENAPI_RESP_SINGLE(OPENAPI_JOB_REQUEUE_RESP, resp, ctxt);

cleanup:
	FREE_NULL_BITMAP(params.job_id.array_bitmap);
	xfree(job_str);
	slurm_free_job_array_resp(resp);
	return rc;
}

extern int op_handler_jobs_requeue(openapi_ctxt_t *ctxt)
{
	openapi_jobs_requeue_query_t query = { 0 };
	list_t *responses = NULL;
	slurm_selected_step_t *step = NULL;
	int rc = SLURM_SUCCESS;

	if (ctxt->method != HTTP_REQUEST_POST)
		return resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
				  "Unsupported HTTP method requested: %s",
				  get_http_method_string(ctxt->method));

	if (DATA_PARSE(ctxt->parser, OPENAPI_JOBS_REQUEUE_QUERY, query,
		       ctxt->query, ctxt->parent_path)) {
		rc = ESLURM_REST_INVALID_QUERY;
		resp_error(ctxt, rc, __func__,
			   "Rejecting request. Failure parsing query");
		goto cleanup;
	}

	if (!query.jobs || list_is_empty(query.jobs)) {
		rc = ESLURM_INVALID_JOB_ID;
		resp_error(ctxt, rc, __func__, "Query missing JobIds");
		goto cleanup;
	}

	if ((query.flags & JOB_SPECIAL_EXIT) &&
	    !(query.flags & JOB_REQUEUE_HOLD)) {
		rc = ESLURM_REST_BAD_REQUEST;
		resp_error(ctxt, rc, __func__,
			   "SpecialExit requires Hold mode");
		goto cleanup;
	}

	responses = list_create((ListDelF) slurm_free_job_array_resp);

	while ((step = list_pop(query.jobs))) {
		char *str = NULL;
		job_array_resp_msg_t *resp = NULL;

		if ((rc = fmt_job_id_string(step, &str))) {
			(void) resp_error(ctxt, rc, __func__, "Invalid JobID");
			slurm_destroy_selected_step(step);
			break;
		}

		errno = SLURM_SUCCESS;
		if ((rc = slurm_requeue2(str, query.flags, &resp))) {
			if ((rc == SLURM_ERROR) && errno)
				rc = errno;

			(void) resp_error(ctxt, rc, __func__,
					  "Requeue JobId=%s failed", str);
		}

		if (resp)
			list_append(responses, resp);

		slurm_destroy_selected_step(step);
		xfree(str);
	}

	DUMP_OPENAPI_RESP_SINGLE(OPENAPI_JOBS_REQUEUE_RESP, responses, ctxt);
	rc = SLURM_SUCCESS;

cleanup:
	FREE_NULL_LIST(query.jobs);
	FREE_NULL_LIST(responses);
	return rc;
}
