| /*****************************************************************************\ |
| * 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; |
| } |