| /*****************************************************************************\ |
| * licenses.c - Functions for handling cluster-wide consumable resources |
| ***************************************************************************** |
| * Copyright (C) 2008-2011 Lawrence Livermore National Security. |
| * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). |
| * Written by Morris Jette <jette@llnl.gov>, et. al. |
| * CODE-OCEC-09-009. All rights reserved. |
| * |
| * 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 <ctype.h> |
| #include <errno.h> |
| #include <pthread.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/stat.h> |
| |
| #include "slurm/slurm_errno.h" |
| |
| #include "src/common/assoc_mgr.h" |
| #include "src/common/list.h" |
| #include "src/common/log.h" |
| #include "src/common/macros.h" |
| #include "src/common/xmalloc.h" |
| #include "src/common/xstring.h" |
| |
| #include "src/interfaces/accounting_storage.h" |
| #include "src/interfaces/data_parser.h" |
| #include "src/interfaces/serializer.h" |
| |
| #include "src/slurmctld/licenses.h" |
| #include "src/slurmctld/reservation.h" |
| #include "src/slurmctld/slurmctld.h" |
| |
| list_t *cluster_license_list = NULL; |
| uint16_t next_lic_id = 0; |
| time_t last_license_update = 0; |
| bool preempt_for_licenses = false; |
| static pthread_mutex_t license_mutex = PTHREAD_MUTEX_INITIALIZER; |
| static void _pack_license(licenses_t *lic, buf_t *buffer, |
| uint16_t protocol_version); |
| |
| typedef struct { |
| licenses_id_t id; |
| slurmctld_resv_t *resv_ptr; |
| } bf_licenses_find_resv_t; |
| |
| typedef struct { |
| job_record_t *job_ptr; |
| list_t *license_list; |
| int rc; |
| bool reboot; |
| time_t when; |
| } license_test_args_t; |
| |
| typedef struct { |
| char *header; |
| job_record_t *job_ptr; |
| } foreach_license_print_t; |
| |
| typedef struct { |
| char *name; |
| char *nodes; |
| } licenses_find_rec_by_nodes_t; |
| |
| typedef struct { |
| licenses_t *license_entry; |
| job_record_t *job_ptr; |
| time_t when; |
| } foreach_get_hres_t; |
| |
| typedef struct { |
| uint32_t *count; |
| char *name; |
| } foreach_get_total_t; |
| |
| typedef struct { |
| job_record_t *job_ptr; |
| licenses_t *license_entry; |
| bitstr_t *node_mask; |
| time_t when; |
| } foreach_hres_filter_t; |
| |
| typedef struct { |
| job_record_t *job_ptr; |
| list_t *license_list; |
| bitstr_t *node_bitmap; |
| time_t when; |
| } hres_filter_args_t; |
| |
| typedef struct { |
| bf_licenses_t *bf_license_list; |
| job_record_t *job_ptr; |
| bitstr_t *node_bitmap; |
| } bf_hres_filter_args_t; |
| |
| typedef struct { |
| bool future; |
| job_record_t *job_ptr; |
| list_t *license_list; |
| bool locked; |
| } license_return_args_t; |
| |
| static int _foreach_license_print(void *x, void *arg) |
| { |
| licenses_t *license_entry = x; |
| foreach_license_print_t *args = arg; |
| |
| if (license_entry->id.hres_id != NO_VAL16) { |
| info("licenses: %s=%s lic_id=%u hres_id=%u mode=%u nodes:%s total=%u used=%u", |
| args->header, license_entry->name, license_entry->id.lic_id, |
| license_entry->id.hres_id, license_entry->mode, |
| license_entry->nodes, license_entry->total, |
| license_entry->used); |
| } else if (!args->job_ptr) { |
| info("licenses: %s=%s lic_id=%u total=%u used=%u", |
| args->header, license_entry->name, license_entry->id.lic_id, |
| license_entry->total, license_entry->used); |
| } else { |
| info("licenses: %s=%s lic_id=%u %pJ available=%u used=%u", |
| args->header, license_entry->name, license_entry->id.lic_id, |
| args->job_ptr, license_entry->total, license_entry->used); |
| } |
| |
| return 0; |
| } |
| |
| /* Print all licenses on a list */ |
| static void _licenses_print(char *header, list_t *licenses, |
| job_record_t *job_ptr) |
| { |
| foreach_license_print_t args = { .header = header, .job_ptr = job_ptr }; |
| if (licenses == NULL) |
| return; |
| if (!(slurm_conf.debug_flags & DEBUG_FLAG_LICENSE)) |
| return; |
| list_for_each(licenses, _foreach_license_print, &args); |
| } |
| |
| /* Free a license_t record (for use by FREE_NULL_LIST) */ |
| extern void license_free_rec(void *x) |
| { |
| licenses_t *license_entry = (licenses_t *) x; |
| |
| if (license_entry) { |
| xfree(license_entry->name); |
| FREE_NULL_BITMAP(license_entry->node_bitmap); |
| xfree(license_entry->nodes); |
| xfree(license_entry); |
| } |
| } |
| |
| /* Find a license_t record by license name (for use by list_find_first) */ |
| static int _license_find_rec(void *x, void *key) |
| { |
| licenses_t *license_entry = (licenses_t *) x; |
| char *name = (char *) key; |
| |
| if ((license_entry->name == NULL) || (name == NULL)) |
| return 0; |
| if (xstrcmp(license_entry->name, name)) |
| return 0; |
| return 1; |
| } |
| |
| /* Find a license_t record by license name (for use by list_find_first) */ |
| static int _license_find_rec_by_nodes(void *x, void *key) |
| { |
| licenses_t *license_entry = (licenses_t *) x; |
| licenses_find_rec_by_nodes_t *target = key; |
| |
| if ((license_entry->name == NULL) || (target->name == NULL)) |
| return 0; |
| if (xstrcmp(license_entry->name, target->name)) |
| return 0; |
| if (xstrcmp(license_entry->nodes, target->nodes)) |
| return 0; |
| return 1; |
| } |
| |
| /* |
| * Find a license_t record by license id (for use by list_find_first) |
| */ |
| static int _license_find_rec_by_id(void *x, void *key) |
| { |
| licenses_t *license_entry = x; |
| licenses_id_t *id = key; |
| |
| xassert(id->lic_id != NO_VAL16); |
| |
| if (license_entry->id.lic_id == id->lic_id) |
| return 1; |
| return 0; |
| } |
| |
| /* |
| * Find a license_t record that does NOT match license id. This is the inverse |
| * of _license_find_rec_by_id |
| */ |
| static int _license_find_rec_by_id_not(void *x, void *key) |
| { |
| return !_license_find_rec_by_id(x, key); |
| } |
| |
| static int _license_find_rec_in_list_by_id(void *x, void *key) |
| { |
| licenses_t *license_entry = x; |
| list_t *licenses = key; |
| if (list_find_first_ro(licenses, _license_find_rec_by_id, |
| &(license_entry->id))) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| static int _license_find_non_hres_rec_in_list_by_id(void *x, void *key) |
| { |
| licenses_t *license_entry = x; |
| list_t *licenses = key; |
| |
| /* Ignore hres licenses */ |
| if (license_entry->id.hres_id != NO_VAL16) |
| return 0; |
| |
| if (list_find_first_ro(licenses, _license_find_rec_by_id, |
| &(license_entry->id))) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* Find a license_t record by license name (for use by list_find_first) */ |
| static int _license_find_remote_rec(void *x, void *key) |
| { |
| licenses_t *license_entry = (licenses_t *) x; |
| |
| if (!license_entry->remote) |
| return 0; |
| return _license_find_rec(x, key); |
| } |
| |
| /* Given a license string, return a list of license_t records */ |
| static list_t *_build_license_list(char *licenses, bool *valid, bool hres) |
| { |
| int i; |
| char *end_num, *tmp_str, *token; |
| char *delim = ",;"; |
| licenses_t *license_entry; |
| list_t *lic_list; |
| |
| *valid = true; |
| if ((licenses == NULL) || (licenses[0] == '\0')) |
| return NULL; |
| |
| if (strchr(licenses, '|')) { |
| if (strchr(licenses, ',') || strchr(licenses, ';') || |
| strchr(licenses, '(')) { |
| /* Both OR and AND requested, invalid */ |
| *valid = false; |
| return NULL; |
| } |
| delim = "|"; |
| } |
| |
| lic_list = list_create(license_free_rec); |
| tmp_str = xstrdup(licenses); |
| token = tmp_str; |
| while (*token && *valid) { |
| int32_t num = 1; |
| char *nodes = NULL; |
| char *name = token; |
| if (strchr(delim, token[0])) { |
| token++; |
| continue; |
| } |
| |
| for (i = 0; token[i]; i++) { |
| if (isspace(token[i])) { |
| *valid = false; |
| break; |
| } |
| |
| if ((token[i] == '(') && hres) { |
| token[i++] = '\0'; |
| nodes = &(token[i]); |
| token = strchr(nodes, ')'); |
| if (!token) { |
| *valid = false; |
| break; |
| } |
| i = 0; |
| token[i++] = '\0'; |
| } |
| |
| if ((token[i] == ':') || |
| (token[i] == '=')) { |
| token[i++] = '\0'; |
| num = (int32_t)strtol(&token[i], &end_num, 10); |
| if ((*end_num != '\0') && |
| !strchr(delim, *end_num)) |
| *valid = false; |
| token = end_num; |
| break; |
| } |
| |
| if (strchr(delim, token[i])) { |
| token[i++] = '\0'; |
| token = &(token[i]); |
| break; |
| } |
| } |
| |
| if (name == token) |
| token = &(token[i]); |
| |
| if (num < 0 || !(*valid)) { |
| *valid = false; |
| break; |
| } |
| if (nodes) { |
| licenses_find_rec_by_nodes_t args = { |
| .name = name, |
| .nodes = nodes, |
| }; |
| license_entry = |
| list_find_first(lic_list, |
| _license_find_rec_by_nodes, |
| &args); |
| } else { |
| license_entry = |
| list_find_first(lic_list, _license_find_rec, |
| name); |
| } |
| |
| if (license_entry) { |
| license_entry->total += num; |
| } else { |
| license_entry = xmalloc(sizeof(licenses_t)); |
| license_entry->id.lic_id = NO_VAL16; |
| license_entry->id.hres_id = NO_VAL16; |
| license_entry->name = xstrdup(name); |
| license_entry->nodes = xstrdup(nodes); |
| license_entry->total = num; |
| if (delim[0] == '|') |
| license_entry->op_or = true; |
| /* Append to preserve the order requested by the user */ |
| list_append(lic_list, license_entry); |
| } |
| } |
| xfree(tmp_str); |
| |
| if (*valid == false) { |
| FREE_NULL_LIST(lic_list); |
| } |
| return lic_list; |
| } |
| |
| /* |
| * Given a list of license_t records, return a license string. |
| * |
| * This can be combined with _build_license_list() to eliminate duplicates |
| * |
| * IN license_list - list of license_t records |
| * |
| * RET string representation of licenses. Must be destroyed by caller. |
| */ |
| extern char *license_list_to_string(list_t *license_list) |
| { |
| char *sep = ""; |
| char *licenses = NULL; |
| list_itr_t *iter; |
| licenses_t *license_entry; |
| |
| if (!license_list) |
| return licenses; |
| |
| iter = list_iterator_create(license_list); |
| while ((license_entry = list_next(iter))) { |
| if (license_entry->nodes) |
| xstrfmtcat(licenses, "%s%s(%s):%u", sep, |
| license_entry->name, license_entry->nodes, |
| license_entry->total); |
| else |
| xstrfmtcat(licenses, "%s%s:%u", sep, |
| license_entry->name, license_entry->total); |
| sep = license_entry->op_or ? "|" : ";"; |
| } |
| list_iterator_destroy(iter); |
| |
| return licenses; |
| } |
| |
| static void _handle_consumed(licenses_t *license_entry, slurmdb_res_rec_t *rec) |
| { |
| uint32_t external = 0; |
| |
| if (rec->flags & SLURMDB_RES_FLAG_ABSOLUTE) { |
| license_entry->total = rec->clus_res_rec->allowed; |
| } else { |
| license_entry->total = ((rec->count * |
| rec->clus_res_rec->allowed) / 100); |
| } |
| |
| if (license_entry->total > rec->count) { |
| debug("allocated more licenses than exist total (%u > %u). this should not happen.", |
| license_entry->total, rec->count); |
| } else |
| external = rec->count - license_entry->total; |
| |
| license_entry->last_consumed = rec->last_consumed; |
| if (license_entry->last_consumed <= (external + license_entry->used)) { |
| /* |
| * "Normal" operation - license consumption is below what the |
| * local cluster, plus possible use from other clusters, |
| * have assigned out. No deficit in this case. |
| */ |
| license_entry->last_deficit = 0; |
| } else { |
| /* |
| * "Deficit" operation. Someone is using licenses that aren't |
| * included in our local tracking, and exceed that available |
| * to other clusters. So... we need to adjust our scheduling |
| * behavior here to avoid over-allocating licenses. |
| */ |
| license_entry->last_deficit = license_entry->last_consumed; |
| license_entry->last_deficit -= external; |
| license_entry->last_deficit -= license_entry->used; |
| } |
| license_entry->last_update = rec->last_update; |
| } |
| |
| /* license_mutex should be locked before calling this. */ |
| static void _add_res_rec_2_lic_list(slurmdb_res_rec_t *rec, bool sync) |
| { |
| licenses_t *license_entry = xmalloc(sizeof(licenses_t)); |
| |
| license_entry->name = xstrdup_printf("%s@%s", rec->name, rec->server); |
| license_entry->remote = sync ? 2 : 1; |
| _handle_consumed(license_entry, rec); |
| |
| license_entry->id.lic_id = next_lic_id++; |
| xassert(license_entry->id.lic_id != NO_VAL16); |
| |
| list_append(cluster_license_list, license_entry); |
| last_license_update = time(NULL); |
| } |
| |
| static int _foreach_license_set_id(void *x, void *key) |
| { |
| licenses_t *license = x; |
| |
| if (license->id.lic_id == NO_VAL16) { |
| license->id.lic_id = next_lic_id++; |
| } |
| |
| if (license->id.lic_id == NO_VAL16) |
| return 1; |
| |
| return 0; |
| } |
| |
| static void _set_license_ids(void) |
| { |
| if (!cluster_license_list) { |
| /* No licenses, nothing to do */ |
| return; |
| } |
| |
| if (list_for_each(cluster_license_list, _foreach_license_set_id, NULL) < |
| 0) |
| fatal("Can't set lic_id"); |
| } |
| |
| static bool _sufficient_licenses(licenses_t *request, licenses_t *match, |
| int resv_licenses) |
| { |
| if (match->total == INFINITE) |
| return true; |
| |
| return (request->total + match->used + match->last_deficit + |
| resv_licenses) <= match->total; |
| } |
| |
| static void _parse_hierarchical_resources(list_t **license_list_ptr) |
| { |
| char *resources_conf = get_extra_conf_path("resources.yaml"); |
| struct stat stat_buf; |
| |
| xassert(license_list_ptr); |
| |
| /* Parse hierarchical resources from resources.yaml if config exists */ |
| if (!stat(resources_conf, &stat_buf)) { |
| int rc; |
| buf_t *conf_buf = NULL; |
| |
| if (!*license_list_ptr) |
| *license_list_ptr = list_create(license_free_rec); |
| |
| if (!(conf_buf = create_mmap_buf(resources_conf))) { |
| fatal("Hierarchical resources could not be loaded from %s", |
| resources_conf); |
| } |
| DATA_PARSE_FROM_STR(H_RESOURCES_AS_LICENSE_LIST, conf_buf->head, |
| conf_buf->size, *license_list_ptr, NULL, |
| MIME_TYPE_YAML, rc); |
| if (rc) |
| fatal("Something wrong with reading %s: %s", |
| resources_conf, slurm_strerror(rc)); |
| |
| if ((slurm_conf.debug_flags & DEBUG_FLAG_LICENSE)) { |
| char *dump_str = NULL; |
| DATA_DUMP_TO_STR(H_RESOURCES_AS_LICENSE_LIST, |
| *license_list_ptr, dump_str, NULL, |
| MIME_TYPE_YAML, SER_FLAGS_NO_TAG, rc); |
| if (rc) |
| error("Hierarchical resources dump failed"); |
| verbose("Dump hierarchical resources:\n %s", dump_str); |
| xfree(dump_str); |
| } |
| FREE_NULL_BUFFER(conf_buf); |
| } |
| |
| xfree(resources_conf); |
| } |
| |
| /* Initialize licenses on this system based upon slurm.conf */ |
| extern int license_init(char *licenses) |
| { |
| bool valid = true; |
| |
| if (xstrcasestr(slurm_conf.preempt_params, "reclaim_licenses")) |
| preempt_for_licenses = true; |
| |
| last_license_update = time(NULL); |
| |
| slurm_mutex_lock(&license_mutex); |
| if (cluster_license_list) |
| fatal("cluster_license_list already defined"); |
| |
| cluster_license_list = _build_license_list(licenses, &valid, false); |
| if (!valid) |
| fatal("Invalid configured licenses: %s", licenses); |
| |
| _parse_hierarchical_resources(&cluster_license_list); |
| |
| next_lic_id = 0; |
| _set_license_ids(); |
| |
| _licenses_print("init_license", cluster_license_list, NULL); |
| slurm_mutex_unlock(&license_mutex); |
| return SLURM_SUCCESS; |
| } |
| |
| static int _foreach_license_set_hres(void *x, void *key) |
| { |
| licenses_t *license = x; |
| licenses_t *hres_head = |
| list_find_first_ro(cluster_license_list, _license_find_rec, |
| license->name); |
| if (license->nodes) { |
| if (hres_head->mode != license->mode) { |
| error("%s HRES Mode mismatch %s", __func__, |
| license->name); |
| return -1; |
| } |
| if (license != hres_head) { |
| licenses_find_rec_by_nodes_t args = { |
| .name = license->name, |
| .nodes = license->nodes, |
| }; |
| licenses_t *hres_dup = |
| list_find_first_ro(cluster_license_list, |
| _license_find_rec_by_nodes, |
| &args); |
| if (hres_dup != license) { |
| error("%s HRes %s duplicate layer", __func__, |
| license->name); |
| return -1; |
| } |
| license->id.hres_id = hres_head->id.hres_id; |
| } else |
| license->id.hres_id = license->id.lic_id; |
| |
| if (node_name2bitmap(license->nodes, false, |
| &license->node_bitmap, NULL)) |
| return -1; |
| } else { |
| xassert(license->mode == HRES_MODE_OFF); |
| if (license != hres_head) { |
| error("%s duplicate license %s", __func__, |
| license->name); |
| return -1; |
| } |
| license->id.hres_id = NO_VAL16; |
| } |
| |
| return 0; |
| } |
| |
| static int _sort_hres(void *void1, void *void2) |
| { |
| licenses_t *lic1 = *(licenses_t **) void1; |
| licenses_t *lic2 = *(licenses_t **) void2; |
| |
| if (lic1->id.hres_id != lic2->id.hres_id) |
| return 0; |
| |
| if (lic1->id.hres_id == NO_VAL16) |
| return 0; |
| |
| if (lic1->mode != HRES_MODE_1) |
| return 0; |
| |
| if (lic1->total > lic2->total) |
| return -1; |
| else if (lic1->total < lic2->total) |
| return 1; |
| |
| return 0; |
| } |
| |
| extern int hres_init() |
| { |
| slurm_mutex_lock(&license_mutex); |
| |
| if (!cluster_license_list) { |
| slurm_mutex_unlock(&license_mutex); |
| return SLURM_SUCCESS; |
| } |
| |
| last_license_update = time(NULL); |
| |
| if (list_for_each_ro(cluster_license_list, _foreach_license_set_hres, |
| NULL) < 0) |
| fatal("Can't set hres_id or bitmap"); |
| list_sort(cluster_license_list, _sort_hres); |
| _licenses_print("hres_init", cluster_license_list, NULL); |
| slurm_mutex_unlock(&license_mutex); |
| return SLURM_SUCCESS; |
| } |
| |
| static int _foreach_hres_filter_mode1(void *x, void *arg) |
| { |
| licenses_t *match = x; |
| foreach_hres_filter_t *args = arg; |
| int resv_licenses = 0; |
| |
| if (match->id.hres_id != args->license_entry->id.hres_id) |
| return 0; |
| |
| resv_licenses = |
| job_test_lic_resv(args->job_ptr, match->id, args->when, false); |
| if (_sufficient_licenses(args->license_entry, match, resv_licenses)) |
| bit_or(args->node_mask, match->node_bitmap); |
| |
| return 0; |
| } |
| |
| static int _foreach_hres_filter_mode2(void *x, void *arg) |
| { |
| licenses_t *match = x; |
| foreach_hres_filter_t *args = arg; |
| int resv_licenses = 0; |
| |
| if (match->id.hres_id != args->license_entry->id.hres_id) |
| return 0; |
| |
| resv_licenses = |
| job_test_lic_resv(args->job_ptr, match->id, args->when, false); |
| |
| if (!_sufficient_licenses(args->license_entry, match, resv_licenses)) |
| bit_and_not(args->node_mask, match->node_bitmap); |
| |
| return 0; |
| } |
| |
| static int _foreach_bf_hres_filter_mode1(void *x, void *arg) |
| { |
| bf_license_t *bf_lic = x; |
| foreach_hres_filter_t *args = arg; |
| |
| if (bf_lic->id.hres_id != args->license_entry->id.hres_id) |
| return 0; |
| if (bf_lic->resv_ptr && (args->job_ptr->resv_ptr != bf_lic->resv_ptr)) |
| return 0; |
| |
| if (args->license_entry->total <= bf_lic->remaining) { |
| licenses_t *match = |
| list_find_first(cluster_license_list, |
| _license_find_rec_by_id, &bf_lic->id); |
| bit_or(args->node_mask, match->node_bitmap); |
| } |
| |
| return 0; |
| } |
| |
| static int _foreach_bf_hres_filter_mode2(void *x, void *arg) |
| { |
| bf_license_t *bf_lic = x; |
| foreach_hres_filter_t *args = arg; |
| |
| if (bf_lic->id.hres_id != args->license_entry->id.hres_id) |
| return 0; |
| if (bf_lic->resv_ptr && (args->job_ptr->resv_ptr != bf_lic->resv_ptr)) |
| return 0; |
| |
| if (args->license_entry->total > bf_lic->remaining) { |
| licenses_t *match = |
| list_find_first(cluster_license_list, |
| _license_find_rec_by_id, &bf_lic->id); |
| bit_and_not(args->node_mask, match->node_bitmap); |
| } |
| |
| return 0; |
| } |
| |
| static int _foreach_hres_filter(void *x, void *arg) |
| { |
| licenses_t *license_entry = x; |
| hres_filter_args_t *args = arg; |
| |
| if (license_entry->id.hres_id != NO_VAL16) { |
| bitstr_t *node_mask = bit_alloc(node_record_count); |
| foreach_hres_filter_t arg2 = { |
| .job_ptr = args->job_ptr, |
| .node_mask = node_mask, |
| .license_entry = license_entry, |
| .when = args->when, |
| }; |
| |
| list_for_each_ro(args->license_list, _foreach_hres_filter_mode1, |
| &arg2); |
| if (license_entry->mode == HRES_MODE_2) |
| list_for_each_ro(args->license_list, |
| _foreach_hres_filter_mode2, &arg2); |
| |
| bit_and(args->node_bitmap, node_mask); |
| FREE_NULL_BITMAP(node_mask); |
| return 0; |
| } |
| |
| return 0; |
| } |
| |
| extern int hres_filter_with_list(job_record_t *job_ptr, bitstr_t *node_bitmap, |
| list_t *license_list) |
| { |
| hres_filter_args_t filter_args = { |
| .job_ptr = job_ptr, |
| .license_list = license_list, |
| .node_bitmap = node_bitmap, |
| .when = time(NULL), |
| }; |
| |
| if (!job_ptr->license_list || !license_list) |
| return SLURM_SUCCESS; |
| |
| list_for_each(job_ptr->license_list, _foreach_hres_filter, |
| &filter_args); |
| return SLURM_SUCCESS; |
| } |
| |
| extern int hres_filter(job_record_t *job_ptr, bitstr_t *node_bitmap) |
| { |
| if (slurm_conf.debug_flags & DEBUG_FLAG_LICENSE) { |
| char *tmp_str = bitmap2node_name(node_bitmap); |
| verbose("%s: %pJ IN node_bitmap:%s", |
| __func__, job_ptr, tmp_str); |
| xfree(tmp_str); |
| } |
| slurm_mutex_lock(&license_mutex); |
| |
| hres_filter_with_list(job_ptr, node_bitmap, cluster_license_list); |
| |
| slurm_mutex_unlock(&license_mutex); |
| if (slurm_conf.debug_flags & DEBUG_FLAG_LICENSE) { |
| char *tmp_str = bitmap2node_name(node_bitmap); |
| verbose("%s: %pJ OUT node_bitmap:%s", |
| __func__, job_ptr, tmp_str); |
| xfree(tmp_str); |
| } |
| return SLURM_SUCCESS; |
| } |
| |
| static int _foreach_bf_hres_filter(void *x, void *arg) |
| { |
| licenses_t *license_entry = x; |
| bf_hres_filter_args_t *args = arg; |
| |
| if (license_entry->id.hres_id != NO_VAL16) { |
| bitstr_t *node_mask = bit_alloc(node_record_count); |
| foreach_hres_filter_t arg2 = { |
| .job_ptr = args->job_ptr, |
| .node_mask = node_mask, |
| .license_entry = license_entry, |
| }; |
| |
| list_for_each_ro(args->bf_license_list, |
| _foreach_bf_hres_filter_mode1, &arg2); |
| |
| if (license_entry->mode == HRES_MODE_2) |
| list_for_each_ro(args->bf_license_list, |
| _foreach_bf_hres_filter_mode2, &arg2); |
| |
| bit_and(args->node_bitmap, node_mask); |
| FREE_NULL_BITMAP(node_mask); |
| return 0; |
| } |
| |
| return 0; |
| } |
| |
| extern void slurm_bf_hres_filter(job_record_t *job_ptr, bitstr_t *node_bitmap, |
| bf_licenses_t *bf_license_list) |
| { |
| bf_hres_filter_args_t filter_args = { |
| .bf_license_list = bf_license_list, |
| .job_ptr = job_ptr, |
| .node_bitmap = node_bitmap, |
| }; |
| |
| if (!job_ptr->license_list || !bf_license_list) |
| return; |
| |
| if (slurm_conf.debug_flags & DEBUG_FLAG_LICENSE) { |
| char *tmp_str = bitmap2node_name(node_bitmap); |
| verbose("%s: %pJ IN node_bitmap:%s", |
| __func__, job_ptr, tmp_str); |
| xfree(tmp_str); |
| } |
| |
| slurm_mutex_lock(&license_mutex); |
| list_for_each(job_ptr->license_list, _foreach_bf_hres_filter, |
| &filter_args); |
| slurm_mutex_unlock(&license_mutex); |
| |
| if (slurm_conf.debug_flags & DEBUG_FLAG_LICENSE) { |
| char *tmp_str = bitmap2node_name(node_bitmap); |
| verbose("%s: %pJ OUT node_bitmap:%s", |
| __func__, job_ptr, tmp_str); |
| xfree(tmp_str); |
| } |
| return; |
| } |
| |
| extern licenses_t *license_find_rec_by_id(list_t *license_list, |
| licenses_id_t id) |
| { |
| return list_find_first_ro(license_list, _license_find_rec_by_id, &id); |
| } |
| |
| /* Update licenses on this system based upon slurm.conf. |
| * Remove all previously allocated licenses */ |
| extern int license_update(char *licenses) |
| { |
| list_itr_t *iter; |
| licenses_t *license_entry, *match; |
| list_t *new_list; |
| bool valid = true; |
| |
| new_list = _build_license_list(licenses, &valid, false); |
| if (!valid) |
| fatal("Invalid configured licenses: %s", licenses); |
| |
| _parse_hierarchical_resources(&new_list); |
| |
| slurm_mutex_lock(&license_mutex); |
| if (!cluster_license_list) { /* no licenses before now */ |
| cluster_license_list = new_list; |
| slurm_mutex_unlock(&license_mutex); |
| return SLURM_SUCCESS; |
| } |
| |
| iter = list_iterator_create(cluster_license_list); |
| while ((license_entry = list_next(iter))) { |
| /* Always add the remote ones, since we handle those |
| else where. */ |
| if (license_entry->remote) { |
| list_remove(iter); |
| if (!new_list) |
| new_list = list_create(license_free_rec); |
| license_entry->used = 0; |
| list_append(new_list, license_entry); |
| continue; |
| } |
| if (new_list) { |
| licenses_find_rec_by_nodes_t args = { |
| .name = license_entry->name, |
| .nodes = license_entry->nodes, |
| }; |
| match = list_find_first(new_list, |
| _license_find_rec_by_nodes, |
| &args); |
| } else |
| match = NULL; |
| |
| if (!match) { |
| info("license %s removed with %u in use", |
| license_entry->name, license_entry->used); |
| } else { |
| match->id.lic_id = license_entry->id.lic_id; |
| match->id.hres_id = license_entry->id.hres_id; |
| if (license_entry->used > match->total) { |
| info("license %s count decreased", |
| match->name); |
| } |
| } |
| } |
| list_iterator_destroy(iter); |
| |
| FREE_NULL_LIST(cluster_license_list); |
| cluster_license_list = new_list; |
| _set_license_ids(); |
| |
| _licenses_print("update_license", cluster_license_list, NULL); |
| slurm_mutex_unlock(&license_mutex); |
| return SLURM_SUCCESS; |
| } |
| |
| extern void license_add_remote(slurmdb_res_rec_t *rec) |
| { |
| licenses_t *license_entry; |
| char *name; |
| |
| |
| xassert(rec); |
| xassert(rec->type == SLURMDB_RESOURCE_LICENSE); |
| |
| name = xstrdup_printf("%s@%s", rec->name, rec->server); |
| |
| slurm_mutex_lock(&license_mutex); |
| if (!cluster_license_list) { |
| /* If last_license_update then init already ran and we |
| * don't have any licenses defined in the slurm.conf |
| * so make the cluster_license_list. |
| */ |
| xassert(last_license_update); |
| cluster_license_list = list_create(license_free_rec); |
| } |
| |
| license_entry = list_find_first( |
| cluster_license_list, _license_find_remote_rec, name); |
| |
| if (license_entry) |
| error("license_add_remote: license %s already exists!", name); |
| else |
| _add_res_rec_2_lic_list(rec, 0); |
| |
| xfree(name); |
| |
| slurm_mutex_unlock(&license_mutex); |
| } |
| |
| extern void license_update_remote(slurmdb_res_rec_t *rec) |
| { |
| licenses_t *license_entry; |
| char *name; |
| |
| xassert(rec); |
| xassert(rec->clus_res_rec); |
| xassert(rec->type == SLURMDB_RESOURCE_LICENSE); |
| |
| name = xstrdup_printf("%s@%s", rec->name, rec->server); |
| |
| slurm_mutex_lock(&license_mutex); |
| if (!cluster_license_list) { |
| /* If last_license_update then init already ran and we |
| * don't have any licenses defined in the slurm.conf |
| * so make the cluster_license_list. |
| */ |
| xassert(last_license_update); |
| cluster_license_list = list_create(license_free_rec); |
| } |
| |
| license_entry = list_find_first( |
| cluster_license_list, _license_find_remote_rec, name); |
| |
| if (!license_entry) { |
| debug("license_update_remote: License '%s' not found, adding", |
| name); |
| _add_res_rec_2_lic_list(rec, 0); |
| } else { |
| _handle_consumed(license_entry, rec); |
| } |
| last_license_update = time(NULL); |
| |
| xfree(name); |
| |
| slurm_mutex_unlock(&license_mutex); |
| } |
| |
| extern void license_remove_remote(slurmdb_res_rec_t *rec) |
| { |
| licenses_t *license_entry; |
| list_itr_t *iter; |
| char *name; |
| |
| xassert(rec); |
| xassert(rec->type == SLURMDB_RESOURCE_LICENSE); |
| |
| slurm_mutex_lock(&license_mutex); |
| if (!cluster_license_list) { |
| xassert(last_license_update); |
| cluster_license_list = list_create(license_free_rec); |
| } |
| |
| name = xstrdup_printf("%s@%s", rec->name, rec->server); |
| |
| iter = list_iterator_create(cluster_license_list); |
| while ((license_entry = list_next(iter))) { |
| if (!license_entry->remote) |
| continue; |
| if (!xstrcmp(license_entry->name, name)) { |
| info("license_remove_remote: license %s " |
| "removed with %u in use", |
| license_entry->name, license_entry->used); |
| list_delete_item(iter); |
| last_license_update = time(NULL); |
| break; |
| } |
| } |
| list_iterator_destroy(iter); |
| |
| if (!license_entry) |
| error("license_remote_remote: License '%s' not found", name); |
| |
| xfree(name); |
| slurm_mutex_unlock(&license_mutex); |
| } |
| |
| extern void license_sync_remote(list_t *res_list) |
| { |
| slurmdb_res_rec_t *rec = NULL; |
| licenses_t *license_entry; |
| list_itr_t *iter; |
| |
| slurm_mutex_lock(&license_mutex); |
| if (res_list && !cluster_license_list) { |
| xassert(last_license_update); |
| cluster_license_list = list_create(license_free_rec); |
| } |
| |
| iter = list_iterator_create(cluster_license_list); |
| if (res_list) { |
| list_itr_t *iter2 = list_iterator_create(res_list); |
| while ((rec = list_next(iter2))) { |
| char *name; |
| if (rec->type != SLURMDB_RESOURCE_LICENSE) |
| continue; |
| name = xstrdup_printf("%s@%s", rec->name, rec->server); |
| while ((license_entry = list_next(iter))) { |
| if (!license_entry->remote) |
| continue; |
| if (!xstrcmp(license_entry->name, name)) { |
| license_entry->remote = 2; |
| _handle_consumed(license_entry, rec); |
| if (license_entry->used > |
| license_entry->total) { |
| info("license %s count " |
| "decreased", |
| license_entry->name); |
| } |
| break; |
| } |
| } |
| xfree(name); |
| if (!license_entry) |
| _add_res_rec_2_lic_list(rec, 1); |
| list_iterator_reset(iter); |
| } |
| list_iterator_destroy(iter2); |
| } |
| |
| while ((license_entry = list_next(iter))) { |
| if (!license_entry->remote) |
| continue; |
| else if (license_entry->remote == 1) { |
| info("license_remove_remote: license %s " |
| "removed with %u in use", |
| license_entry->name, license_entry->used); |
| list_delete_item(iter); |
| last_license_update = time(NULL); |
| } else if (license_entry->remote == 2) |
| license_entry->remote = 1; |
| } |
| list_iterator_destroy(iter); |
| |
| slurm_mutex_unlock(&license_mutex); |
| } |
| |
| /* Free memory associated with licenses on this system */ |
| extern void license_free(void) |
| { |
| slurm_mutex_lock(&license_mutex); |
| FREE_NULL_LIST(cluster_license_list); |
| slurm_mutex_unlock(&license_mutex); |
| } |
| |
| /* |
| * license_validate - Test if the required licenses are valid |
| * IN licenses - required licenses |
| * IN validate_configured - if true, validate that there are enough configured |
| * licenses for the requested amount. |
| * IN validate_existing - if true, validate that licenses exist, otherwise don't |
| * return them in the final list. |
| * OUT tres_req_cnt - appropriate counts for each requested gres, |
| * since this only matters on pending jobs you can |
| * send in NULL otherwise |
| * OUT valid - true if required licenses are valid and a sufficient number |
| * are configured (though not necessarily available now) |
| * RET license_list, must be destroyed by caller |
| */ |
| extern list_t *license_validate(char *licenses, bool validate_configured, |
| bool validate_existing, bool hres, |
| uint64_t *tres_req_cnt, bool *valid) |
| { |
| list_itr_t *iter; |
| licenses_t *license_entry, *match; |
| list_t *job_license_list; |
| static bool first_run = 1; |
| static slurmdb_tres_rec_t tres_req; |
| int tres_pos; |
| |
| /* Init all the license TRES to 0 */ |
| if (tres_req_cnt) { |
| assoc_mgr_lock_t locks = { .tres = READ_LOCK }; |
| assoc_mgr_lock(&locks); |
| |
| /* |
| * We can start at TRES_ARRAY_TOTAL_CNT as we know licenses are |
| * after the static TRES. |
| */ |
| for (tres_pos = TRES_ARRAY_TOTAL_CNT; |
| tres_pos < slurmctld_tres_cnt; |
| tres_pos++) { |
| if (tres_req_cnt[tres_pos] && |
| !xstrcasecmp(assoc_mgr_tres_array[tres_pos]->type, |
| "license")) { |
| tres_req_cnt[tres_pos] = 0; |
| } |
| } |
| assoc_mgr_unlock(&locks); |
| } |
| |
| job_license_list = _build_license_list(licenses, valid, hres); |
| if (!job_license_list) |
| return job_license_list; |
| |
| /* we only need to init this once */ |
| if (first_run) { |
| first_run = 0; |
| memset(&tres_req, 0, sizeof(slurmdb_tres_rec_t)); |
| tres_req.type = "license"; |
| } |
| |
| slurm_mutex_lock(&license_mutex); |
| iter = list_iterator_create(job_license_list); |
| while ((license_entry = list_next(iter))) { |
| if (cluster_license_list) { |
| if (license_entry->nodes) { |
| licenses_find_rec_by_nodes_t args = { |
| .name = license_entry->name, |
| .nodes = license_entry->nodes, |
| }; |
| match = list_find_first( |
| cluster_license_list, |
| _license_find_rec_by_nodes, &args); |
| } else |
| match = list_find_first(cluster_license_list, |
| _license_find_rec, |
| license_entry->name); |
| } else |
| match = NULL; |
| if (!match) { |
| debug("License name requested (%s) does not exist", |
| license_entry->name); |
| if (!validate_existing) { |
| list_delete_item(iter); |
| continue; |
| } |
| *valid = false; |
| break; |
| } else if (validate_configured && |
| (license_entry->total > match->total)) { |
| debug("Licenses count requested higher than configured " |
| "(%s: %u > %u)", |
| match->name, license_entry->total, match->total); |
| *valid = false; |
| break; |
| } |
| |
| license_entry->id.lic_id = match->id.lic_id; |
| license_entry->id.hres_id = match->id.hres_id; |
| license_entry->mode = match->mode; |
| |
| if (tres_req_cnt) { |
| tres_req.name = license_entry->name; |
| if ((tres_pos = assoc_mgr_find_tres_pos( |
| &tres_req, false)) != -1) |
| tres_req_cnt[tres_pos] = |
| (uint64_t)license_entry->total; |
| } |
| } |
| list_iterator_destroy(iter); |
| slurm_mutex_unlock(&license_mutex); |
| |
| _licenses_print("request_license", job_license_list, NULL); |
| |
| if (!(*valid)) { |
| FREE_NULL_LIST(job_license_list); |
| } |
| return job_license_list; |
| } |
| |
| /* |
| * license_job_merge - The licenses from one job have just been merged into |
| * another job by appending one job's licenses to another, possibly |
| * including duplicate names. Reconstruct this job's licenses and |
| * license_list fields to eliminate duplicates. |
| */ |
| extern void license_job_merge(job_record_t *job_ptr) |
| { |
| bool valid = true; |
| |
| FREE_NULL_LIST(job_ptr->license_list); |
| job_ptr->license_list = |
| _build_license_list(job_ptr->licenses, &valid, false); |
| xfree(job_ptr->licenses); |
| job_ptr->licenses = license_list_to_string(job_ptr->license_list); |
| } |
| |
| static void _add_license(list_t *license_list, licenses_t *license_entry) |
| { |
| if (!list_find_first(license_list, _license_find_rec_by_id, |
| &license_entry->id)) { |
| list_append(license_list, license_entry); |
| } |
| } |
| |
| static int _foreach_license_job_test(void *x, void *arg) |
| { |
| licenses_t *license_entry = x; |
| licenses_t *match; |
| license_test_args_t *test_args = arg; |
| job_record_t *job_ptr = test_args->job_ptr; |
| list_t *license_list = test_args->license_list; |
| bool reboot = test_args->reboot; |
| time_t when = test_args->when; |
| int resv_licenses; |
| |
| if (license_entry->id.hres_id != NO_VAL16) |
| return 0; |
| |
| match = list_find_first(license_list, _license_find_rec_by_id, |
| &(license_entry->id)); |
| if (!match) { |
| error("could not find license %s for job %u", |
| license_entry->name, job_ptr->job_id); |
| /* |
| * Preempting jobs for licenses won't be effective, so don't |
| * preempt for any. |
| */ |
| if (job_ptr->licenses_to_preempt) |
| FREE_NULL_LIST(job_ptr->licenses_to_preempt); |
| test_args->rc = SLURM_ERROR; |
| return -1; |
| } else if (license_entry->total > match->total) { |
| info("job %u wants more %s(lic_id=%u) licenses than configured", |
| job_ptr->job_id, license_entry->name, match->id.lic_id); |
| /* |
| * Preempting jobs for licenses won't be effective so don't |
| * preempt for any. |
| */ |
| if (job_ptr->licenses_to_preempt) |
| FREE_NULL_LIST(job_ptr->licenses_to_preempt); |
| test_args->rc = SLURM_ERROR; |
| return -1; |
| } else if (!_sufficient_licenses(license_entry, match, 0)) { |
| if (job_ptr->licenses_to_preempt) |
| _add_license(job_ptr->licenses_to_preempt, |
| license_entry); |
| test_args->rc = EAGAIN; |
| } else { |
| /* Assume node reboot required since we have not |
| * selected the compute nodes yet */ |
| resv_licenses = job_test_lic_resv(job_ptr, license_entry->id, |
| when, reboot); |
| if (!_sufficient_licenses(license_entry, match, |
| resv_licenses)) { |
| if (job_ptr->licenses_to_preempt) |
| _add_license(job_ptr->licenses_to_preempt, |
| license_entry); |
| test_args->rc = EAGAIN; |
| } else if (license_entry->op_or) { |
| test_args->rc = SLURM_SUCCESS; |
| FREE_NULL_LIST(job_ptr->licenses_to_preempt); |
| /* Stop list_for_each */ |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| /* |
| * license_job_test_with_list - Test if the licenses required for a job are |
| * available in provided list |
| * IN job_ptr - job identification |
| * IN when - time to check |
| * IN reboot - true if node reboot required to start job |
| * RET: SLURM_SUCCESS, EAGAIN (not available now), SLURM_ERROR (never runnable) |
| */ |
| extern int license_job_test_with_list(job_record_t *job_ptr, time_t when, |
| bool reboot, list_t *license_list, |
| bool check_preempt_licenses) |
| { |
| license_test_args_t test_args = { |
| .job_ptr = job_ptr, |
| .license_list = license_list, |
| .rc = SLURM_SUCCESS, |
| .reboot = reboot, |
| .when = when, |
| }; |
| licenses_t *license_entry; |
| bool use_licenses_to_preempt; |
| |
| if (!job_ptr->license_list) /* no licenses needed */ |
| return SLURM_SUCCESS; |
| |
| /* reclaim_licenses is disabled with OR'd licenses */ |
| license_entry = list_peek(job_ptr->license_list); |
| use_licenses_to_preempt = preempt_for_licenses && |
| check_preempt_licenses && |
| !license_entry->op_or; |
| if (!job_ptr->licenses_to_preempt && use_licenses_to_preempt) |
| job_ptr->licenses_to_preempt = list_create(NULL); |
| |
| list_for_each(job_ptr->license_list, _foreach_license_job_test, |
| &test_args); |
| if (use_licenses_to_preempt) |
| _licenses_print("licenses_to_preempt", |
| job_ptr->licenses_to_preempt, job_ptr); |
| |
| return test_args.rc; |
| } |
| |
| /* |
| * license_job_test - Test if the licenses required for a job are available |
| * IN job_ptr - job identification |
| * IN when - time to check |
| * IN reboot - true if node reboot required to start job |
| * RET: SLURM_SUCCESS, EAGAIN (not available now), SLURM_ERROR (never runnable) |
| */ |
| extern int license_job_test(job_record_t *job_ptr, time_t when, bool reboot) |
| { |
| int rc; |
| |
| slurm_mutex_lock(&license_mutex); |
| rc = license_job_test_with_list(job_ptr, when, reboot, |
| cluster_license_list, false); |
| slurm_mutex_unlock(&license_mutex); |
| |
| return rc; |
| } |
| |
| static int _foreach_license_copy(void *x, void *arg) |
| { |
| licenses_t *license_entry_src = x; |
| licenses_t *license_entry_dest = xmalloc(sizeof(licenses_t)); |
| list_t *license_list_dest = arg; |
| |
| license_entry_dest->name = xstrdup(license_entry_src->name); |
| license_entry_dest->total = license_entry_src->total; |
| license_entry_dest->used = license_entry_src->used; |
| license_entry_dest->last_deficit = license_entry_src->last_deficit; |
| license_entry_dest->id = license_entry_src->id; |
| license_entry_dest->mode = license_entry_src->mode; |
| license_entry_dest->op_or = license_entry_src->op_or; |
| list_append(license_list_dest, license_entry_dest); |
| |
| return 0; |
| } |
| |
| static int _foreach_license_light_copy(void *x, void *arg) |
| { |
| licenses_t *license_entry_src = x; |
| licenses_t *license_entry_dest = xmalloc(sizeof(licenses_t)); |
| list_t *license_list_dest = arg; |
| |
| license_entry_dest->total = license_entry_src->total; |
| license_entry_dest->used = license_entry_src->used; |
| license_entry_dest->last_deficit = license_entry_src->last_deficit; |
| license_entry_dest->id = license_entry_src->id; |
| license_entry_dest->mode = license_entry_src->mode; |
| license_entry_dest->op_or = license_entry_src->op_or; |
| list_append(license_list_dest, license_entry_dest); |
| |
| return 0; |
| } |
| /* |
| * license_copy - create a copy of a license list |
| * IN license_list_src - job license list to be copied |
| * RET a copy of the license list |
| */ |
| extern list_t *license_copy(list_t *license_list_src) |
| { |
| list_t *license_list_dest = NULL; |
| |
| if (!license_list_src) |
| return license_list_dest; |
| |
| license_list_dest = list_create(license_free_rec); |
| |
| list_for_each(license_list_src, _foreach_license_copy, |
| license_list_dest); |
| |
| return license_list_dest; |
| } |
| |
| /* |
| * cluster_license_copy - create a copy of the cluster_license_list |
| * RET a copy of the license list |
| */ |
| extern list_t *cluster_license_copy(void) |
| { |
| list_t *license_list_dest = NULL; |
| |
| slurm_mutex_lock(&license_mutex); |
| if (cluster_license_list) { |
| license_list_dest = list_create(license_free_rec); |
| list_for_each(cluster_license_list, _foreach_license_light_copy, |
| license_list_dest); |
| } |
| slurm_mutex_unlock(&license_mutex); |
| |
| return license_list_dest; |
| } |
| |
| /* |
| * We need to track the allocated licenses separately, so that: |
| * |
| * - when the job is state saved and then restored, or |
| * - when the job completes, |
| * |
| * we update the license counts in cluster_license_list using only the licenses |
| * that were allocated. |
| */ |
| static int _set_licenses_alloc(job_record_t *job_ptr, bool lic_or, |
| licenses_t *license_entry) |
| { |
| if (lic_or) { |
| if (!license_entry) { |
| /* |
| * We tested that there were enough licenses available |
| * but then there weren't enough when we tried to |
| * allocate. This indicates faulty logic. |
| */ |
| error("Could not allocate licenses %s for %pJ", |
| job_ptr->licenses, job_ptr); |
| return SLURM_ERROR; |
| } |
| |
| /* Remove all other licenses besides the one we allocated. */ |
| list_delete_all(job_ptr->license_list, |
| _license_find_rec_by_id_not, |
| &license_entry->id); |
| xassert(list_count(job_ptr->license_list) == 1); |
| xassert(license_entry == |
| (licenses_t *) list_peek(job_ptr->license_list)); |
| } |
| job_ptr->licenses_allocated = |
| license_list_to_string(job_ptr->license_list); |
| |
| return SLURM_SUCCESS; |
| } |
| |
| static int _foreach_hres_job_get(void *x, void *arg) |
| { |
| licenses_t *match = x; |
| foreach_get_hres_t *args = arg; |
| |
| if (match->id.hres_id != args->license_entry->id.hres_id) |
| return 0; |
| |
| if (!bit_overlap_any(match->node_bitmap, args->job_ptr->node_bitmap)) |
| return 0; |
| |
| if (args->license_entry->mode == HRES_MODE_1) { |
| int resv_licenses = job_test_lic_resv(args->job_ptr, match->id, |
| args->when, false); |
| if (!_sufficient_licenses(args->license_entry, match, |
| resv_licenses)) |
| return 0; |
| match->used += args->license_entry->total; |
| args->license_entry->id.lic_id = match->id.lic_id; |
| xfree(args->license_entry->nodes); |
| args->license_entry->nodes = xstrdup(match->nodes); |
| return -1; |
| } else if (args->license_entry->mode == HRES_MODE_2) { |
| match->used += args->license_entry->total; |
| return 0; |
| } |
| |
| return 0; |
| } |
| |
| /* |
| * license_job_get - Get the licenses required for a job |
| * IN job_ptr - job identification |
| * RET SLURM_SUCCESS or failure code |
| */ |
| extern int license_job_get(job_record_t *job_ptr, bool restore) |
| { |
| list_itr_t *iter; |
| licenses_t *license_entry, *match; |
| int rc = SLURM_SUCCESS; |
| bool lic_or = false; |
| |
| if (!job_ptr->license_list) /* no licenses needed */ |
| return rc; |
| |
| last_license_update = time(NULL); |
| |
| slurm_mutex_lock(&license_mutex); |
| iter = list_iterator_create(job_ptr->license_list); |
| while ((license_entry = list_next(iter))) { |
| if (license_entry->id.hres_id != NO_VAL16) { |
| foreach_get_hres_t arg = { |
| .job_ptr = job_ptr, |
| .license_entry = license_entry, |
| .when = last_license_update, |
| }; |
| list_for_each_ro(cluster_license_list, |
| _foreach_hres_job_get, &arg); |
| |
| license_entry->used += license_entry->total; |
| |
| continue; |
| } |
| |
| lic_or = license_entry->op_or; |
| match = list_find_first(cluster_license_list, |
| _license_find_rec_by_id, |
| &license_entry->id); |
| if (match) { |
| /* |
| * With OR, we only know that at least one of the job's |
| * requested licenses are available, so we need to test |
| * for availability again. With AND we know that all |
| * licenses are available so we don't need to check. |
| */ |
| if (lic_or) { |
| int resv_blk_lic_cnt = |
| job_test_lic_resv(job_ptr, match->id, |
| last_license_update, |
| false); |
| |
| if (!_sufficient_licenses(license_entry, match, |
| resv_blk_lic_cnt)) { |
| /* Not enough of this license */ |
| continue; |
| } |
| } |
| |
| match->used += license_entry->total; |
| license_entry->used += license_entry->total; |
| if (match->remote && restore) { |
| if (license_entry->total > match->last_deficit) |
| match->last_deficit = 0; |
| else |
| match->last_deficit -= |
| license_entry->total; |
| } |
| if (lic_or) { |
| break; |
| } |
| } else { |
| error("could not find license %s for job %u", |
| license_entry->name, job_ptr->job_id); |
| rc = SLURM_ERROR; |
| } |
| } |
| list_iterator_destroy(iter); |
| |
| /* When restoring, allocated licenses is already set */ |
| if (!rc && !restore) |
| rc = _set_licenses_alloc(job_ptr, lic_or, license_entry); |
| |
| _licenses_print("acquire_license", cluster_license_list, job_ptr); |
| slurm_mutex_unlock(&license_mutex); |
| return rc; |
| } |
| |
| static int _foreach_hres_job_return_mode2(void *x, void *arg) |
| { |
| licenses_t *lic = x; |
| foreach_get_hres_t *args = arg; |
| licenses_t *match; |
| |
| if (lic->id.hres_id != args->license_entry->id.hres_id) |
| return 0; |
| if (lic->node_bitmap) |
| match = lic; |
| else |
| match = list_find_first_ro(cluster_license_list, |
| _license_find_rec_by_id, &lic->id); |
| if (!match || |
| !bit_overlap_any(match->node_bitmap, args->job_ptr->node_bitmap)) |
| return 0; |
| if (lic->used >= args->license_entry->total) |
| lic->used -= args->license_entry->total; |
| else { |
| error("%s: license use count underflow for lic_id=%u", |
| __func__, lic->id.lic_id); |
| lic->used = 0; |
| } |
| |
| return 0; |
| } |
| |
| static int _foreach_license_job_return(void *x, void *arg) |
| { |
| licenses_t *license_entry = x; |
| license_return_args_t *args = arg; |
| licenses_t *match; |
| |
| if (license_entry->mode == HRES_MODE_2) { |
| foreach_get_hres_t arg2 = { |
| .job_ptr = args->job_ptr, |
| .license_entry = license_entry, |
| }; |
| if (!args->locked) |
| slurm_mutex_lock(&license_mutex); |
| list_for_each_ro(args->license_list, |
| _foreach_hres_job_return_mode2, &arg2); |
| if (!args->locked) |
| slurm_mutex_unlock(&license_mutex); |
| |
| license_entry->used = 0; |
| return 0; |
| } |
| |
| match = list_find_first(args->license_list, _license_find_rec_by_id, |
| &license_entry->id); |
| if (match) { |
| if (match->used >= license_entry->total) |
| match->used -= license_entry->total; |
| else { |
| error("%s: license use count underflow for lic_id=%u", |
| __func__, match->id.lic_id); |
| match->used = 0; |
| } |
| if (!args->future) { |
| license_entry->used = 0; |
| if (license_entry->mode == HRES_MODE_1) { |
| license_entry->id.lic_id = |
| license_entry->id.hres_id; |
| } |
| } |
| } else { |
| /* This can happen after a reconfiguration */ |
| error("%s: job returning unknown license lic_id=%u", |
| __func__, license_entry->id.lic_id); |
| } |
| return 0; |
| } |
| |
| /* |
| * Return the licenses allocated to a job to the provided list |
| * IN job_ptr - job identification |
| * RET count of license having state changed |
| */ |
| extern int license_job_return_to_list(job_record_t *job_ptr, |
| list_t *license_list, bool locked, |
| bool future) |
| { |
| int rc = 0; |
| license_return_args_t args = { |
| .future = future, |
| .job_ptr = job_ptr, |
| .license_list = license_list, |
| .locked = locked, |
| }; |
| |
| if (!job_ptr->license_list) /* no licenses needed */ |
| return rc; |
| |
| log_flag(TRACE_JOBS, "%s: %pJ", __func__, job_ptr); |
| |
| rc = list_for_each(job_ptr->license_list, _foreach_license_job_return, |
| &args); |
| |
| return rc; |
| } |
| |
| /* |
| * license_job_return - Return the licenses allocated to a job |
| * IN job_ptr - job identification |
| * RET SLURM_SUCCESS or failure code |
| */ |
| extern int license_job_return(job_record_t *job_ptr) |
| { |
| int rc = SLURM_SUCCESS; |
| |
| slurm_mutex_lock(&license_mutex); |
| if (license_job_return_to_list(job_ptr, cluster_license_list, true, |
| false)) |
| last_license_update = time(NULL); |
| _licenses_print("return_license", cluster_license_list, job_ptr); |
| slurm_mutex_unlock(&license_mutex); |
| |
| return rc; |
| } |
| |
| /* |
| * license_list_overlap - test if there is any overlap in licenses |
| * names found in the two lists |
| */ |
| extern bool license_list_overlap(list_t *list_1, list_t *list_2) |
| { |
| if (!list_1 || !list_2) |
| return false; |
| return list_find_first_ro(list_1, _license_find_rec_in_list_by_id, |
| list_2); |
| } |
| |
| /* |
| * license_list_overlap_non_hres - test if there is any overlap in non-hres |
| * licenses names found in the two lists |
| */ |
| extern bool license_list_overlap_non_hres(list_t *list_1, list_t *list_2) |
| { |
| if (!list_1 || !list_2) |
| return false; |
| return list_find_first_ro(list_1, |
| _license_find_non_hres_rec_in_list_by_id, |
| list_2); |
| } |
| |
| /* pack_all_licenses() |
| * |
| * Return license counters to the library. |
| */ |
| extern buf_t *get_all_license_info(uint16_t protocol_version) |
| { |
| list_itr_t *iter; |
| licenses_t *lic_entry; |
| uint32_t lics_packed; |
| int tmp_offset; |
| buf_t *buffer; |
| time_t now = time(NULL); |
| |
| debug2("%s: calling for all licenses", __func__); |
| |
| buffer = init_buf(BUF_SIZE); |
| |
| /* write header: version and time |
| */ |
| lics_packed = 0; |
| pack32(lics_packed, buffer); |
| pack_time(now, buffer); |
| |
| slurm_mutex_lock(&license_mutex); |
| if (cluster_license_list) { |
| iter = list_iterator_create(cluster_license_list); |
| while ((lic_entry = list_next(iter))) { |
| set_reserved_license_count(lic_entry); |
| /* Now encode the license data structure. |
| */ |
| _pack_license(lic_entry, buffer, protocol_version); |
| ++lics_packed; |
| } |
| list_iterator_destroy(iter); |
| } |
| |
| slurm_mutex_unlock(&license_mutex); |
| debug2("%s: processed %d licenses", __func__, lics_packed); |
| |
| /* put the real record count in the message body header |
| */ |
| tmp_offset = get_buf_offset(buffer); |
| set_buf_offset(buffer, 0); |
| pack32(lics_packed, buffer); |
| set_buf_offset(buffer, tmp_offset); |
| |
| return buffer; |
| } |
| |
| static int _foreach_get_total_license_cnt(void *x, void *arg) |
| { |
| licenses_t *license_entry = (licenses_t *) x; |
| foreach_get_total_t *args = arg; |
| |
| if ((license_entry->name == NULL) || (args->name == NULL)) |
| return 0; |
| |
| if (xstrcmp(license_entry->name, args->name)) |
| return 0; |
| |
| *(args->count) = +license_entry->total; |
| return 0; |
| } |
| |
| extern uint32_t get_total_license_cnt(char *name) |
| { |
| uint32_t count = 0; |
| |
| slurm_mutex_lock(&license_mutex); |
| if (cluster_license_list) { |
| foreach_get_total_t arg = { |
| .count = &count, |
| .name = name, |
| }; |
| |
| list_for_each_ro(cluster_license_list, |
| _foreach_get_total_license_cnt, &arg); |
| } |
| slurm_mutex_unlock(&license_mutex); |
| |
| return count; |
| } |
| |
| /* node_read should be locked before coming in here |
| * returns 1 if change happened. |
| */ |
| extern char *licenses_2_tres_str(list_t *license_list) |
| { |
| list_itr_t *itr; |
| slurmdb_tres_rec_t *tres_rec; |
| licenses_t *license_entry; |
| char *tres_str = NULL; |
| static bool first_run = 1; |
| static slurmdb_tres_rec_t tres_req; |
| assoc_mgr_lock_t locks = { .tres = READ_LOCK }; |
| |
| if (!license_list) |
| return NULL; |
| |
| /* we only need to init this once */ |
| if (first_run) { |
| first_run = 0; |
| memset(&tres_req, 0, sizeof(slurmdb_tres_rec_t)); |
| tres_req.type = "license"; |
| } |
| |
| assoc_mgr_lock(&locks); |
| itr = list_iterator_create(license_list); |
| while ((license_entry = list_next(itr))) { |
| tres_req.name = license_entry->name; |
| if (!(tres_rec = assoc_mgr_find_tres_rec(&tres_req))) |
| continue; /* not tracked */ |
| |
| if (slurmdb_find_tres_count_in_string( |
| tres_str, tres_rec->id) != INFINITE64) |
| continue; /* already handled */ |
| /* New license */ |
| xstrfmtcat(tres_str, "%s%u=%"PRIu64, |
| tres_str ? "," : "", |
| tres_rec->id, (uint64_t)license_entry->total); |
| } |
| list_iterator_destroy(itr); |
| assoc_mgr_unlock(&locks); |
| |
| return tres_str; |
| } |
| |
| extern void license_set_job_tres_cnt(list_t *license_list, |
| uint64_t *tres_cnt, |
| bool locked) |
| { |
| list_itr_t *itr; |
| licenses_t *license_entry; |
| static bool first_run = 1; |
| static slurmdb_tres_rec_t tres_rec; |
| int tres_pos; |
| assoc_mgr_lock_t locks = { .tres = READ_LOCK }; |
| |
| /* we only need to init this once */ |
| if (first_run) { |
| first_run = 0; |
| memset(&tres_rec, 0, sizeof(slurmdb_tres_rec_t)); |
| tres_rec.type = "license"; |
| } |
| |
| if (!license_list || !tres_cnt) |
| return; |
| |
| if (!locked) |
| assoc_mgr_lock(&locks); |
| |
| itr = list_iterator_create(license_list); |
| while ((license_entry = list_next(itr))) { |
| tres_rec.name = license_entry->name; |
| if ((tres_pos = assoc_mgr_find_tres_pos( |
| &tres_rec, locked)) != -1) |
| tres_cnt[tres_pos] = (uint64_t)license_entry->total; |
| } |
| list_iterator_destroy(itr); |
| |
| if (!locked) |
| assoc_mgr_unlock(&locks); |
| } |
| |
| /* |
| * Please update src/common/slurm_protocol_pack.c _unpack_license_info_msg() if |
| * this changes. |
| */ |
| static void _pack_license(licenses_t *lic, buf_t *buffer, |
| uint16_t protocol_version) |
| { |
| if (protocol_version >= SLURM_25_05_PROTOCOL_VERSION) { |
| packstr(lic->name, buffer); |
| pack32(lic->total, buffer); |
| pack32(lic->used, buffer); |
| pack32(lic->reserved, buffer); |
| pack8(lic->remote, buffer); |
| pack32(lic->last_consumed, buffer); |
| pack32(lic->last_deficit, buffer); |
| pack_time(lic->last_update, buffer); |
| pack8(lic->mode, buffer); |
| packstr(lic->nodes, buffer); |
| } else if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) { |
| packstr(lic->name, buffer); |
| pack32(lic->total, buffer); |
| pack32(lic->used, buffer); |
| pack32(lic->reserved, buffer); |
| pack8(lic->remote, buffer); |
| pack32(lic->last_consumed, buffer); |
| pack32(lic->last_deficit, buffer); |
| pack_time(lic->last_update, buffer); |
| } else { |
| error("%s: protocol_version %hu not supported", |
| __func__, protocol_version); |
| } |
| } |
| |
| static void _bf_license_free_rec(void *x) |
| { |
| bf_license_t *entry = x; |
| |
| if (!entry) |
| return; |
| |
| xfree(entry); |
| } |
| |
| /* |
| * Will never match on a reserved license. |
| */ |
| static int _bf_licenses_find_rec(void *x, void *key) |
| { |
| bf_license_t *license_entry = x; |
| licenses_id_t *id = key; |
| |
| xassert(license_entry->id.lic_id != NO_VAL16); |
| xassert(id->lic_id != NO_VAL16); |
| |
| if (license_entry->resv_ptr) |
| return 0; |
| |
| if (license_entry->id.lic_id == id->lic_id) |
| return 1; |
| |
| return 0; |
| } |
| |
| static int _bf_licenses_find_resv(void *x, void *key) |
| { |
| bf_license_t *license_entry = x; |
| bf_licenses_find_resv_t *target = key; |
| |
| if (license_entry->resv_ptr != target->resv_ptr) |
| return 0; |
| |
| if (license_entry->id.lic_id != target->id.lic_id) |
| return 0; |
| |
| return 1; |
| } |
| |
| extern list_t *bf_licenses_initial(bool bf_running_job_reserve) |
| { |
| list_t *bf_list; |
| list_itr_t *iter; |
| licenses_t *license_entry; |
| bf_license_t *bf_entry; |
| |
| slurm_mutex_lock(&license_mutex); |
| if (!cluster_license_list || !list_count(cluster_license_list)) { |
| slurm_mutex_unlock(&license_mutex); |
| return NULL; |
| } |
| |
| bf_list = list_create(_bf_license_free_rec); |
| |
| iter = list_iterator_create(cluster_license_list); |
| while ((license_entry = list_next(iter))) { |
| bf_entry = xmalloc(sizeof(*bf_entry)); |
| bf_entry->remaining = license_entry->total; |
| bf_entry->id = license_entry->id; |
| |
| if (!bf_running_job_reserve && |
| (bf_entry->remaining != INFINITE)) |
| bf_entry->remaining -= license_entry->used; |
| |
| list_append(bf_list, bf_entry); |
| } |
| list_iterator_destroy(iter); |
| |
| slurm_mutex_unlock(&license_mutex); |
| |
| return bf_list; |
| } |
| |
| extern char *bf_licenses_to_string(bf_licenses_t *licenses_list) |
| { |
| char *sep = ""; |
| char *licenses = NULL; |
| list_itr_t *iter; |
| bf_license_t *entry; |
| |
| if (!licenses_list) |
| return NULL; |
| |
| iter = list_iterator_create(licenses_list); |
| while ((entry = list_next(iter))) { |
| xstrfmtcat(licenses, "%s%s%s%slic_id=%u:%u", sep, |
| (entry->resv_ptr ? "resv=" : ""), |
| (entry->resv_ptr ? entry->resv_ptr->name : ""), |
| (entry->resv_ptr ? ":" : ""), entry->id.lic_id, |
| entry->remaining); |
| sep = ","; |
| } |
| list_iterator_destroy(iter); |
| |
| return licenses; |
| } |
| |
| static int _foreach_bf_license_copy(void *x, void *arg) |
| { |
| bf_license_t *entry_src = x; |
| bf_licenses_t *licenses_dest = arg; |
| bf_license_t *entry_dest; |
| |
| entry_dest = xmalloc(sizeof(*entry_dest)); |
| entry_dest->remaining = entry_src->remaining; |
| entry_dest->resv_ptr = entry_src->resv_ptr; |
| entry_dest->id = entry_src->id; |
| list_append(licenses_dest, entry_dest); |
| |
| return 0; |
| } |
| |
| extern bf_licenses_t *slurm_bf_licenses_copy(bf_licenses_t *licenses_src) |
| { |
| bf_licenses_t *licenses_dest = NULL; |
| |
| if (!licenses_src) |
| return NULL; |
| |
| licenses_dest = list_create(_bf_license_free_rec); |
| |
| list_for_each(licenses_src, _foreach_bf_license_copy, licenses_dest); |
| |
| return licenses_dest; |
| } |
| |
| static int _foreach_hres_deduct(void *x, void *arg) |
| { |
| bf_license_t *bf_lic = x; |
| licenses_t *match; |
| foreach_get_hres_t *args = arg; |
| |
| if ((bf_lic->id.hres_id != args->license_entry->id.hres_id) || |
| ((args->license_entry->mode == HRES_MODE_1) && |
| IS_JOB_RUNNING(args->job_ptr) && |
| (bf_lic->id.lic_id != args->license_entry->id.lic_id))) |
| return 0; |
| |
| if (bf_lic->resv_ptr && (args->job_ptr->resv_ptr != bf_lic->resv_ptr)) |
| return 0; |
| |
| match = list_find_first(cluster_license_list, _license_find_rec_by_id, |
| &bf_lic->id); |
| if (!match || |
| !bit_overlap_any(match->node_bitmap, args->job_ptr->node_bitmap)) |
| |
| return 0; |
| |
| if (bf_lic->remaining == INFINITE) { |
| ; |
| } else if (bf_lic->remaining < args->license_entry->total) { |
| error("%s: underflow on lic_id=%u", __func__, match->id.lic_id); |
| bf_lic->remaining = 0; |
| } else { |
| bf_lic->remaining -= args->license_entry->total; |
| } |
| |
| if (match->mode == HRES_MODE_1) |
| return -1; |
| else |
| return 0; |
| } |
| extern void slurm_bf_licenses_deduct(bf_licenses_t *licenses, |
| job_record_t *job_ptr) |
| { |
| licenses_t *job_entry; |
| list_itr_t *iter; |
| bool found = false, lic_or = false; |
| |
| xassert(job_ptr); |
| |
| if (!job_ptr->license_list) |
| return; |
| |
| iter = list_iterator_create(job_ptr->license_list); |
| while ((job_entry = list_next(iter))) { |
| bf_license_t *resv_entry = NULL, *bf_entry; |
| int needed = job_entry->total; |
| int resv_acquired = 0; |
| |
| if (job_entry->id.hres_id != NO_VAL16) { |
| foreach_get_hres_t arg = { |
| .job_ptr = job_ptr, |
| .license_entry = job_entry, |
| }; |
| slurm_mutex_lock(&license_mutex); |
| list_for_each_ro(licenses, _foreach_hres_deduct, &arg); |
| slurm_mutex_unlock(&license_mutex); |
| |
| continue; |
| } |
| |
| lic_or = job_entry->op_or; |
| /* |
| * Jobs with reservations may use licenses out of the |
| * reservation, as well as global ones. Deduct from |
| * reservation first, then global as needed. |
| */ |
| if (job_ptr->resv_ptr) { |
| bf_licenses_find_resv_t target_record = { |
| .id = job_entry->id, |
| .resv_ptr = job_ptr->resv_ptr, |
| }; |
| |
| resv_entry = list_find_first(licenses, |
| _bf_licenses_find_resv, |
| &target_record); |
| if (resv_entry && (needed <= resv_entry->remaining)) { |
| resv_entry->remaining -= needed; |
| /* OR - reservation has enough, break. */ |
| if (lic_or) { |
| found = true; |
| break; |
| } |
| continue; |
| } else if (resv_entry) { |
| resv_acquired = resv_entry->remaining; |
| needed -= resv_acquired; |
| resv_entry->remaining = 0; |
| } |
| } |
| |
| bf_entry = list_find_first(licenses, _bf_licenses_find_rec, |
| &job_entry->id); |
| |
| if (!bf_entry) { |
| error("%s: missing license lic_id=%u", |
| __func__, job_entry->id.lic_id); |
| } else if (bf_entry->remaining < needed) { |
| /* |
| * OR - Not an error; skip this one and keep going |
| * until we find the next one that is available. |
| */ |
| if (lic_or) { |
| /* Return resv_acquired licenses */ |
| if (resv_entry) { |
| resv_entry->remaining += resv_acquired; |
| needed += resv_acquired; |
| } |
| continue; |
| } |
| error("%s: underflow on lic_id=%u", |
| __func__, bf_entry->id.lic_id); |
| bf_entry->remaining = 0; |
| } else { |
| bf_entry->remaining -= needed; |
| if (lic_or) { |
| found = true; |
| break; |
| } |
| } |
| } |
| list_iterator_destroy(iter); |
| |
| if (lic_or && !found) { |
| /* |
| * If we get to this function, we should always have found an |
| * available license. If we did not, this indicates an error |
| * in testing if one is available in slurm_bf_licenses_avail(). |
| */ |
| error("%s: %pJ No OR'd licenses available for bf plan", |
| __func__, job_ptr); |
| } |
| } |
| |
| /* |
| * Transfer licenses into the control of a reservation. |
| * Finds the global license, deducts the required number, then assigns those |
| * to a new record locked to that reservation. |
| */ |
| extern void slurm_bf_licenses_transfer(bf_licenses_t *licenses, |
| job_record_t *job_ptr) |
| { |
| licenses_t *resv_entry; |
| list_itr_t *iter; |
| |
| xassert(job_ptr); |
| |
| if (!job_ptr->license_list) |
| return; |
| |
| iter = list_iterator_create(job_ptr->license_list); |
| while ((resv_entry = list_next(iter))) { |
| bf_license_t *bf_entry, *new_entry; |
| int needed = resv_entry->total; |
| int reservable = resv_entry->total; |
| |
| bf_entry = list_find_first(licenses, _bf_licenses_find_rec, |
| &(resv_entry->id)); |
| |
| if (!bf_entry) { |
| error("%s: missing license lic_id=%u", |
| __func__, resv_entry->id.lic_id); |
| } else if (bf_entry->remaining < needed) { |
| error("%s: underflow on lic_id=%u", |
| __func__,bf_entry->id.lic_id); |
| reservable = bf_entry->remaining; |
| bf_entry->remaining = 0; |
| } else { |
| bf_entry->remaining -= needed; |
| reservable = needed; |
| } |
| |
| new_entry = xmalloc(sizeof(*new_entry)); |
| new_entry->id = resv_entry->id; |
| new_entry->remaining = reservable; |
| new_entry->resv_ptr = job_ptr->resv_ptr; |
| |
| list_append(licenses, new_entry); |
| } |
| list_iterator_destroy(iter); |
| } |
| |
| extern bool slurm_bf_licenses_avail(bf_licenses_t *licenses, |
| job_record_t *job_ptr, |
| bitstr_t *node_bitmap) |
| { |
| list_itr_t *iter; |
| licenses_t *need; |
| bool avail = true; |
| bitstr_t *tmp_bitmap = NULL; |
| |
| if (!job_ptr->license_list) |
| return true; |
| |
| iter = list_iterator_create(job_ptr->license_list); |
| while ((need = list_next(iter))) { |
| bf_license_t *resv_entry = NULL, *bf_entry; |
| int needed = need->total; |
| |
| if (need->id.hres_id != NO_VAL16) { |
| if (!node_bitmap) |
| continue; |
| COPY_BITMAP(tmp_bitmap, node_bitmap); |
| slurm_bf_hres_filter(job_ptr, tmp_bitmap, licenses); |
| if (!bit_equal(tmp_bitmap, node_bitmap)) { |
| avail = false; |
| break; |
| } |
| continue; |
| } |
| /* |
| * Jobs with reservations may use licenses out of the |
| * reservation, as well as global ones. Deduct from |
| * reservation first, then global as needed. |
| */ |
| if (job_ptr->resv_ptr) { |
| bf_licenses_find_resv_t target_record = { |
| .id = need->id, |
| .resv_ptr = job_ptr->resv_ptr, |
| }; |
| |
| resv_entry = list_find_first(licenses, |
| _bf_licenses_find_resv, |
| &target_record); |
| |
| if (resv_entry && (needed <= resv_entry->remaining)) { |
| /* |
| * OR - only need one, stop searching. |
| * Set avail = true in case a previous license |
| * was unavailable. |
| */ |
| if (need->op_or) { |
| avail = true; |
| break; |
| } |
| /* AND */ |
| continue; |
| } else if (resv_entry) |
| needed -= resv_entry->remaining; |
| } |
| |
| bf_entry = list_find_first(licenses, _bf_licenses_find_rec, |
| &(need->id)); |
| |
| if (!bf_entry || (bf_entry->remaining < needed)) { |
| avail = false; |
| /* |
| * OR - keep searching until we find one that is |
| * available or we get through the whole list. |
| */ |
| if (need->op_or) |
| continue; |
| /* AND */ |
| break; |
| } |
| /* OR - only need one, stop searching. */ |
| if (need->op_or) { |
| avail = true; |
| break; |
| } |
| } |
| list_iterator_destroy(iter); |
| FREE_NULL_BITMAP(tmp_bitmap); |
| |
| return avail; |
| } |
| |
| static int _bf_licenses_find_difference(void *x, void *key) |
| { |
| bf_license_t *entry_a = x; |
| bf_licenses_t *b = key; |
| bf_license_t *entry_b; |
| bf_licenses_find_resv_t target_record = { |
| .id = entry_a->id, |
| .resv_ptr = entry_a->resv_ptr, |
| }; |
| |
| entry_b = list_find_first_ro(b, _bf_licenses_find_resv, &target_record); |
| |
| if (!entry_b || (entry_a->remaining != entry_b->remaining)) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| extern bool slurm_bf_licenses_equal(bf_licenses_t *a, bf_licenses_t *b) |
| { |
| return !(list_find_first_ro(a, _bf_licenses_find_difference, b)); |
| } |