/*****************************************************************************\
 *  qos.c - Slurm REST API accounting QOS 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 "slurm/slurmdb.h"

#include "src/common/list.h"
#include "src/common/log.h"
#include "src/common/slurmdbd_defs.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"

#include "src/slurmrestd/openapi.h"
#include "src/slurmrestd/operations.h"
#include "api.h"

/*
 * Modify request for QOS will ignore an empty List. This allows slurmdbd to
 * know we want this field to be empty.
 */
#define EMPTY_QOS_ID_ENTRY "\'\'"

/* If the QOS already exists, update it. If not, create it */
static int _foreach_update_qos(void *x, void *arg)
{
	ctxt_t *ctxt = arg;
	int rc;
	slurmdb_qos_rec_t *qos = x, *found_qos = NULL;
	slurmdb_qos_cond_t cond = { 0 };

	/* Search for a QOS with the same id and/or name, if set */
	if (qos->id || qos->name) {
		list_t *qos_list = NULL;

		if (qos->id) {
			/* Need to free string copy of id with xfree_ptr */
			cond.id_list = list_create(xfree_ptr);
			list_append(cond.id_list,
				    xstrdup_printf("%u", qos->id));
		}
		if (qos->name) {
			/* Temporarily alias/borrow qos->name into cond */
			cond.name_list = list_create(NULL);
			list_append(cond.name_list, qos->name);
		}

		/* See if QOS already exists */
		rc = db_query_list_xempty(ctxt, &qos_list, slurmdb_qos_get,
					  &cond);

		if (!rc && qos_list && (list_count(qos_list) == 1))
			found_qos = list_pop(qos_list);

		FREE_NULL_LIST(qos_list);
	}

	if (!found_qos && qos->id) {
		/* No QOS exists for qos->id. Can't update */
		rc = resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
				"QOS was not found for the requested ID");
	} else if (!found_qos && !qos->name) {
		/* Can't create a QOS without a name */
		rc = resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
				"Cannot create a QOS without a name");
	} else if (!found_qos) {
		list_t *qos_add_list = list_create(NULL);

		/* The QOS was not found, so create a new QOS */
		debug("%s: adding qos request: name=%s description=%s",
		      __func__, qos->name, qos->description);

		list_append(qos_add_list, qos);
		rc = db_query_rc(ctxt, qos_add_list, slurmdb_qos_add);

		FREE_NULL_LIST(qos_add_list);
	} else {
		/* Exactly one QOS was found: let's update it */
		debug("%s: modifying qos request: id=%u name=%s",
		      __func__, found_qos->id, found_qos->name);

		xassert(!qos->name || !xstrcmp(found_qos->name, qos->name));
		xassert(!qos->id || (found_qos->id == qos->id));

		if (!qos->id)
			qos->id = found_qos->id;

		if (qos->preempt_list && list_is_empty(qos->preempt_list) &&
		    found_qos->preempt_list &&
		    !list_is_empty(found_qos->preempt_list)) {
			/*
			 * If the new QOS list is empty but the QOS had a
			 * preempt list before, then we need to set this special
			 * entry to notify slurmdbd that this is explicitly
			 * empty and not a no change request.
			 *
			 * If we always set this value, then slurmdbd will
			 * return ESLURM_QOS_PREEMPTION_LOOP.
			 */
			list_append(qos->preempt_list, EMPTY_QOS_ID_ENTRY);
		}

		rc = db_modify_rc(ctxt, &cond, qos, slurmdb_qos_modify);
	}

	slurmdb_destroy_qos_rec(found_qos);
	FREE_NULL_LIST(cond.id_list);
	FREE_NULL_LIST(cond.name_list);

	return (rc != SLURM_SUCCESS) ? DATA_FOR_EACH_FAIL : DATA_FOR_EACH_CONT;
}

extern int update_qos(ctxt_t *ctxt, bool commit, list_t *qos_list)
{
	if (!(list_for_each_ro(qos_list, _foreach_update_qos, ctxt) < 0) &&
	    !ctxt->rc && commit)
		db_query_commit(ctxt);

	return ctxt->rc;
}

static int _op_handler_qos(ctxt_t *ctxt, slurmdb_qos_cond_t *qos_cond)
{
	list_t *qos_list = NULL;

	if (ctxt->method == HTTP_REQUEST_GET) {
		db_query_list(ctxt, &qos_list, slurmdb_qos_get, qos_cond);
		DUMP_OPENAPI_RESP_SINGLE(OPENAPI_SLURMDBD_QOS_RESP, qos_list,
					 ctxt);
	} else if (ctxt->method == HTTP_REQUEST_DELETE) {
		if (!qos_cond->name_list ||
		    list_is_empty(qos_cond->name_list)) {
			resp_error(ctxt, ESLURM_DATA_AMBIGUOUS_MODIFY, __func__,
				   "QOS name must be provided for DELETE");
			goto cleanup;
		}

		(void) db_query_list(ctxt, &qos_list, slurmdb_qos_remove,
				     qos_cond);

		if (qos_list && !ctxt->rc)
			db_query_commit(ctxt);

		DUMP_OPENAPI_RESP_SINGLE(OPENAPI_SLURMDBD_QOS_REMOVED_RESP,
					 qos_list, ctxt);
	} else if (ctxt->method == HTTP_REQUEST_POST) {
		openapi_resp_single_t post = { 0 };
		openapi_resp_single_t *post_ptr = &post;

		if (!DATA_PARSE(ctxt->parser, OPENAPI_SLURMDBD_QOS_RESP, post,
				ctxt->query, ctxt->parent_path) &&
		    post.response) {
			qos_list = post.response;
			update_qos(ctxt, true, qos_list);
		}
		FREE_OPENAPI_RESP_COMMON_CONTENTS(post_ptr);
	} else {
		resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
			   "Unsupported HTTP method requested: %s",
			   get_http_method_string(ctxt->method));
	}

cleanup:
	FREE_NULL_LIST(qos_list);
	return SLURM_SUCCESS;
}

extern int op_handler_single_qos(ctxt_t *ctxt)
{
	int rc;
	openapi_qos_param_t params = {0};
	openapi_qos_query_t query = {0};
	slurmdb_qos_cond_t *qos_cond = NULL;

	if ((rc = DATA_PARSE(ctxt->parser, OPENAPI_SLURMDBD_QOS_QUERY, query,
			     ctxt->query, ctxt->parent_path)))
		return rc;
	if ((rc = DATA_PARSE(ctxt->parser, OPENAPI_SLURMDBD_QOS_PARAM, params,
			     ctxt->parameters, ctxt->parent_path)))
		return rc;

	qos_cond = xmalloc(sizeof(*qos_cond));
	qos_cond->name_list = list_create(xfree_ptr);
	list_append(qos_cond->name_list, params.name);
	if (query.with_deleted)
		qos_cond->flags |= QOS_COND_FLAG_WITH_DELETED;

	rc = _op_handler_qos(ctxt, qos_cond);

	slurmdb_destroy_qos_cond(qos_cond);

	return rc;
}

extern int op_handler_multi_qos(ctxt_t *ctxt)
{
	int rc;
	slurmdb_qos_cond_t *qos_cond = NULL;

	if (((ctxt->method == HTTP_REQUEST_GET) ||
	     (ctxt->method == HTTP_REQUEST_DELETE)) &&
	    (rc = DATA_PARSE(ctxt->parser, QOS_CONDITION_PTR, qos_cond,
			     ctxt->parameters, ctxt->parent_path)))
		return rc;

	rc = _op_handler_qos(ctxt, qos_cond);

	slurmdb_destroy_qos_cond(qos_cond);

	return rc;
}
