blob: c991aff34968f740d14248acb4a1d510ed0f30b0 [file] [log] [blame]
/*****************************************************************************\
* associations.c - Slurm REST API acct associations 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 "src/common/list.h"
#include "src/common/log.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "src/slurmrestd/operations.h"
#include "api.h"
static void _dump_assoc_cond(ctxt_t *ctxt, slurmdb_assoc_cond_t *cond,
bool only_one)
{
list_t *assoc_list = NULL;
if (!db_query_list(ctxt, &assoc_list, slurmdb_associations_get, cond) &&
(only_one && (list_count(assoc_list) > 1))) {
resp_error(ctxt, ESLURM_DATA_AMBIGUOUS_QUERY, __func__,
"Ambiguous request: More than 1 association would have been dumped.");
list_flush(assoc_list);
}
DUMP_OPENAPI_RESP_SINGLE(OPENAPI_ASSOCS_RESP, assoc_list, ctxt);
FREE_NULL_LIST(assoc_list);
}
static void _delete_assoc(ctxt_t *ctxt, slurmdb_assoc_cond_t *assoc_cond,
bool only_one)
{
int rc = SLURM_SUCCESS;
list_t *removed = NULL;
rc = db_query_list(ctxt, &removed, slurmdb_associations_remove,
assoc_cond);
if (rc) {
resp_error(ctxt, rc, __func__, "remove associations failed");
} else if (only_one && list_count(removed) > 1) {
resp_error(ctxt, ESLURM_DATA_AMBIGUOUS_MODIFY, __func__,
"ambiguous request: More than 1 association would have been deleted.");
} else if (!rc) {
DUMP_OPENAPI_RESP_SINGLE(OPENAPI_ASSOCS_REMOVED_RESP, removed,
ctxt);
db_query_commit(ctxt);
}
FREE_NULL_LIST(removed);
}
/* Turn *dst into a TRES string that will turn submitted *dst to match mod */
static void _diff_tres(char **dst, char *mod)
{
list_itr_t *itr;
list_t *dst_list = NULL;
list_t *mod_list = NULL;
slurmdb_tres_rec_t *tres;
if (!*dst || !*dst[0]) {
/* direct assignment when dst is empty */
xfree(*dst);
*dst = xstrdup(mod);
return;
}
slurmdb_tres_list_from_string(&dst_list, *dst, TRES_STR_FLAG_REPLACE,
NULL);
xfree(*dst);
slurmdb_tres_list_from_string(&mod_list, mod, TRES_STR_FLAG_REPLACE,
NULL);
/* find all removed or tres with updated counts */
itr = list_iterator_create(dst_list);
while ((tres = list_next(itr))) {
slurmdb_tres_rec_t *m = NULL;
if (mod_list)
m = list_find_first(mod_list, slurmdb_find_tres_in_list,
&tres->id);
if (!m) {
/* mark TRES for removal in slurmdbd */
tres->count = -1;
} else {
tres->count = m->count;
}
}
list_iterator_destroy(itr);
/* add any new tres */
if (mod_list) {
itr = list_iterator_create(mod_list);
while ((tres = list_next(itr))) {
slurmdb_tres_rec_t *d = NULL;
if (dst_list)
d = list_find_first(dst_list,
slurmdb_find_tres_in_list,
&tres->id);
if (!d) {
list_append(dst_list,
slurmdb_copy_tres_rec(tres));
} else {
xassert(tres->count == d->count);
}
}
list_iterator_destroy(itr);
}
*dst = slurmdb_make_tres_string(dst_list, TRES_STR_FLAG_SIMPLE);
FREE_NULL_LIST(dst_list);
FREE_NULL_LIST(mod_list);
}
/*
* Create a diff of the current association and the requested destination state.
*
* Feed that diff to slurmdbd to get the dst state.
*/
static slurmdb_assoc_rec_t *_diff_assoc(slurmdb_assoc_rec_t *assoc,
slurmdb_assoc_rec_t *dst)
{
if (dst->accounting_list)
SWAP(assoc->accounting_list, dst->accounting_list);
if (dst->acct)
SWAP(assoc->acct, dst->acct);
/* skip assoc_next */
/* skip assoc_next_id */
/* skip bf_usage */
if (dst->cluster)
SWAP(assoc->cluster, dst->cluster);
assoc->def_qos_id = dst->def_qos_id;
/* skip flags */
assoc->grp_jobs = dst->grp_jobs;
assoc->grp_jobs_accrue = dst->grp_jobs_accrue;
assoc->grp_submit_jobs = dst->grp_submit_jobs;
_diff_tres(&assoc->grp_tres, dst->grp_tres);
/* skip grp_tres_ctld */
_diff_tres(&assoc->grp_tres_mins, dst->grp_tres_mins);
/* skip grp_tres_mins_ctld */
_diff_tres(&assoc->grp_tres_run_mins, dst->grp_tres_run_mins);
/* skip grp_tres_run_mins_ctld */
assoc->grp_wall = dst->grp_wall;
/* skip id */
assoc->is_def = dst->is_def;
/* skip leaf_usage */
assoc->max_jobs = dst->max_jobs;
assoc->max_jobs_accrue = dst->max_jobs_accrue;
assoc->max_submit_jobs = dst->max_submit_jobs;
_diff_tres(&assoc->max_tres_mins_pj, dst->max_tres_mins_pj);
/* skip max_tres_mins_ctld */
_diff_tres(&assoc->max_tres_run_mins, dst->max_tres_run_mins);
/* skip max_tres_run_mins_ctld */
_diff_tres(&assoc->max_tres_pj, dst->max_tres_pj);
/* skip max_tres_ctld */
_diff_tres(&assoc->max_tres_pn, dst->max_tres_pn);
/* skip max_tres_pn_ctld */
assoc->max_wall_pj = dst->max_wall_pj;
assoc->min_prio_thresh = dst->min_prio_thresh;
if (dst->parent_acct && xstrcmp(assoc->parent_acct, dst->parent_acct))
SWAP(assoc->parent_acct, dst->parent_acct);
else
xfree(assoc->parent_acct);
/* skip parent_id */
if (dst->partition)
SWAP(assoc->partition, dst->partition);
assoc->priority = dst->priority;
if (dst->qos_list)
SWAP(assoc->qos_list, dst->qos_list);
assoc->shares_raw = dst->shares_raw;
/* skip uid */
/* skip usage */
if (dst->user)
SWAP(assoc->user, dst->user);
/* skip user_rec */
return assoc;
}
static int _foreach_update_assoc(void *x, void *arg)
{
int rc;
ctxt_t *ctxt = arg;
slurmdb_assoc_rec_t *assoc = x;
slurmdb_assoc_cond_t cond = {
/* all values are symlinks so don't double free */
.acct_list = list_create(NULL),
.cluster_list = list_create(NULL),
.partition_list = list_create(NULL),
.user_list = list_create(NULL),
};
list_t *assoc_list = NULL;
if (assoc->parent_acct && !assoc->parent_acct[0])
xfree(assoc->parent_acct);
/*
* slurmdbd will treat an empty list as a wildcard so we must place
* empty string values to for unset fields
*/
list_append(cond.acct_list, (assoc->acct ? assoc->acct : ""));
list_append(cond.cluster_list, (assoc->cluster ? assoc->cluster : ""));
list_append(cond.partition_list,
(assoc->partition ? assoc->partition : ""));
list_append(cond.user_list, (assoc->user ? assoc->user : ""));
/* first query is an existence check and we don't care about errors */
if ((rc = db_query_list_xempty(ctxt, &assoc_list,
slurmdb_associations_get, &cond)) ||
!assoc_list || list_is_empty(assoc_list)) {
debug("%s: [%s] adding association request: acct=%s cluster=%s partition=%s user=%s existence_check[%d]:%s",
__func__, ctxt->id, assoc->acct, assoc->cluster,
assoc->partition, assoc->user, rc, slurm_strerror(rc));
FREE_NULL_LIST(assoc_list);
/* avoid double free of assoc */
assoc_list = list_create(NULL);
list_append(assoc_list, assoc);
(void) db_query_rc(ctxt, assoc_list, slurmdb_associations_add);
} else if (list_count(assoc_list) > 1) {
rc = resp_error(ctxt, ESLURM_DATA_AMBIGUOUS_MODIFY, __func__,
"ambiguous association modify request");
} else {
slurmdb_assoc_rec_t *diff_assoc;
debug("%s: [%s] modifying association request: acct=%s cluster=%s partition=%s user=%s",
__func__, ctxt->id, assoc->acct, assoc->cluster,
assoc->partition, assoc->user);
/*
* slurmdb requires that the modify request will be a list of
* diffs instead of the final state of the assoc unlike add
*/
diff_assoc = _diff_assoc(list_pop(assoc_list), assoc);
rc = db_modify_rc(ctxt, &cond, diff_assoc,
slurmdb_associations_modify);
slurmdb_destroy_assoc_rec(diff_assoc);
}
FREE_NULL_LIST(assoc_list);
FREE_NULL_LIST(cond.acct_list);
FREE_NULL_LIST(cond.cluster_list);
FREE_NULL_LIST(cond.partition_list);
FREE_NULL_LIST(cond.user_list);
return rc ? SLURM_ERROR : SLURM_SUCCESS;
}
extern int update_associations(ctxt_t *ctxt, bool commit, list_t *assoc_list)
{
if (!(list_for_each(assoc_list, _foreach_update_assoc, ctxt) < 0) &&
!ctxt->rc && commit)
db_query_commit(ctxt);
return ctxt->rc;
}
static void _update_associations(ctxt_t *ctxt)
{
openapi_resp_single_t resp = {0};
openapi_resp_single_t *resp_ptr = &resp;
if (!DATA_PARSE(ctxt->parser, OPENAPI_ASSOCS_RESP, resp, ctxt->query,
ctxt->parent_path)) {
list_t *assoc_list = resp.response;
update_associations(ctxt, true, assoc_list);
FREE_NULL_LIST(assoc_list);
}
FREE_OPENAPI_RESP_COMMON_CONTENTS(resp_ptr);
}
extern int op_handler_association(ctxt_t *ctxt)
{
slurmdb_assoc_cond_t *assoc_cond = NULL;
if (DATA_PARSE(ctxt->parser, ASSOC_CONDITION_PTR, assoc_cond,
ctxt->query, ctxt->parent_path))
goto cleanup;
if (ctxt->method == HTTP_REQUEST_GET)
_dump_assoc_cond(ctxt, assoc_cond, true);
else if (ctxt->method == HTTP_REQUEST_DELETE)
_delete_assoc(ctxt, assoc_cond, true);
else {
resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
"Unsupported HTTP method requested: %s",
get_http_method_string(ctxt->method));
}
cleanup:
slurmdb_destroy_assoc_cond(assoc_cond);
return SLURM_SUCCESS;
}
extern int op_handler_associations(ctxt_t *ctxt)
{
slurmdb_assoc_cond_t *assoc_cond = NULL;
if ((ctxt->method == HTTP_REQUEST_GET) ||
(ctxt->method == HTTP_REQUEST_DELETE)) {
if (DATA_PARSE(ctxt->parser, ASSOC_CONDITION_PTR, assoc_cond,
ctxt->query, ctxt->parent_path))
goto cleanup;
if (!assoc_cond)
assoc_cond = xmalloc(sizeof(*assoc_cond));
if (assoc_cond->usage_start && !assoc_cond->usage_end)
assoc_cond->usage_end = time(NULL);
else if (assoc_cond->usage_start > assoc_cond->usage_end)
SWAP(assoc_cond->usage_start, assoc_cond->usage_end);
}
if (ctxt->method == HTTP_REQUEST_GET)
_dump_assoc_cond(ctxt, assoc_cond, false);
else if (ctxt->method == HTTP_REQUEST_POST)
_update_associations(ctxt);
else if (ctxt->method == HTTP_REQUEST_DELETE)
_delete_assoc(ctxt, assoc_cond, false);
else {
resp_error(ctxt, ESLURM_REST_INVALID_QUERY, __func__,
"Unsupported HTTP method requested: %s",
get_http_method_string(ctxt->method));
}
cleanup:
slurmdb_destroy_assoc_cond(assoc_cond);
return SLURM_SUCCESS;
}