| /*****************************************************************************\ | |
| * gres.c - driver for gres plugin | |
| ***************************************************************************** | |
| * Copyright (C) 2010 Lawrence Livermore National Security. | |
| * Copyright (C) SchedMD LLC. | |
| * Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). | |
| * Written by Morris Jette <jette1@llnl.gov> | |
| * 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 "config.h" | |
| #define _GNU_SOURCE | |
| #include <ctype.h> | |
| #include <inttypes.h> | |
| #include <limits.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <math.h> | |
| #include <sys/stat.h> | |
| #include <sys/types.h> | |
| #ifdef MAJOR_IN_MKDEV | |
| # include <sys/mkdev.h> | |
| #endif | |
| #ifdef MAJOR_IN_SYSMACROS | |
| # include <sys/sysmacros.h> | |
| #endif | |
| #include "slurm/slurm.h" | |
| #include "slurm/slurm_errno.h" | |
| #include "src/common/assoc_mgr.h" | |
| #include "src/common/bitstring.h" | |
| #include "src/common/drained_tres.h" | |
| #include "src/interfaces/cgroup.h" | |
| #include "src/interfaces/gres.h" | |
| #include "src/interfaces/gpu.h" | |
| #include "src/common/job_resources.h" | |
| #include "src/common/list.h" | |
| #include "src/common/log.h" | |
| #include "src/common/macros.h" | |
| #include "src/common/node_conf.h" | |
| #include "src/common/pack.h" | |
| #include "src/common/parse_config.h" | |
| #include "src/common/parse_value.h" | |
| #include "src/common/plugin.h" | |
| #include "src/common/plugrack.h" | |
| #include "src/common/read_config.h" | |
| #include "src/interfaces/select.h" | |
| #include "src/common/slurm_protocol_api.h" | |
| #include "src/common/slurm_protocol_pack.h" | |
| #include "src/common/strlcpy.h" | |
| #include "src/common/xmalloc.h" | |
| #include "src/common/xsched.h" | |
| #include "src/common/xstring.h" | |
| strong_alias(gres_find_id, slurm_gres_find_id); | |
| strong_alias(gres_find_job_by_key_exact_type, | |
| slurm_gres_find_job_by_key_exact_type); | |
| strong_alias(gres_find_sock_by_job_state, slurm_gres_find_sock_by_job_state); | |
| strong_alias(gres_get_node_used, slurm_gres_get_node_used); | |
| strong_alias(gres_get_system_cnt, slurm_gres_get_system_cnt); | |
| strong_alias(gres_get_step_info, slurm_gres_get_step_info); | |
| strong_alias(gres_sock_delete, slurm_gres_sock_delete); | |
| strong_alias(gres_job_list_delete, slurm_gres_job_list_delete); | |
| strong_alias(destroy_gres_device, slurm_destroy_gres_device); | |
| strong_alias(destroy_gres_slurmd_conf, slurm_destroy_gres_slurmd_conf); | |
| static s_p_options_t _gres_options[] = { | |
| { "AutoDetect", S_P_STRING }, | |
| { "ChunkOrder", S_P_STRING }, /* Chunk position to device index map */ | |
| { "ChunkSizes", S_P_STRING }, /* Valid device chunk sizes */ | |
| { "Count", S_P_STRING }, /* Number of Gres available */ | |
| { "CPUs", S_P_STRING }, /* CPUs to bind to Gres resource | |
| * (deprecated, use Cores) */ | |
| { "Cores", S_P_STRING }, /* Cores to bind to Gres resource */ | |
| { "DmemRegion", S_P_STRING }, /* dmem cgroup region of Gres device */ | |
| { "File", S_P_STRING }, /* Path to Gres device */ | |
| { "Files", S_P_STRING }, /* Path to Gres device */ | |
| { "Flags", S_P_STRING }, /* GRES Flags */ | |
| { "Link", S_P_STRING }, /* Communication link IDs */ | |
| { "Links", S_P_STRING }, /* Communication link IDs */ | |
| { "MultipleFiles", S_P_STRING }, /* list of GRES device files */ | |
| { "Name", S_P_STRING }, /* Gres name */ | |
| { "Type", S_P_STRING }, /* Gres type (e.g. model name) */ | |
| { "UUID", S_P_STRING }, /* Device UUID/unique identifier */ | |
| { NULL } | |
| }; | |
| /* Gres symbols provided by the plugin */ | |
| typedef struct slurm_gres_ops { | |
| int (*node_config_load) ( list_t *gres_conf_list, | |
| node_config_load_t *node_conf); | |
| void (*job_set_env) ( char ***job_env_ptr, | |
| bitstr_t *gres_bit_alloc, | |
| uint64_t gres_cnt, | |
| uint64_t *gres_per_bit, | |
| gres_internal_flags_t flags); | |
| void (*step_set_env) ( char ***step_env_ptr, | |
| bitstr_t *gres_bit_alloc, | |
| uint64_t gres_cnt, | |
| uint64_t *gres_per_bit, | |
| gres_internal_flags_t flags); | |
| void (*task_set_env) ( char ***task_env_ptr, | |
| bitstr_t *gres_bit_alloc, | |
| uint64_t gres_cnt, | |
| uint64_t *gres_per_bit, | |
| bitstr_t *usable_gres, | |
| gres_internal_flags_t flags); | |
| void (*send_stepd) ( buf_t *buffer ); | |
| void (*recv_stepd) ( buf_t *buffer ); | |
| list_t *(*get_devices)(void); | |
| list_t *(*get_dmem_devices)(void); | |
| void (*step_hardware_init) ( bitstr_t *, char * ); | |
| void (*step_hardware_fini) ( void ); | |
| gres_prep_t *(*prep_build_env)(gres_job_state_t *gres_js); | |
| void (*prep_set_env) ( char ***prep_env_ptr, | |
| gres_prep_t *gres_prep, | |
| int node_inx ); | |
| } slurm_gres_ops_t; | |
| /* | |
| * Gres plugin context, one for each gres type. | |
| * Add to gres_context through _add_gres_context(). | |
| */ | |
| typedef struct slurm_gres_context { | |
| plugin_handle_t cur_plugin; | |
| uint32_t config_flags; /* See GRES_CONF_* in gres.h */ | |
| char * gres_name; /* name (e.g. "gpu") */ | |
| char * gres_name_colon; /* name + colon (e.g. "gpu:") */ | |
| int gres_name_colon_len; /* size of gres_name_colon */ | |
| char * gres_type; /* plugin name (e.g. "gres/gpu") */ | |
| list_t *np_gres_devices; /* list of devices when we don't have a plugin */ | |
| slurm_gres_ops_t ops; /* pointers to plugin symbols */ | |
| uint32_t plugin_id; /* key for searches */ | |
| plugrack_t *plugin_list; /* plugrack info */ | |
| uint64_t total_cnt; /* Total GRES across all nodes */ | |
| } slurm_gres_context_t; | |
| typedef struct { | |
| uint32_t plugin_id; | |
| bool with_type; | |
| bool without_type; | |
| void *without_type_state; /* gres_[job|step]_state_t */ | |
| } overlap_check_t; | |
| typedef struct { | |
| slurm_gres_context_t *gres_ctx; | |
| int new_has_file; | |
| int new_has_type; | |
| int rec_count; | |
| } foreach_gres_conf_t; | |
| typedef struct { | |
| bitstr_t **gres_bit_alloc; | |
| uint64_t gres_cnt; | |
| uint64_t **gres_per_bit; | |
| bool is_job; | |
| int node_inx; | |
| uint32_t plugin_id; | |
| bool sharing_gres_allocated; | |
| } foreach_gres_accumulate_device_t; | |
| typedef struct { | |
| node_config_load_t *config; | |
| list_t **gres_devices; | |
| int index; | |
| int max_dev_num; | |
| list_t *names_list; | |
| int rc; | |
| } foreach_fill_in_gres_devices_t; | |
| typedef struct { | |
| int node_inx; | |
| char ***prep_env_ptr; | |
| } foreach_prep_set_env_t; | |
| typedef struct { | |
| uint32_t core_cnt; | |
| int core_end_bit; | |
| int core_start_bit; | |
| uint32_t job_id; | |
| list_t *node_gres_list; | |
| char *node_name; | |
| bool use_total_gres; | |
| } foreach_job_test_t; | |
| typedef struct { | |
| void *data; | |
| enum gres_step_data_type data_type; | |
| uint32_t node_inx; | |
| uint32_t plugin_id; | |
| int rc; | |
| } foreach_step_info_t; | |
| typedef struct { | |
| char *gres_str; | |
| char *sep; | |
| int sock_inx; | |
| } foreach_sock_str_t; | |
| typedef struct { | |
| list_t *device_list; | |
| bitstr_t *gres_bit_alloc; | |
| bitstr_t *usable_gres; | |
| } foreach_alloc_gres_device_t; | |
| typedef struct { | |
| bool filter_type; | |
| uint64_t gres_cnt; | |
| char *gres_type; | |
| bool is_job; | |
| uint32_t plugin_id; | |
| } foreach_gres_list_cnt_t; | |
| typedef struct { | |
| int job_node_index; | |
| list_t *new_gres_list; | |
| } foreach_state_list_dup_t; | |
| typedef struct { | |
| int bitmap_size; | |
| int gres_inx; | |
| uint32_t plugin_id; | |
| bitstr_t *task_cpus_bitmap; | |
| bitstr_t *usable_gres; | |
| } foreach_closest_usable_gres_t; | |
| typedef struct { | |
| int best_slot; | |
| int gres_inx; | |
| bitstr_t *gres_slots; | |
| int ntasks_per_gres; | |
| bool overlap; | |
| uint32_t plugin_id; | |
| bitstr_t *task_cpus_bitmap; | |
| } foreach_gres_to_task_t; | |
| typedef struct { | |
| int array_len; | |
| uint32_t *gres_count_ids; | |
| uint64_t *gres_count_vals; | |
| int index; | |
| int val_type; | |
| } foreach_node_count_t; | |
| typedef struct { | |
| const char *node_name; | |
| char **note; | |
| } foreach_note_absent_t; | |
| typedef struct { | |
| char *max_drained_tres; | |
| bool over_limit; | |
| char **reason; | |
| } foreach_drain_over_limit_t; | |
| typedef struct { | |
| bool check_only; /* Set needed rather than rewrite the bitmaps */ | |
| bool needed; | |
| bitstr_t *node_bitmap; | |
| } foreach_slurmd_indexes_t; | |
| /* Pointers to functions in src/slurmd/common/xcpuinfo.h that we may use */ | |
| typedef struct xcpuinfo_funcs { | |
| int (*xcpuinfo_abs_to_mac) (char *abs, char **mac); | |
| } xcpuinfo_funcs_t; | |
| xcpuinfo_funcs_t xcpuinfo_ops; | |
| typedef struct { | |
| uint32_t flags; | |
| uint32_t name_hash; | |
| bool no_gpu_env; | |
| } prev_gres_flags_t; | |
| typedef struct { | |
| uint32_t config_flags; | |
| int config_type_cnt; | |
| bool has_topo_info; | |
| uint64_t gres_cnt; | |
| uint32_t plugin_id; | |
| uint32_t rec_cnt; | |
| uint64_t topo_cnt; | |
| } tot_from_slurmd_conf_t; | |
| typedef struct { | |
| uint64_t *dev_cnt; /* Registered devices of each chunk domain's Type */ | |
| gres_node_state_t *gres_ns; | |
| uint32_t plugin_id; | |
| } chunk_changed_args_t; | |
| /* The chunk configuration of one Type, collected from its records */ | |
| typedef struct { | |
| bitstr_t *devs; /* Devices of the Type, NULL if it sets no ChunkSizes */ | |
| char *order; /* Borrowed from the Type's first record */ | |
| char *sizes; /* Borrowed from the Type's first record */ | |
| uint32_t type_id; | |
| } chunk_build_t; | |
| /* Track the end of the string, since the order list can run to 1024 entries */ | |
| typedef struct { | |
| char *chunks; | |
| char *pos; | |
| } node_chunks_t; | |
| typedef struct { | |
| int chunk_build_cnt; | |
| chunk_build_t *chunk_builds; /* One per Type with devices */ | |
| bool chunk_mismatch; /* Set if the records of a Type disagree */ | |
| int core_cnt; | |
| int cores_per_sock; | |
| bool cpu_config_err; | |
| int cpus_config; | |
| uint64_t dev_cnt; | |
| slurm_gres_context_t *gres_ctx; | |
| gres_node_state_t *gres_ns; | |
| int gres_inx; | |
| int topo_cnt; | |
| bool has_file; | |
| char *node_name; | |
| int rc; | |
| char **reason_down; | |
| int sock_cnt; | |
| uint64_t tot_gres_cnt; | |
| } rebuild_topo_t; | |
| typedef struct { | |
| bool changed; | |
| gres_node_state_t *gres_ns; | |
| int inx; | |
| uint32_t plugin_id; | |
| } topo_type_cmp_t; | |
| typedef struct { | |
| slurm_gres_context_t *gres_ctx; | |
| gres_node_state_t *gres_ns; | |
| } add_gres_info_t; | |
| typedef struct { | |
| uint64_t count; | |
| slurm_gres_context_t *gres_ctx; | |
| char *type_name; | |
| } conf_cnt_t; | |
| typedef struct { | |
| list_t *gres_conf_list; | |
| slurm_gres_context_t *gres_ctx; | |
| } check_conf_t; | |
| typedef struct { | |
| uint64_t cpu_cnt; | |
| list_t *gres_conf_list; | |
| slurm_gres_context_t *gres_ctx; | |
| list_t *new_list; | |
| } merge_gres_t; | |
| typedef struct { | |
| void *generic_gres_data; | |
| bool is_job; | |
| uint32_t plugin_id; | |
| } merge_generic_t; | |
| typedef struct { | |
| uint32_t cpus_per_gres; | |
| gres_job_state_validate_t *gres_js_val; | |
| bool have_gres_shared; | |
| bool have_gres_sharing; | |
| bool is_job; | |
| bool overlap_merge; | |
| int over_count; | |
| overlap_check_t *over_array; | |
| int rc; | |
| uint32_t tmp_min_cpus; | |
| } job_validate_t; | |
| typedef struct { | |
| uint32_t job_id; | |
| list_t *node_gres_list; | |
| int node_inx; | |
| char *node_name; | |
| } validate_job_gres_cnt_t; | |
| typedef struct { | |
| int job_node_index; | |
| list_t *new_list; | |
| } job_state_extract_t; | |
| typedef struct { | |
| buf_t *buffer; | |
| bool details; | |
| uint32_t magic; | |
| uint16_t protocol_version; | |
| } pack_state_t; | |
| typedef struct { | |
| gres_slurmd_conf_t *dup; | |
| list_t *seen; | |
| } find_dup_unique_id_args_t; | |
| typedef struct { | |
| int index; | |
| char *name; | |
| } gres_type_order_t; | |
| /* UUIDs slurmd reported for one GRES, in that report's order. */ | |
| typedef struct { | |
| bool bad; | |
| int cap; | |
| int n; | |
| uint32_t plugin_id; | |
| char **uuid; | |
| } uuid_rep_t; | |
| /* Local variables */ | |
| static int gres_context_cnt = -1; | |
| static uint32_t gres_cpu_cnt = 0; | |
| static slurm_gres_context_t *gres_context = NULL; | |
| static char *gres_node_name = NULL; | |
| static char *local_plugins_str = NULL; | |
| static pthread_mutex_t gres_context_lock = PTHREAD_MUTEX_INITIALIZER; | |
| static list_t *gres_conf_list = NULL; | |
| static uint32_t gpu_plugin_id = NO_VAL; | |
| static volatile uint32_t autodetect_flags = GRES_AUTODETECT_UNSET; | |
| static bool gres_conf_parsed_early = false; | |
| static buf_t *gres_context_buf = NULL; | |
| static buf_t *gres_conf_buf = NULL; | |
| static bool reset_prev = true; | |
| static bool use_local_index = false; | |
| static bool dev_index_mode_set = false; | |
| /* Local functions */ | |
| static void _accumulate_job_gres_alloc(gres_job_state_t *gres_js, int node_inx, | |
| bitstr_t **gres_bit_alloc, | |
| uint64_t *gres_cnt, | |
| uint64_t **gres_per_bit); | |
| static void _accumulate_step_gres_alloc(gres_state_t *gres_state_step, | |
| bitstr_t **gres_bit_alloc, | |
| uint64_t *gres_cnt, | |
| uint64_t **gres_per_bit); | |
| static void _add_drained_gres_recs(gres_state_t *gres_state_node, list_t *recs); | |
| static void _add_gres_context(char *gres_name); | |
| static gres_node_state_t *_build_gres_node_state(void); | |
| static void _build_node_gres_str(list_t **gres_list, char **gres_str, | |
| int cores_per_sock, int sock_per_node); | |
| static bitstr_t *_core_bitmap_rebuild(bitstr_t *old_core_bitmap, int new_size); | |
| static void _prep_list_del(void *x); | |
| static void _get_gres_cnt(gres_node_state_t *gres_ns, char *orig_config, | |
| char *gres_name, char *gres_name_colon, | |
| int gres_name_colon_len); | |
| static void _gres_bit_alloc_resize(gres_node_state_t *gres_ns, | |
| uint64_t gres_bits); | |
| static uint64_t _get_job_gres_list_cnt(list_t *gres_list, char *gres_name, | |
| char *gres_type); | |
| static int _find_node_state_by_name(void *x, void *arg); | |
| static void _gres_drain_clear_bit(gres_node_state_t *gres_ns, int bit); | |
| static void _gres_drain_cnt_clamp(gres_node_state_t *gres_ns); | |
| static void * _job_state_dup2(gres_job_state_t *gres_js, int job_node_index); | |
| static int _load_plugin(slurm_gres_context_t *gres_ctx); | |
| static int _log_gres_slurmd_conf(void *x, void *arg); | |
| static void _my_stat(char *file_name); | |
| static void _node_config_init(char *orig_config, | |
| slurm_gres_context_t *gres_ctx, | |
| gres_state_t *gres_state_node); | |
| static char * _node_gres_used(gres_node_state_t *gres_ns, char *gres_name); | |
| static int _node_reconfig(char *node_name, char *new_gres, char **gres_str, | |
| gres_state_t *gres_state_node, | |
| bool config_overrides, | |
| slurm_gres_context_t *gres_ctx, | |
| bool *updated_gpu_cnt); | |
| static int _node_reconfig_test(char *node_name, char *new_gres, | |
| gres_state_t *gres_state_node, | |
| slurm_gres_context_t *gres_ctx); | |
| static void * _node_state_dup(gres_node_state_t *gres_ns); | |
| static int _parse_gres_config(void **dest, slurm_parser_enum_t type, | |
| const char *key, const char *value, | |
| const char *line, char **leftover); | |
| static int _parse_gres_config_node(void **dest, slurm_parser_enum_t type, | |
| const char *key, const char *value, | |
| const char *line, char **leftover); | |
| static int _post_plugin_gres_conf(void *x, void *arg); | |
| static void * _step_state_dup(gres_step_state_t *gres_ss); | |
| static void * _step_state_dup2(gres_step_state_t *gres_ss, | |
| int job_node_index); | |
| static bool _shared_topo_bit_drained(gres_node_state_t *gres_ns, int topo_inx); | |
| static int _unload_plugin(slurm_gres_context_t *gres_ctx); | |
| static void _validate_slurm_conf(list_t *slurm_conf_list, | |
| slurm_gres_context_t *gres_ctx); | |
| static void _validate_gres_conf(list_t *gres_conf_list, | |
| slurm_gres_context_t *gres_ctx); | |
| static int _validate_file(char *path_name, char *gres_name); | |
| static int _valid_gres_type(char *gres_name, gres_node_state_t *gres_ns, | |
| bool config_overrides, char **reason_down); | |
| static void _parse_accel_bind_type(uint16_t accel_bind_type, | |
| char *tres_bind_str); | |
| static int _get_usable_gres(int context_inx, int proc_id, | |
| char *tres_bind_str, bitstr_t **usable_gres_ptr, | |
| bitstr_t *gres_bit_alloc, bool get_devices, | |
| stepd_step_rec_t *step, uint64_t *gres_per_bit, | |
| gres_internal_flags_t *flags); | |
| extern uint32_t gres_build_id(char *name) | |
| { | |
| int i, j; | |
| uint32_t id = 0; | |
| if (!name) | |
| return id; | |
| for (i = 0, j = 0; name[i]; i++) { | |
| id += (name[i] << j); | |
| j = (j + 8) % 32; | |
| } | |
| return id; | |
| } | |
| extern int gres_find_id(void *x, void *key) | |
| { | |
| uint32_t *plugin_id = (uint32_t *)key; | |
| gres_state_t *state_ptr = (gres_state_t *) x; | |
| if (state_ptr->plugin_id == *plugin_id) | |
| return 1; | |
| return 0; | |
| } | |
| extern int gres_find_gpu_or_alt(void *x, void *key) | |
| { | |
| gres_state_t *state_ptr = (gres_state_t *) x; | |
| /* Currently all shared gres are gpu alt gres */ | |
| if ((state_ptr->plugin_id == gpu_plugin_id) || | |
| state_ptr->config_flags & GRES_CONF_SHARED) | |
| return 1; | |
| return 0; | |
| } | |
| extern int gres_find_flags(void *x, void *key) | |
| { | |
| gres_state_t *state_ptr = x; | |
| uint32_t flags = *(uint32_t *)key; | |
| if (state_ptr->config_flags & flags) | |
| return 1; | |
| return 0; | |
| } | |
| /* Find job record with matching name and type */ | |
| extern int gres_find_job_by_key_exact_type(void *x, void *key) | |
| { | |
| gres_state_t *gres_state_job = (gres_state_t *) x; | |
| gres_key_t *job_key = (gres_key_t *) key; | |
| gres_job_state_t *gres_js; | |
| gres_js = (gres_job_state_t *)gres_state_job->gres_data; | |
| if ((gres_state_job->plugin_id == job_key->plugin_id) && | |
| (gres_js->type_id == job_key->type_id)) | |
| return 1; | |
| return 0; | |
| } | |
| /* Find job record with matching name and type */ | |
| extern int gres_find_job_by_key(void *x, void *key) | |
| { | |
| gres_state_t *gres_state_job = (gres_state_t *) x; | |
| gres_key_t *job_key = (gres_key_t *) key; | |
| gres_job_state_t *gres_js; | |
| gres_js = (gres_job_state_t *)gres_state_job->gres_data; | |
| if ((gres_state_job->plugin_id == job_key->plugin_id) && | |
| ((job_key->type_id == NO_VAL) || | |
| (gres_js->type_id == job_key->type_id))) | |
| return 1; | |
| return 0; | |
| } | |
| /* Find job record with matching name and type */ | |
| extern int gres_find_job_by_key_with_cnt(void *x, void *key) | |
| { | |
| gres_state_t *gres_state_job = (gres_state_t *) x; | |
| gres_key_t *job_key = (gres_key_t *) key; | |
| gres_job_state_t *gres_js; | |
| gres_js = (gres_job_state_t *)gres_state_job->gres_data; | |
| if (!gres_find_job_by_key(x, key)) | |
| return 0; | |
| /* This gres has been allocated on this node */ | |
| if (!gres_js->node_cnt || | |
| ((job_key->node_offset < gres_js->node_cnt) && | |
| gres_js->gres_cnt_node_alloc[job_key->node_offset])) | |
| return 1; | |
| return 0; | |
| } | |
| extern int gres_find_step_by_key(void *x, void *key) | |
| { | |
| gres_state_t *state_ptr = (gres_state_t *) x; | |
| gres_key_t *step_key = (gres_key_t *) key; | |
| gres_step_state_t *gres_ss = (gres_step_state_t *)state_ptr->gres_data; | |
| if ((state_ptr->plugin_id == step_key->plugin_id) && | |
| (gres_ss->type_id == step_key->type_id)) | |
| return 1; | |
| return 0; | |
| } | |
| extern bool gres_use_local_device_index(void) | |
| { | |
| bool use_cgroup = false; | |
| if (dev_index_mode_set) | |
| return use_local_index; | |
| dev_index_mode_set = true; | |
| if (!slurm_conf.task_plugin) | |
| return use_local_index; | |
| if (xstrstr(slurm_conf.task_plugin, "cgroup")) | |
| use_cgroup = true; | |
| if (!use_cgroup) | |
| return use_local_index; | |
| cgroup_conf_init(); | |
| if (slurm_cgroup_conf.constrain_devices) | |
| use_local_index = true; | |
| return use_local_index; | |
| } | |
| extern gres_state_t *gres_create_state(void *src_ptr, | |
| gres_state_src_t state_src, | |
| gres_state_type_enum_t state_type, | |
| void *gres_data) | |
| { | |
| gres_state_t *new_gres_state = xmalloc(sizeof(gres_state_t)); | |
| new_gres_state->gres_data = gres_data; | |
| new_gres_state->state_type = state_type; | |
| switch (state_src) { | |
| case GRES_STATE_SRC_STATE_PTR: | |
| { | |
| gres_state_t *gres_state = src_ptr; | |
| new_gres_state->config_flags = gres_state->config_flags; | |
| new_gres_state->plugin_id = gres_state->plugin_id; | |
| new_gres_state->gres_name = xstrdup(gres_state->gres_name); | |
| break; | |
| } | |
| case GRES_STATE_SRC_CONTEXT_PTR: | |
| { | |
| slurm_gres_context_t *gres_ctx = src_ptr; | |
| new_gres_state->config_flags = gres_ctx->config_flags; | |
| new_gres_state->plugin_id = gres_ctx->plugin_id; | |
| new_gres_state->gres_name = xstrdup(gres_ctx->gres_name); | |
| break; | |
| } | |
| case GRES_STATE_SRC_KEY_PTR: | |
| { | |
| gres_key_t *search_key = src_ptr; | |
| new_gres_state->config_flags = search_key->config_flags; | |
| new_gres_state->plugin_id = search_key->plugin_id; | |
| /* | |
| * gres_name should be handled after this since search_key | |
| * doesn't have that | |
| */ | |
| break; | |
| } | |
| default: | |
| error("%s: No way to create gres_state given", __func__); | |
| xfree(new_gres_state); | |
| break; | |
| } | |
| return new_gres_state; | |
| } | |
| /* | |
| * Find a gres_context by plugin_id | |
| * Must hold gres_context_lock before calling. | |
| */ | |
| static slurm_gres_context_t *_find_context_by_id(uint32_t plugin_id) | |
| { | |
| for (int j = 0; j < gres_context_cnt; j++) | |
| if (gres_context[j].plugin_id == plugin_id) | |
| return &gres_context[j]; | |
| return NULL; | |
| } | |
| static int _load_plugin(slurm_gres_context_t *gres_ctx) | |
| { | |
| /* | |
| * Must be synchronized with slurm_gres_ops_t above. | |
| */ | |
| static const char *syms[] = { | |
| "gres_p_node_config_load", | |
| "gres_p_job_set_env", | |
| "gres_p_step_set_env", | |
| "gres_p_task_set_env", | |
| "gres_p_send_stepd", | |
| "gres_p_recv_stepd", | |
| "gres_p_get_devices", | |
| "gres_p_get_dmem_devices", | |
| "gres_p_step_hardware_init", | |
| "gres_p_step_hardware_fini", | |
| "gres_p_prep_build_env", | |
| "gres_p_prep_set_env" | |
| }; | |
| int n_syms = sizeof(syms) / sizeof(char *); | |
| /* Find the correct plugin */ | |
| if (gres_ctx->config_flags & GRES_CONF_COUNT_ONLY) { | |
| debug("Plugin of type %s only tracks gres counts", | |
| gres_ctx->gres_type); | |
| return SLURM_SUCCESS; | |
| } | |
| gres_ctx->cur_plugin = plugin_load_and_link( | |
| gres_ctx->gres_type, | |
| n_syms, syms, | |
| (void **) &gres_ctx->ops); | |
| if (gres_ctx->cur_plugin != PLUGIN_INVALID_HANDLE) | |
| return SLURM_SUCCESS; | |
| if (errno != ESLURM_PLUGIN_NOTFOUND) { | |
| error("Couldn't load specified plugin name for %s: %s", | |
| gres_ctx->gres_type, slurm_strerror(errno)); | |
| return SLURM_ERROR; | |
| } | |
| debug("gres: Couldn't find the specified plugin name for %s looking " | |
| "at all files", gres_ctx->gres_type); | |
| /* Get plugin list */ | |
| if (gres_ctx->plugin_list == NULL) { | |
| gres_ctx->plugin_list = plugrack_create("gres"); | |
| plugrack_read_dir(gres_ctx->plugin_list, | |
| slurm_conf.plugindir); | |
| } | |
| gres_ctx->cur_plugin = plugrack_use_by_type( | |
| gres_ctx->plugin_list, | |
| gres_ctx->gres_type ); | |
| if (gres_ctx->cur_plugin == PLUGIN_INVALID_HANDLE) { | |
| debug("Cannot find plugin of type %s, just track gres counts", | |
| gres_ctx->gres_type); | |
| gres_ctx->config_flags |= GRES_CONF_COUNT_ONLY; | |
| return SLURM_ERROR; | |
| } | |
| /* Dereference the API. */ | |
| if (plugin_get_syms(gres_ctx->cur_plugin, | |
| n_syms, syms, | |
| (void **) &gres_ctx->ops ) < n_syms ) { | |
| error("Incomplete %s plugin detected", | |
| gres_ctx->gres_type); | |
| return SLURM_ERROR; | |
| } | |
| return SLURM_SUCCESS; | |
| } | |
| static int _unload_plugin(slurm_gres_context_t *gres_ctx) | |
| { | |
| int rc; | |
| /* | |
| * Must check return code here because plugins might still | |
| * be loaded and active. | |
| */ | |
| if (gres_ctx->plugin_list) | |
| rc = plugrack_destroy(gres_ctx->plugin_list); | |
| else { | |
| rc = SLURM_SUCCESS; | |
| plugin_unload(gres_ctx->cur_plugin); | |
| } | |
| xfree(gres_ctx->gres_name); | |
| xfree(gres_ctx->gres_name_colon); | |
| xfree(gres_ctx->gres_type); | |
| FREE_NULL_LIST(gres_ctx->np_gres_devices); | |
| return rc; | |
| } | |
| extern bool gres_is_shared_name(char *name) | |
| { | |
| if (!xstrcmp(name, "mps") || | |
| !xstrcmp(name, "shard")) | |
| return true; | |
| return false; | |
| } | |
| static void _set_shared_flag(char *name, uint32_t *config_flags) | |
| { | |
| if (gres_is_shared_name(name)) | |
| *config_flags |= GRES_CONF_SHARED; | |
| } | |
| /* | |
| * Add new gres context to gres_context array and load the plugin. | |
| * Must hold gres_context_lock before calling. | |
| */ | |
| static void _add_gres_context(char *gres_name) | |
| { | |
| slurm_gres_context_t *gres_ctx; | |
| if (!gres_name || !gres_name[0]) | |
| fatal("%s: invalid empty gres_name", __func__); | |
| xrecalloc(gres_context, (gres_context_cnt + 1), | |
| sizeof(slurm_gres_context_t)); | |
| gres_ctx = &gres_context[gres_context_cnt]; | |
| _set_shared_flag(gres_name, &gres_ctx->config_flags); | |
| gres_ctx->gres_name = xstrdup(gres_name); | |
| gres_ctx->plugin_id = gres_build_id(gres_name); | |
| gres_ctx->gres_type = xstrdup_printf("gres/%s", gres_name); | |
| gres_ctx->plugin_list = NULL; | |
| gres_ctx->cur_plugin = PLUGIN_INVALID_HANDLE; | |
| gres_context_cnt++; | |
| } | |
| /* | |
| * Initialize the GRES plugins. | |
| * | |
| * Returns a Slurm errno. | |
| */ | |
| extern int gres_init(void) | |
| { | |
| int i, j, rc = SLURM_SUCCESS; | |
| char *last = NULL, *names, *one_name, *full_name; | |
| char *sorted_names = NULL, *sep = "", *shared_names = NULL; | |
| bool have_gpu = false, have_shared = false; | |
| char *shared_sep = ""; | |
| slurm_mutex_lock(&gres_context_lock); | |
| if (gres_context_cnt >= 0) | |
| goto fini; | |
| local_plugins_str = xstrdup(slurm_conf.gres_plugins); | |
| gres_context_cnt = 0; | |
| if ((local_plugins_str == NULL) || (local_plugins_str[0] == '\0')) | |
| goto fini; | |
| /* Ensure that "gres/'shared'" follows "gres/gpu" */ | |
| have_gpu = false; | |
| have_shared = false; | |
| names = xstrdup(local_plugins_str); | |
| one_name = strtok_r(names, ",", &last); | |
| while (one_name) { | |
| bool skip_name = false; | |
| if (gres_is_shared_name(one_name)) { | |
| have_shared = true; | |
| if (!have_gpu) { | |
| /* "shared" must follow "gpu" */ | |
| skip_name = true; | |
| xstrfmtcat(shared_names, "%s%s", | |
| shared_sep, one_name); | |
| shared_sep = ","; | |
| } | |
| } else if (!xstrcmp(one_name, "gpu")) { | |
| have_gpu = true; | |
| gpu_plugin_id = gres_build_id("gpu"); | |
| } | |
| if (!skip_name) { | |
| xstrfmtcat(sorted_names, "%s%s", sep, one_name); | |
| sep = ","; | |
| } | |
| one_name = strtok_r(NULL, ",", &last); | |
| } | |
| if (shared_names) { | |
| if (!have_gpu) | |
| fatal("GresTypes: gres/'shared' requires that gres/gpu also be configured"); | |
| xstrfmtcat(sorted_names, "%s%s", sep, shared_names); | |
| xfree(shared_names); | |
| } | |
| xfree(names); | |
| gres_context_cnt = 0; | |
| one_name = strtok_r(sorted_names, ",", &last); | |
| while (one_name) { | |
| full_name = xstrdup("gres/"); | |
| xstrcat(full_name, one_name); | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| if (!xstrcmp(full_name, gres_context[i].gres_type)) | |
| break; | |
| } | |
| xfree(full_name); | |
| if (i < gres_context_cnt) { | |
| error("Duplicate plugin %s ignored", | |
| gres_context[i].gres_type); | |
| } else { | |
| _add_gres_context(one_name); | |
| } | |
| one_name = strtok_r(NULL, ",", &last); | |
| } | |
| xfree(sorted_names); | |
| /* Ensure that plugin_id is valid and unique */ | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| for (j = i + 1; j < gres_context_cnt; j++) { | |
| if (gres_context[i].plugin_id != | |
| gres_context[j].plugin_id) | |
| continue; | |
| fatal("Gres: Duplicate plugin_id %u for %s and %s, " | |
| "change gres name for one of them", | |
| gres_context[i].plugin_id, | |
| gres_context[i].gres_type, | |
| gres_context[j].gres_type); | |
| } | |
| xassert(gres_context[i].gres_name); | |
| gres_context[i].gres_name_colon = | |
| xstrdup_printf("%s:", gres_context[i].gres_name); | |
| gres_context[i].gres_name_colon_len = | |
| strlen(gres_context[i].gres_name_colon); | |
| } | |
| fini: | |
| if (have_shared && running_in_slurmctld() && !running_cons_tres()) { | |
| fatal("Use of shared gres requires the use of select/cons_tres"); | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return rc; | |
| } | |
| extern int gres_get_gres_cnt(void) | |
| { | |
| static int cnt = -1; | |
| if (cnt != -1) | |
| return cnt; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| cnt = gres_context_cnt; | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return cnt; | |
| } | |
| /* | |
| * Add a GRES record. This is used by the node_features plugin after the | |
| * slurm.conf file is read and the initial GRES records are built by | |
| * gres_init(). | |
| */ | |
| extern void gres_add(char *gres_name) | |
| { | |
| int i; | |
| slurm_mutex_lock(&gres_context_lock); | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| if (!xstrcmp(gres_context[i].gres_name, gres_name)) | |
| goto fini; | |
| } | |
| _add_gres_context(gres_name); | |
| fini: slurm_mutex_unlock(&gres_context_lock); | |
| } | |
| /* Given a gres_name, return its context index or -1 if not found */ | |
| static int _gres_name_context(char *gres_name) | |
| { | |
| int i; | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| if (!xstrcmp(gres_context[i].gres_name, gres_name)) | |
| return i; | |
| } | |
| return -1; | |
| } | |
| /* | |
| * Takes a GRES config line (typically from slurm.conf) and remove any | |
| * records for GRES which are not defined in GresTypes. | |
| * RET string of valid GRES, Release memory using xfree() | |
| */ | |
| extern char *gres_name_filter(char *orig_gres, char *nodes) | |
| { | |
| char *new_gres = NULL, *save_ptr = NULL; | |
| char *colon, *sep = "", *tmp, *tok, *name; | |
| slurm_mutex_lock(&gres_context_lock); | |
| if (!orig_gres || !orig_gres[0] || !gres_context_cnt) { | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return new_gres; | |
| } | |
| tmp = xstrdup(orig_gres); | |
| tok = strtok_r(tmp, ",", &save_ptr); | |
| while (tok) { | |
| name = xstrdup(tok); | |
| if ((colon = strchr(name, ':'))) | |
| colon[0] = '\0'; | |
| if (_gres_name_context(name) != -1) { | |
| xstrfmtcat(new_gres, "%s%s", sep, tok); | |
| sep = ","; | |
| } else { | |
| /* Logging may not be initialized at this point */ | |
| error("Invalid GRES configured on node %s: %s", nodes, | |
| tok); | |
| } | |
| xfree(name); | |
| tok = strtok_r(NULL, ",", &save_ptr); | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| xfree(tmp); | |
| return new_gres; | |
| } | |
| /* | |
| * Terminate the gres plugin. Free memory. | |
| * | |
| * Returns a Slurm errno. | |
| */ | |
| extern int gres_fini(void) | |
| { | |
| int i, j, rc = SLURM_SUCCESS; | |
| slurm_mutex_lock(&gres_context_lock); | |
| xfree(gres_node_name); | |
| if (gres_context_cnt < 0) | |
| goto fini; | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| j = _unload_plugin(gres_context + i); | |
| if (j != SLURM_SUCCESS) | |
| rc = j; | |
| } | |
| xfree(gres_context); | |
| xfree(local_plugins_str); | |
| FREE_NULL_LIST(gres_conf_list); | |
| FREE_NULL_BUFFER(gres_context_buf); | |
| FREE_NULL_BUFFER(gres_conf_buf); | |
| gres_context_cnt = -1; | |
| fini: slurm_mutex_unlock(&gres_context_lock); | |
| return rc; | |
| } | |
| /* | |
| * ************************************************************************ | |
| * P L U G I N C A L L S * | |
| * ************************************************************************ | |
| */ | |
| /* | |
| * Return a plugin-specific help message for salloc, sbatch and srun | |
| * Result must be xfree()'d. | |
| * | |
| * NOTE: GRES "type" (e.g. model) information is only available from slurmctld | |
| * after slurmd registers. It is not readily available from srun (as used here). | |
| */ | |
| extern char *gres_help_msg(void) | |
| { | |
| int i; | |
| char *msg = xstrdup("Valid gres options are:\n"); | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| xstrcat(msg, gres_context[i].gres_name); | |
| xstrcat(msg, "[[:type]:count]\n"); | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return msg; | |
| } | |
| /* | |
| * Perform reconfig, re-read any configuration files | |
| * OUT did_change - set if gres configuration changed | |
| */ | |
| extern int gres_reconfig(void) | |
| { | |
| int rc = SLURM_SUCCESS; | |
| bool plugin_change; | |
| slurm_mutex_lock(&gres_context_lock); | |
| if (xstrcmp(slurm_conf.gres_plugins, local_plugins_str)) | |
| plugin_change = true; | |
| else | |
| plugin_change = false; | |
| reset_prev = true; | |
| /* Reset the flags so when the node checks in we believe that */ | |
| for (int i = 0; i < gres_context_cnt; i++) | |
| gres_context[i].config_flags |= GRES_CONF_FROM_STATE; | |
| slurm_mutex_unlock(&gres_context_lock); | |
| if (plugin_change) { | |
| error("GresPlugins changed from %s to %s ignored", | |
| local_plugins_str, slurm_conf.gres_plugins); | |
| error("Restart the slurmctld daemon to change GresPlugins"); | |
| #if 0 | |
| /* This logic would load new plugins, but we need the old | |
| * plugins to persist in order to process old state | |
| * information. */ | |
| rc = gres_fini(); | |
| if (rc == SLURM_SUCCESS) | |
| rc = gres_init(); | |
| #endif | |
| } | |
| return rc; | |
| } | |
| /* Return 1 if a gres_conf record is the correct plugin_id and has no file */ | |
| static int _find_fileless_gres(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = (gres_slurmd_conf_t *)x; | |
| uint32_t plugin_id = *(uint32_t *)arg; | |
| if ((gres_slurmd_conf->plugin_id == plugin_id) && | |
| !gres_slurmd_conf->file) { | |
| warning("Ignoring file-less GPU %s:%s from final GRES list", | |
| gres_slurmd_conf->name, gres_slurmd_conf->type_name); | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Log the contents of a gres_slurmd_conf_t record | |
| */ | |
| static int _log_gres_slurmd_conf(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *p; | |
| int index = -1, offset, mult = 1; | |
| p = (gres_slurmd_conf_t *) x; | |
| xassert(p); | |
| if (!(slurm_conf.debug_flags & DEBUG_FLAG_GRES)) { | |
| verbose("Gres Name=%s Type=%s Count=%"PRIu64" Flags=%s", | |
| p->name, p->type_name, p->count, | |
| gres_flags2str(p->config_flags)); | |
| return 0; | |
| } | |
| if (p->file) { | |
| index = 0; | |
| offset = strlen(p->file); | |
| while (offset > 0) { | |
| offset--; | |
| if ((p->file[offset] < '0') || (p->file[offset] > '9')) | |
| break; | |
| index += (p->file[offset] - '0') * mult; | |
| mult *= 10; | |
| } | |
| } | |
| if (p->cpus && (index != -1)) { | |
| info("Gres Name=%s Type=%s Count=%"PRIu64" Index=%d ID=%u File=%s Cores=%s CoreCnt=%u Links=%s Flags=%s%s%s", | |
| p->name, | |
| p->type_name, | |
| p->count, | |
| index, | |
| p->plugin_id, | |
| p->file, | |
| p->cpus, | |
| p->cpu_cnt, | |
| p->links, | |
| gres_flags2str(p->config_flags), | |
| p->unique_id ? " UUID=" : "", | |
| p->unique_id ? p->unique_id : ""); | |
| } else if (index != -1) { | |
| info("Gres Name=%s Type=%s Count=%"PRIu64" Index=%d ID=%u File=%s Links=%s Flags=%s%s%s", | |
| p->name, | |
| p->type_name, | |
| p->count, | |
| index, | |
| p->plugin_id, | |
| p->file, | |
| p->links, | |
| gres_flags2str(p->config_flags), | |
| p->unique_id ? " UUID=" : "", | |
| p->unique_id ? p->unique_id : ""); | |
| } else if (p->file) { | |
| info("Gres Name=%s Type=%s Count=%"PRIu64" ID=%u File=%s Links=%s Flags=%s%s%s", | |
| p->name, | |
| p->type_name, | |
| p->count, | |
| p->plugin_id, | |
| p->file, | |
| p->links, | |
| gres_flags2str(p->config_flags), | |
| p->unique_id ? " UUID=" : "", | |
| p->unique_id ? p->unique_id : ""); | |
| } else { | |
| info("Gres Name=%s Type=%s Count=%"PRIu64" ID=%u Links=%s Flags=%s", | |
| p->name, | |
| p->type_name, | |
| p->count, | |
| p->plugin_id, | |
| p->links, | |
| gres_flags2str(p->config_flags)); | |
| } | |
| if (p->chunk_sizes) | |
| info("Gres Name=%s Type=%s ChunkSizes=%s ChunkOrder=%s", | |
| p->name, | |
| p->type_name, | |
| p->chunk_sizes, | |
| p->chunk_order ? p->chunk_order : "(identity)"); | |
| return 0; | |
| } | |
| static int _post_plugin_gres_conf(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x; | |
| slurm_gres_context_t *gres_ctx = arg; | |
| if (gres_slurmd_conf->plugin_id != gres_ctx->plugin_id) | |
| return 0; | |
| if (gres_slurmd_conf->config_flags & GRES_CONF_UUID) { | |
| if (!gres_slurmd_conf->unique_id) { | |
| warning("Flags=env_uuid set but no GPU UUID available for device %s. Falling back to numeric indices. Use AutoDetect or set UUID in gres.conf to provide UUIDs.", | |
| gres_slurmd_conf->file ? gres_slurmd_conf->file : | |
| "(unknown)"); | |
| gres_slurmd_conf->config_flags &= ~GRES_CONF_UUID; | |
| } else | |
| gres_ctx->config_flags |= GRES_CONF_UUID; | |
| } | |
| return 1; | |
| } | |
| /* Make sure that specified file name exists, wait up to 20 seconds or generate | |
| * fatal error and exit. */ | |
| static void _my_stat(char *file_name) | |
| { | |
| struct stat config_stat; | |
| bool sent_msg = false; | |
| int i; | |
| if (!running_in_slurmd_stepd()) | |
| return; | |
| for (i = 0; i < 20; i++) { | |
| if (i) | |
| sleep(1); | |
| if (stat(file_name, &config_stat) == 0) { | |
| if (sent_msg) | |
| info("gres.conf file %s now exists", file_name); | |
| return; | |
| } | |
| if (errno != ENOENT) | |
| break; | |
| if (!sent_msg) { | |
| error("Waiting for gres.conf file %s", file_name); | |
| sent_msg = true; | |
| } | |
| } | |
| fatal("can't stat gres.conf file %s: %m", file_name); | |
| return; | |
| } | |
| static int _validate_file(char *filenames, char *gres_name) | |
| { | |
| char *one_name; | |
| hostlist_t *hl; | |
| int file_count = 0; | |
| if (!(hl = hostlist_create(filenames))) | |
| fatal("can't parse File=%s", filenames); | |
| while ((one_name = hostlist_shift(hl))) { | |
| _my_stat(one_name); | |
| file_count++; | |
| xfree(one_name); | |
| } | |
| hostlist_destroy(hl); | |
| return file_count; | |
| } | |
| /* | |
| * Create and return a comma-separated zeroed-out links string with a -1 in the | |
| * given GPU position indicated by index. Caller must xfree() the returned | |
| * string. | |
| * | |
| * Used to record the enumeration order (PCI bus ID order) of GPUs for sorting, | |
| * even when the GPU does not support nvlinks. E.g. for three total GPUs, their | |
| * links strings would look like this: | |
| * | |
| * GPU at index 0: -1,0,0 | |
| * GPU at index 1: 0,-1,0 | |
| * GPU at index 2: -0,0,-1 | |
| */ | |
| extern char *gres_links_create_empty(unsigned int index, | |
| unsigned int device_count) | |
| { | |
| char *links_str = NULL; | |
| for (unsigned int i = 0; i < device_count; ++i) { | |
| xstrfmtcat(links_str, "%s%d", | |
| i ? "," : "", | |
| (i == index) ? -1 : 0); | |
| } | |
| return links_str; | |
| } | |
| /* | |
| * Check that we have a comma-delimited list of numbers, and return the index of | |
| * the GPU (-1) in the links string. | |
| * | |
| * Returns a non-zero-based index of the GPU in the links string, if found. | |
| * If not found, returns a negative value. | |
| * Return values: | |
| * 0+: GPU index | |
| * -1: links string is NULL. | |
| * -2: links string is not NULL, but is invalid. Possible invalid reasons: | |
| * * error parsing the comma-delimited links string | |
| * * links string is an empty string | |
| * * the 'self' GPU identifier isn't found (i.e. no -1) | |
| * * there is more than one 'self' GPU identifier found | |
| */ | |
| extern int gres_links_validate(char *links) | |
| { | |
| char *tmp, *tok, *save_ptr = NULL, *end_ptr = NULL; | |
| long int val; | |
| int rc; | |
| int i; | |
| if (!links) | |
| return -1; | |
| if (links[0] == '\0') { | |
| error("%s: Links is an empty string", __func__); | |
| return -2; | |
| } | |
| tmp = xstrdup(links); | |
| tok = strtok_r(tmp, ",", &save_ptr); | |
| rc = -1; | |
| i = 0; | |
| while (tok) { | |
| val = strtol(tok, &end_ptr, 10); | |
| if ((val < -2) || (val > GRES_MAX_LINK) || (val == LONG_MIN) || | |
| (end_ptr[0] != '\0')) { | |
| error("%s: Failed to parse token '%s' in links string '%s'", | |
| __func__, tok, links); | |
| rc = -2; | |
| break; | |
| } | |
| if (val == -1) { | |
| if (rc != -1) { | |
| error("%s: links string '%s' has more than one -1", | |
| __func__, links); | |
| rc = -2; | |
| break; | |
| } | |
| rc = i; | |
| } | |
| i++; | |
| tok = strtok_r(NULL, ",", &save_ptr); | |
| } | |
| xfree(tmp); | |
| /* If the current GPU (-1) wasn't found, that's an error */ | |
| if (rc == -1) { | |
| error("%s: -1 wasn't found in links string '%s'", __func__, | |
| links); | |
| rc = -2; | |
| } | |
| return rc; | |
| } | |
| /* Count comma-delimited entries in str, empty entries included */ | |
| static int _comma_entry_cnt(char *str) | |
| { | |
| int cnt = 1; | |
| for (int i = 0; str[i]; i++) { | |
| if (str[i] == ',') | |
| cnt++; | |
| } | |
| return cnt; | |
| } | |
| extern int gres_chunk_sizes_parse(char *chunk_sizes, uint32_t **sizes, | |
| int *sizes_cnt) | |
| { | |
| char *tmp = NULL, *tok = NULL, *save_ptr = NULL, *end_ptr = NULL; | |
| uint32_t *tmp_sizes = NULL, prev = 0; | |
| int cnt = 0, i = 0, rc = SLURM_SUCCESS; | |
| if (sizes) | |
| *sizes = NULL; | |
| if (sizes_cnt) | |
| *sizes_cnt = 0; | |
| if (!chunk_sizes || !chunk_sizes[0]) { | |
| error("%s: ChunkSizes is an empty string", __func__); | |
| return SLURM_ERROR; | |
| } | |
| cnt = _comma_entry_cnt(chunk_sizes); | |
| if (cnt > GRES_MAX_CHUNK_SIZE) { | |
| error("%s: ChunkSizes string '%s' has too many entries", | |
| __func__, chunk_sizes); | |
| return SLURM_ERROR; | |
| } | |
| tmp_sizes = xcalloc(cnt, sizeof(uint32_t)); | |
| tmp = xstrdup(chunk_sizes); | |
| tok = strtok_r(tmp, ",", &save_ptr); | |
| for (i = 0; tok; i++) { | |
| long int val = strtol(tok, &end_ptr, 10); | |
| if ((val <= 0) || (val > GRES_MAX_CHUNK_SIZE) || end_ptr[0]) { | |
| error("%s: Failed to parse token '%s' in ChunkSizes string '%s'", | |
| __func__, tok, chunk_sizes); | |
| rc = SLURM_ERROR; | |
| break; | |
| } | |
| if (val <= (long int) prev) { | |
| error("%s: ChunkSizes string '%s' is not strictly ascending", | |
| __func__, chunk_sizes); | |
| rc = SLURM_ERROR; | |
| break; | |
| } | |
| if (prev && (val % (long int) prev)) { | |
| error("%s: ChunkSizes value %ld in '%s' is not a multiple of %u", | |
| __func__, val, chunk_sizes, prev); | |
| rc = SLURM_ERROR; | |
| break; | |
| } | |
| prev = val; | |
| tmp_sizes[i] = val; | |
| tok = strtok_r(NULL, ",", &save_ptr); | |
| } | |
| xfree(tmp); | |
| if (!rc && (i != cnt)) { | |
| error("%s: ChunkSizes string '%s' has an empty entry", | |
| __func__, chunk_sizes); | |
| rc = SLURM_ERROR; | |
| } | |
| if (rc || !sizes) { | |
| xfree(tmp_sizes); | |
| return rc; | |
| } | |
| *sizes = tmp_sizes; | |
| if (sizes_cnt) | |
| *sizes_cnt = cnt; | |
| return rc; | |
| } | |
| extern int gres_chunk_order_parse(char *chunk_order, int **order, | |
| int *order_cnt) | |
| { | |
| char *tmp = NULL, *tok = NULL, *save_ptr = NULL, *end_ptr = NULL; | |
| int *tmp_order = NULL; | |
| bitstr_t *seen = NULL; | |
| int cnt = 0, i = 0, rc = SLURM_SUCCESS; | |
| if (order) | |
| *order = NULL; | |
| if (order_cnt) | |
| *order_cnt = 0; | |
| if (!chunk_order || !chunk_order[0]) { | |
| error("%s: ChunkOrder is an empty string", __func__); | |
| return SLURM_ERROR; | |
| } | |
| cnt = _comma_entry_cnt(chunk_order); | |
| if (cnt > GRES_MAX_CHUNK_SIZE) { | |
| error("%s: ChunkOrder string '%s' has too many entries", | |
| __func__, chunk_order); | |
| return SLURM_ERROR; | |
| } | |
| tmp_order = xcalloc(cnt, sizeof(int)); | |
| seen = bit_alloc(cnt); | |
| tmp = xstrdup(chunk_order); | |
| tok = strtok_r(tmp, ",", &save_ptr); | |
| for (i = 0; tok; i++) { | |
| long int val = strtol(tok, &end_ptr, 10); | |
| if ((val < 0) || (val >= cnt) || end_ptr[0]) { | |
| error("%s: ChunkOrder value '%s' in '%s' is not in the range 0-%d", | |
| __func__, tok, chunk_order, (cnt - 1)); | |
| rc = SLURM_ERROR; | |
| break; | |
| } | |
| if (bit_test(seen, val)) { | |
| error("%s: ChunkOrder string '%s' has a duplicate value %ld", | |
| __func__, chunk_order, val); | |
| rc = SLURM_ERROR; | |
| break; | |
| } | |
| bit_set(seen, val); | |
| tmp_order[i] = val; | |
| tok = strtok_r(NULL, ",", &save_ptr); | |
| } | |
| xfree(tmp); | |
| FREE_NULL_BITMAP(seen); | |
| /* Values were distinct and in range, so a full list is a permutation */ | |
| if (!rc && (i != cnt)) { | |
| error("%s: ChunkOrder string '%s' has an empty entry", | |
| __func__, chunk_order); | |
| rc = SLURM_ERROR; | |
| } | |
| if (rc || !order) { | |
| xfree(tmp_order); | |
| return rc; | |
| } | |
| *order = tmp_order; | |
| if (order_cnt) | |
| *order_cnt = cnt; | |
| return rc; | |
| } | |
| static char *_get_autodetect_flags_str(void) | |
| { | |
| char *flags = NULL; | |
| if (!(autodetect_flags & | |
| (GRES_AUTODETECT_GPU_FLAGS | GRES_AUTODETECT_TPU))) | |
| xstrfmtcat(flags, "%sunset", flags ? "," : ""); | |
| else { | |
| if (autodetect_flags & GRES_AUTODETECT_GPU_FULL) | |
| xstrfmtcat(flags, "%sfull", flags ? "," : ""); | |
| else if (autodetect_flags & GRES_AUTODETECT_GPU_NVML) | |
| xstrfmtcat(flags, "%snvml", flags ? "," : ""); | |
| else if (autodetect_flags & GRES_AUTODETECT_GPU_RSMI) | |
| xstrfmtcat(flags, "%srsmi", flags ? "," : ""); | |
| else if (autodetect_flags & GRES_AUTODETECT_GPU_AMD) | |
| xstrfmtcat(flags, "%samd", flags ? "," : ""); | |
| else if (autodetect_flags & GRES_AUTODETECT_GPU_ONEAPI) | |
| xstrfmtcat(flags, "%soneapi", flags ? "," : ""); | |
| else if (autodetect_flags & GRES_AUTODETECT_GPU_NRT) | |
| xstrfmtcat(flags, "%snrt", flags ? "," : ""); | |
| else if (autodetect_flags & GRES_AUTODETECT_GPU_NVIDIA) | |
| xstrfmtcat(flags, "%snvidia", flags ? "," : ""); | |
| else if (autodetect_flags & GRES_AUTODETECT_GPU_OFF) | |
| xstrfmtcat(flags, "%soff", flags ? "," : ""); | |
| if (autodetect_flags & GRES_AUTODETECT_TPU) | |
| xstrfmtcat(flags, "%stpu", flags ? "," : ""); | |
| } | |
| return flags; | |
| } | |
| static uint32_t _handle_autodetect_flags(char *str) | |
| { | |
| uint32_t flags = 0; | |
| /* Set the node-local gpus value of autodetect_flags */ | |
| if (xstrcasestr(str, "full")) | |
| flags |= GRES_AUTODETECT_GPU_FULL; | |
| else if (xstrcasestr(str, "nvml")) | |
| flags |= GRES_AUTODETECT_GPU_NVML; | |
| else if (xstrcasestr(str, "rsmi")) | |
| flags |= GRES_AUTODETECT_GPU_RSMI; | |
| else if (xstrcasestr(str, "amd")) | |
| flags |= GRES_AUTODETECT_GPU_AMD; | |
| else if (xstrcasestr(str, "oneapi")) | |
| flags |= GRES_AUTODETECT_GPU_ONEAPI; | |
| else if (xstrcasestr(str, "nrt")) | |
| flags |= GRES_AUTODETECT_GPU_NRT; | |
| else if (xstrcasestr(str, "nvidia")) | |
| flags |= GRES_AUTODETECT_GPU_NVIDIA; | |
| else if (xstrcasestr(str, "tpu")) | |
| flags |= GRES_AUTODETECT_TPU; | |
| else if (!xstrcasecmp(str, "off")) | |
| flags |= GRES_AUTODETECT_GPU_OFF; | |
| else | |
| error("unknown autodetect flag '%s'", str); | |
| return flags; | |
| } | |
| static void _handle_local_autodetect(char *str) | |
| { | |
| uint32_t autodetect_flags_local = _handle_autodetect_flags(str); | |
| /* Only set autodetect_flags once locally, unless it's the same val */ | |
| if ((autodetect_flags != GRES_AUTODETECT_UNSET) && | |
| (autodetect_flags != autodetect_flags_local)) { | |
| fatal("gres.conf: duplicate node-local AutoDetect specification does not match the first"); | |
| return; | |
| } | |
| /* Set the node-local gpus value of autodetect_flags */ | |
| autodetect_flags |= autodetect_flags_local; | |
| if (slurm_conf.debug_flags & DEBUG_FLAG_GRES) { | |
| char *flags = _get_autodetect_flags_str(); | |
| log_flag(GRES, "Using node-local AutoDetect=%s(%d)", | |
| flags, autodetect_flags); | |
| xfree(flags); | |
| } | |
| } | |
| static void _handle_global_autodetect(char *str) | |
| { | |
| /* If GPU or TPU flags exist, node-local value was already specified */ | |
| if (autodetect_flags & | |
| (GRES_AUTODETECT_GPU_FLAGS | GRES_AUTODETECT_TPU)) | |
| debug2("gres.conf: AutoDetect flags were locally set, so ignoring global flags"); | |
| else | |
| autodetect_flags |= _handle_autodetect_flags(str); | |
| if (slurm_conf.debug_flags & DEBUG_FLAG_GRES) { | |
| char *flags = _get_autodetect_flags_str(); | |
| log_flag(GRES, "Global AutoDetect=%s(%d)", | |
| flags, autodetect_flags); | |
| xfree(flags); | |
| } | |
| } | |
| static int _get_match(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf1 = x; | |
| gres_slurmd_conf_t *gres_slurmd_conf2 = arg; | |
| /* We only need to check type name because they should all be gpus */ | |
| if (!gres_slurmd_conf1->type_name && !gres_slurmd_conf2->type_name) | |
| return 1; | |
| if (!gres_slurmd_conf1->type_name || !gres_slurmd_conf2->type_name) | |
| return 0; | |
| if (!xstrcmp(gres_slurmd_conf1->type_name, | |
| gres_slurmd_conf2->type_name)) | |
| return 1; | |
| return 0; | |
| } | |
| static int _merge_by_type(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x, *merged_gres_slurmd_conf; | |
| list_t *gres_list_merged = arg; | |
| merged_gres_slurmd_conf = list_find_first(gres_list_merged, _get_match, | |
| gres_slurmd_conf); | |
| /* We are merging types and don't care about files or links */ | |
| if (merged_gres_slurmd_conf) | |
| merged_gres_slurmd_conf->count++; | |
| else | |
| list_append(gres_list_merged, gres_slurmd_conf); | |
| return SLURM_SUCCESS; | |
| } | |
| static int _match_gres_name(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x; | |
| char *name = arg; | |
| return !xstrcasecmp(gres_slurmd_conf->name, name); | |
| } | |
| static int _slurm_conf_gres_str(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x; | |
| char **gres_str = arg; | |
| if (gres_slurmd_conf && gres_slurmd_conf->name) { | |
| bool has_type = gres_slurmd_conf->type_name && | |
| gres_slurmd_conf->type_name[0]; | |
| xstrfmtcat(*gres_str, "%s%s:%s%s%ld", | |
| gres_str && gres_str[0] ? "," : "", | |
| gres_slurmd_conf->name, | |
| has_type ? gres_slurmd_conf->type_name : "", | |
| has_type ? ":" : "", | |
| gres_slurmd_conf->count); | |
| } | |
| return SLURM_SUCCESS; | |
| } | |
| extern void gres_get_autodetected_gpus(node_config_load_t node_conf, | |
| char **first_gres_str, | |
| char **autodetect_str) | |
| { | |
| list_t *gres_list_system = NULL, *gres_list_merged = NULL; | |
| char *gres_str = NULL; | |
| char *autodetect_option_name = NULL; | |
| int autodetect_options[] = { | |
| GRES_AUTODETECT_GPU_NVML, | |
| GRES_AUTODETECT_GPU_NVIDIA, | |
| GRES_AUTODETECT_GPU_RSMI, | |
| GRES_AUTODETECT_GPU_AMD, | |
| GRES_AUTODETECT_GPU_ONEAPI, | |
| GRES_AUTODETECT_GPU_NRT, | |
| GRES_AUTODETECT_UNSET /* For loop is done */ | |
| }; | |
| for (int i = 0; autodetect_options[i] != GRES_AUTODETECT_UNSET; i++) { | |
| autodetect_flags = autodetect_options[i]; | |
| if (gpu_plugin_init(&node_conf) != SLURM_SUCCESS) | |
| continue; | |
| gres_list_system = gpu_g_get_system_gpu_list(&node_conf); | |
| if (gres_list_system) { | |
| gres_list_merged = list_create(NULL); | |
| list_for_each(gres_list_system, _merge_by_type, | |
| gres_list_merged); | |
| list_for_each(gres_list_merged, _slurm_conf_gres_str, | |
| &gres_str); | |
| } | |
| FREE_NULL_LIST(gres_list_merged); | |
| FREE_NULL_LIST(gres_list_system); | |
| gpu_plugin_fini(); | |
| if (!gres_str) | |
| continue; | |
| if (autodetect_flags == GRES_AUTODETECT_GPU_NVML) | |
| i++; /* Skip NVIDIA if NVML finds gpus */ | |
| autodetect_option_name = _get_autodetect_flags_str(); | |
| xstrfmtcat(*autodetect_str, "%sFound %s with Autodetect=%s (Substring of gpu name may be used instead)", | |
| (*autodetect_str ? "\n" : ""), | |
| gres_str, | |
| autodetect_option_name); | |
| xfree(autodetect_option_name); | |
| if (!*first_gres_str){ | |
| *first_gres_str = gres_str; | |
| gres_str = NULL; | |
| } else { | |
| xfree(gres_str); | |
| } | |
| } | |
| } | |
| extern char *gres_get_dynamic_gpu_str(node_config_load_t node_conf) | |
| { | |
| list_t *gres_list_system = NULL, *gres_list_merged = NULL; | |
| char *gres_str = NULL; | |
| uint32_t saved_gpu_flags; | |
| xassert(slurm_conf.plugindir); | |
| /* Honor Autodetect=off in gres.conf. */ | |
| if (autodetect_flags & GRES_AUTODETECT_GPU_OFF) | |
| return NULL; | |
| if (gres_conf_list && | |
| list_find_first_ro(gres_conf_list, _match_gres_name, "gpu")) | |
| return NULL; | |
| saved_gpu_flags = autodetect_flags & GRES_AUTODETECT_GPU_FLAGS; | |
| /* Default to FULL when gres.conf didn't pin a plugin. */ | |
| if (!saved_gpu_flags) | |
| gres_autodetect_flags_set_gpu(GRES_AUTODETECT_GPU_FULL); | |
| if (gpu_plugin_init(&node_conf) != SLURM_SUCCESS) { | |
| gres_autodetect_flags_set_gpu(saved_gpu_flags); | |
| return NULL; | |
| } | |
| gres_list_system = gpu_g_get_system_gpu_list(&node_conf); | |
| if (gres_list_system) { | |
| gres_list_merged = list_create(NULL); | |
| list_for_each(gres_list_system, _merge_by_type, | |
| gres_list_merged); | |
| list_for_each(gres_list_merged, _slurm_conf_gres_str, | |
| &gres_str); | |
| FREE_NULL_LIST(gres_list_merged); | |
| FREE_NULL_LIST(gres_list_system); | |
| } | |
| gres_autodetect_flags_set_gpu(saved_gpu_flags); | |
| return gres_str; | |
| } | |
| /* | |
| * Check to see if current GRES record matches the name of the previous GRES | |
| * record that set env flags. | |
| */ | |
| static bool _same_gres_name_as_prev(prev_gres_flags_t *prev_gres, | |
| gres_slurmd_conf_t *p) | |
| { | |
| if ((gres_build_id(p->name) == prev_gres->name_hash)) | |
| return true; | |
| else | |
| return false; | |
| } | |
| /* | |
| * Save off env flags, GRES name, and no_gpu_env (for the next gres.conf line to | |
| * possibly inherit or to check against). | |
| */ | |
| static void _set_prev_gres_flags(prev_gres_flags_t *prev_gres, | |
| gres_slurmd_conf_t *p, uint32_t env_flags, | |
| bool no_gpu_env) | |
| { | |
| prev_gres->flags = env_flags; | |
| prev_gres->name_hash = gres_build_id(p->name); | |
| prev_gres->no_gpu_env = no_gpu_env; | |
| } | |
| /* | |
| * Parse a gres.conf Flags string | |
| */ | |
| extern uint32_t gres_flags_parse(char *input, bool *no_gpu_env, | |
| bool *sharing_mentioned) | |
| { | |
| uint32_t flags = 0; | |
| if (xstrcasestr(input, "CountOnly")) | |
| flags |= GRES_CONF_COUNT_ONLY; | |
| if (xstrcasestr(input, "nvidia_gpu_env")) | |
| flags |= GRES_CONF_ENV_NVML; | |
| if (xstrcasestr(input, "amd_gpu_env")) | |
| flags |= GRES_CONF_ENV_RSMI; | |
| if (xstrcasestr(input, "intel_gpu_env")) | |
| flags |= GRES_CONF_ENV_ONEAPI; | |
| if (xstrcasestr(input, "opencl_env")) | |
| flags |= GRES_CONF_ENV_OPENCL; | |
| if (xstrcasestr(input, "one_sharing")) | |
| flags |= GRES_CONF_ONE_SHARING; | |
| if (xstrcasestr(input, "explicit")) | |
| flags |= GRES_CONF_EXPLICIT; | |
| if (xstrcasestr(input, "env_uuid")) | |
| flags |= GRES_CONF_UUID; | |
| /* String 'no_gpu_env' will clear all GPU env vars */ | |
| if (no_gpu_env) | |
| *no_gpu_env = xstrcasestr(input, "no_gpu_env"); | |
| if (sharing_mentioned) { | |
| if ((flags & GRES_CONF_ONE_SHARING) || | |
| xstrcasestr(input, "all_sharing")) | |
| *sharing_mentioned = true; | |
| } | |
| return flags; | |
| } | |
| /* | |
| * Build gres_slurmd_conf_t record based upon a line from the gres.conf file | |
| */ | |
| static int _parse_gres_config(void **dest, slurm_parser_enum_t type, | |
| const char *key, const char *value, | |
| const char *line, char **leftover) | |
| { | |
| int i; | |
| s_p_hashtbl_t *tbl; | |
| gres_slurmd_conf_t *p; | |
| uint64_t tmp_uint64, mult; | |
| char *tmp_str, *last; | |
| bool cores_flag = false, cpus_flag = false; | |
| int file_dev_cnt = 0; | |
| char *type_str = NULL; | |
| char *autodetect_string = NULL; | |
| bool autodetect = false, set_default_envs = true; | |
| /* Remember the last-set Flags value */ | |
| static prev_gres_flags_t prev_gres = { 0 }; | |
| if (reset_prev) { | |
| memset(&prev_gres, 0, sizeof(prev_gres)); | |
| reset_prev = false; | |
| } | |
| tbl = s_p_hashtbl_create(_gres_options); | |
| s_p_parse_line(tbl, *leftover, leftover); | |
| p = xmalloc(sizeof(gres_slurmd_conf_t)); | |
| /* | |
| * Detect and set the node-local AutoDetect option only if | |
| * NodeName is specified. | |
| */ | |
| if (s_p_get_string(&autodetect_string, "AutoDetect", tbl)) { | |
| if (value) | |
| error("gres.conf: In-line AutoDetect requires NodeName to take effect"); | |
| else { | |
| _handle_local_autodetect(autodetect_string); | |
| /* AutoDetect was specified w/ NodeName */ | |
| autodetect = true; | |
| } | |
| xfree(autodetect_string); | |
| } | |
| if (!value) { | |
| if (!s_p_get_string(&p->name, "Name", tbl)) { | |
| if (!autodetect) | |
| error("Invalid GRES data, no type name (%s)", | |
| line); | |
| xfree(p); | |
| s_p_hashtbl_destroy(tbl); | |
| return 0; | |
| } | |
| } else { | |
| p->name = xstrdup(value); | |
| } | |
| if (s_p_get_string(&p->type_name, "Type", tbl)) { | |
| p->config_flags |= GRES_CONF_HAS_TYPE; | |
| } | |
| p->cpu_cnt = gres_cpu_cnt; | |
| if (s_p_get_string(&p->cpus, "Cores", tbl)) { | |
| cores_flag = true; | |
| type_str = "Cores"; | |
| } else if (s_p_get_string(&p->cpus, "CPUs", tbl)) { | |
| cpus_flag = true; | |
| type_str = "CPUs"; | |
| } | |
| if (cores_flag || cpus_flag) { | |
| char *local_cpus = NULL; | |
| if (xcpuinfo_ops.xcpuinfo_abs_to_mac) { | |
| i = (xcpuinfo_ops.xcpuinfo_abs_to_mac) | |
| (p->cpus, &local_cpus); | |
| if (i != SLURM_SUCCESS) { | |
| error("Invalid GRES data for %s, %s=%s", | |
| p->name, type_str, p->cpus); | |
| } | |
| } else { | |
| /* | |
| * Not converting Cores into machine format is only for | |
| * testing or if we don't care about cpus_bitmap. The | |
| * slurmd should always convert to machine format. | |
| */ | |
| debug("%s: %s=%s is not being converted to machine-local format", | |
| __func__, type_str, p->cpus); | |
| local_cpus = xstrdup(p->cpus); | |
| i = SLURM_SUCCESS; | |
| } | |
| if (i == SLURM_SUCCESS) { | |
| p->cpus_bitmap = bit_alloc(gres_cpu_cnt); | |
| if (!bit_size(p->cpus_bitmap) || | |
| bit_unfmt(p->cpus_bitmap, local_cpus)) { | |
| fatal("Invalid GRES data for %s, %s=%s (only %u CPUs are available)", | |
| p->name, type_str, p->cpus, gres_cpu_cnt); | |
| } | |
| } | |
| xfree(local_cpus); | |
| } | |
| if (s_p_get_string(&p->file, "File", tbl) || | |
| s_p_get_string(&p->file, "Files", tbl)) { | |
| file_dev_cnt = _validate_file(p->file, p->name); | |
| p->count = file_dev_cnt; | |
| p->config_flags |= GRES_CONF_HAS_FILE; | |
| } | |
| if (s_p_get_string(&p->file, "MultipleFiles", tbl)) { | |
| int file_count = 0; | |
| if (p->config_flags & GRES_CONF_HAS_FILE) | |
| fatal("File and MultipleFiles options are mutually exclusive"); | |
| p->count = 1; | |
| /* All of the files together describe a single device */ | |
| file_dev_cnt = 1; | |
| file_count = _validate_file(p->file, p->name); | |
| if (file_count < 2) | |
| fatal("MultipleFiles does not contain multiple files. Use File instead"); | |
| p->config_flags |= GRES_CONF_HAS_FILE; | |
| p->config_flags |= GRES_CONF_HAS_MULT; | |
| } | |
| if (s_p_get_string(&p->dmem_region, "DmemRegion", tbl) && | |
| ((p->count != 1) || (p->config_flags & GRES_CONF_HAS_MULT))) { | |
| fatal("Invalid GRES record for %s, DmemRegion is bound to a single device File", | |
| p->name); | |
| } | |
| if (s_p_get_string(&tmp_str, "Flags", tbl)) { | |
| uint32_t env_flags = 0; | |
| bool no_gpu_env = false; | |
| bool sharing_mentioned = false; | |
| uint32_t flags = gres_flags_parse(tmp_str, &no_gpu_env, | |
| &sharing_mentioned); | |
| /* The default for MPS is to have only one gpu sharing */ | |
| if (!sharing_mentioned && !xstrcasecmp(p->name, "mps")) | |
| flags |= GRES_CONF_ONE_SHARING; | |
| /* Break out flags into env flags and non-env flags */ | |
| env_flags = flags & GRES_CONF_ENV_SET; | |
| p->config_flags |= flags; | |
| if (env_flags && no_gpu_env) | |
| fatal("Invalid GRES record name=%s type=%s: Flags (%s) contains \"no_gpu_env\", which must be mutually exclusive to all other GRES env flags of same node and name", | |
| p->name, p->type_name, tmp_str); | |
| /* | |
| * Only override default env logic when explicit vendor | |
| * env flags or no_gpu_env are present. Pure modifier | |
| * flags like env_uuid (GRES_CONF_UUID) let defaults or | |
| * AutoDetect provide vendor flags. | |
| */ | |
| if (env_flags || no_gpu_env) { | |
| set_default_envs = false; | |
| /* | |
| * Make sure that Flags are consistent with each | |
| * other if set for multiple lines of the same | |
| * GRES. | |
| */ | |
| if (prev_gres.name_hash && | |
| _same_gres_name_as_prev(&prev_gres, p) && | |
| ((prev_gres.flags != flags) || | |
| (prev_gres.no_gpu_env != no_gpu_env))) | |
| fatal("Invalid GRES record name=%s type=%s: Flags (%s) does not match env flags for previous GRES of same node and name", | |
| p->name, p->type_name, tmp_str); | |
| _set_prev_gres_flags(&prev_gres, p, flags, no_gpu_env); | |
| } | |
| xfree(tmp_str); | |
| } else if ((prev_gres.flags || prev_gres.no_gpu_env) && | |
| _same_gres_name_as_prev(&prev_gres, p)) { | |
| /* Inherit flags from previous GRES line with same name */ | |
| set_default_envs = false; | |
| p->config_flags |= prev_gres.flags; | |
| } else { | |
| if (!xstrcasecmp(p->name, "mps")) | |
| p->config_flags |= GRES_CONF_ONE_SHARING; | |
| } | |
| /* Flags not set. By default, all env vars are set for GPUs */ | |
| if (set_default_envs && !xstrcasecmp(p->name, "gpu")) { | |
| uint32_t env_flags = GRES_CONF_ENV_SET | GRES_CONF_ENV_DEF; | |
| /* Preserve modifier flags (e.g. env_uuid) already set */ | |
| env_flags |= p->config_flags & GRES_CONF_UUID; | |
| p->config_flags |= env_flags; | |
| _set_prev_gres_flags(&prev_gres, p, env_flags, false); | |
| } | |
| if (s_p_get_string(&p->links, "Link", tbl) || | |
| s_p_get_string(&p->links, "Links", tbl)) { | |
| if (gres_links_validate(p->links) < -1) { | |
| error("gres.conf: Ignoring invalid Links=%s for Name=%s", | |
| p->links, p->name); | |
| xfree(p->links); | |
| } | |
| } | |
| if (s_p_get_string(&p->unique_id, "UUID", tbl)) { | |
| if (!p->unique_id[0]) | |
| fatal("Invalid GRES record for %s, UUID is empty", | |
| p->name); | |
| /* | |
| * A comma would split the value apart in the vendor env vars | |
| * set by Flags=env_uuid, which are comma-separated lists. | |
| */ | |
| if (xstrchr(p->unique_id, ',')) | |
| fatal("Invalid GRES record for %s, UUID (%s) may not contain a comma", | |
| p->name, p->unique_id); | |
| } | |
| if (s_p_get_string(&p->chunk_sizes, "ChunkSizes", tbl)) { | |
| if (gres_chunk_sizes_parse(p->chunk_sizes, NULL, NULL) != | |
| SLURM_SUCCESS) | |
| fatal("Invalid GRES record for %s, invalid ChunkSizes %s", | |
| p->name, p->chunk_sizes); | |
| } | |
| if (s_p_get_string(&p->chunk_order, "ChunkOrder", tbl)) { | |
| if (!p->chunk_sizes) | |
| fatal("Invalid GRES record for %s, ChunkOrder requires ChunkSizes", | |
| p->name); | |
| if (gres_chunk_order_parse(p->chunk_order, NULL, NULL) != | |
| SLURM_SUCCESS) | |
| fatal("Invalid GRES record for %s, invalid ChunkOrder %s", | |
| p->name, p->chunk_order); | |
| } | |
| if (p->chunk_sizes && !(p->config_flags & GRES_CONF_HAS_FILE)) | |
| fatal("Invalid GRES record for %s, ChunkSizes requires File or MultipleFiles", | |
| p->name); | |
| /* Chunks and links describe the same topology, so chunks win */ | |
| if (p->chunk_sizes && p->links) { | |
| warning("gres.conf: Ignoring Links for Name=%s Type=%s, ChunkSizes takes precedence", | |
| p->name, p->type_name); | |
| xfree(p->links); | |
| } | |
| _set_shared_flag(p->name, &p->config_flags); | |
| if (s_p_get_string(&tmp_str, "Count", tbl)) { | |
| tmp_uint64 = strtoll(tmp_str, &last, 10); | |
| if ((tmp_uint64 == LONG_MIN) || (tmp_uint64 == LONG_MAX)) { | |
| fatal("Invalid GRES record for %s, invalid count %s", | |
| p->name, tmp_str); | |
| } | |
| if ((mult = suffix_mult(last)) != NO_VAL64) { | |
| tmp_uint64 *= mult; | |
| } else { | |
| fatal("Invalid GRES record for %s, invalid count %s", | |
| p->name, tmp_str); | |
| } | |
| /* | |
| * Some GRES can have count > 1 for a given file. For example, | |
| * each GPU can have arbitrary count of MPS elements. | |
| */ | |
| if (p->count && (p->count != tmp_uint64) && | |
| !gres_id_shared(p->config_flags)) { | |
| fatal("Invalid GRES record for %s, count does not match File value", | |
| p->name); | |
| } | |
| if (tmp_uint64 >= NO_VAL64) { | |
| fatal("GRES %s has invalid count value %"PRIu64, | |
| p->name, tmp_uint64); | |
| } | |
| p->count = tmp_uint64; | |
| xfree(tmp_str); | |
| } else if (p->count == 0) | |
| p->count = 1; | |
| /* | |
| * A UUID names one physical device, so the record it is on has to | |
| * resolve to exactly one. MultipleFiles is fine here: its files are all | |
| * part of a single device. A shared GRES (mps/shard) may still have a | |
| * count > 1, since that count is the number of shared TRES units | |
| * carved out of the one device. | |
| */ | |
| if (p->unique_id) { | |
| if (!(p->config_flags & GRES_CONF_HAS_FILE)) | |
| fatal("Invalid GRES record for %s, UUID requires File or MultipleFiles", | |
| p->name); | |
| if (file_dev_cnt > 1) | |
| fatal("Invalid GRES record for %s, UUID requires a single device file, but File=%s has %d", | |
| p->name, p->file, file_dev_cnt); | |
| } | |
| s_p_hashtbl_destroy(tbl); | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| if (xstrcasecmp(p->name, gres_context[i].gres_name) == 0) | |
| break; | |
| } | |
| if (i >= gres_context_cnt) { | |
| error("Ignoring gres.conf record, invalid name: %s", p->name); | |
| destroy_gres_slurmd_conf(p); | |
| return 0; | |
| } | |
| p->plugin_id = gres_context[i].plugin_id; | |
| *dest = (void *)p; | |
| return 1; | |
| } | |
| static int _parse_gres_config_node(void **dest, slurm_parser_enum_t type, | |
| const char *key, const char *value, | |
| const char *line, char **leftover) | |
| { | |
| s_p_hashtbl_t *tbl; | |
| if (gres_node_name && value) { | |
| bool match = false; | |
| hostlist_t *hl; | |
| hl = hostlist_create(value); | |
| if (hl) { | |
| match = (hostlist_find(hl, gres_node_name) >= 0); | |
| hostlist_destroy(hl); | |
| } | |
| if (!match) { | |
| debug("skipping GRES for NodeName=%s %s", value, line); | |
| tbl = s_p_hashtbl_create(_gres_options); | |
| s_p_parse_line(tbl, *leftover, leftover); | |
| s_p_hashtbl_destroy(tbl); | |
| return 0; | |
| } | |
| } | |
| return _parse_gres_config(dest, type, key, NULL, line, leftover); | |
| } | |
| static int _foreach_slurm_conf(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_node = (gres_state_t *)x; | |
| slurm_gres_context_t *gres_ctx = (slurm_gres_context_t *)arg; | |
| gres_node_state_t *gres_ns; | |
| uint64_t tmp_count = 0; | |
| /* Only look at the GRES under the current plugin (same name) */ | |
| if (gres_state_node->plugin_id != gres_ctx->plugin_id) | |
| return 0; | |
| gres_ns = (gres_node_state_t *)gres_state_node->gres_data; | |
| /* | |
| * gres_cnt_config should equal the combined count from | |
| * type_cnt_avail if there are no untyped GRES | |
| */ | |
| for (uint16_t i = 0; i < gres_ns->type_cnt; i++) | |
| tmp_count += gres_ns->type_cnt_avail[i]; | |
| /* Forbid mixing typed and untyped GRES under the same name */ | |
| if (gres_ns->type_cnt && | |
| gres_ns->gres_cnt_config > tmp_count) | |
| fatal("%s: Some %s GRES in slurm.conf have a type while others do not (gres_ns->gres_cnt_config (%"PRIu64") > tmp_count (%"PRIu64"))", | |
| __func__, gres_ctx->gres_name, | |
| gres_ns->gres_cnt_config, tmp_count); | |
| return 1; | |
| } | |
| static void _validate_slurm_conf(list_t *slurm_conf_list, | |
| slurm_gres_context_t *gres_ctx) | |
| { | |
| if (!slurm_conf_list) | |
| return; | |
| (void)list_for_each_nobreak(slurm_conf_list, _foreach_slurm_conf, | |
| gres_ctx); | |
| } | |
| static int _foreach_gres_conf(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = (gres_slurmd_conf_t *)x; | |
| foreach_gres_conf_t *foreach_gres_conf = (foreach_gres_conf_t *)arg; | |
| slurm_gres_context_t *gres_ctx = foreach_gres_conf->gres_ctx; | |
| bool orig_has_file, orig_has_type; | |
| /* Only look at the GRES under the current plugin (same name) */ | |
| if (gres_slurmd_conf->plugin_id != gres_ctx->plugin_id) | |
| return 0; | |
| /* | |
| * If any plugin of this type has this set it will virally set | |
| * any other to be the same as we use the gres_ctx from here | |
| * on out. | |
| */ | |
| if (gres_slurmd_conf->config_flags & GRES_CONF_EXPLICIT) | |
| gres_ctx->config_flags |= GRES_CONF_EXPLICIT; | |
| if (gres_slurmd_conf->config_flags & GRES_CONF_COUNT_ONLY) | |
| gres_ctx->config_flags |= GRES_CONF_COUNT_ONLY; | |
| if (gres_slurmd_conf->config_flags & GRES_CONF_HAS_FILE) | |
| gres_ctx->config_flags |= GRES_CONF_HAS_FILE; | |
| if (gres_slurmd_conf->config_flags & GRES_CONF_ONE_SHARING) | |
| gres_ctx->config_flags |= GRES_CONF_ONE_SHARING; | |
| if (!(gres_slurmd_conf->config_flags & GRES_CONF_ENV_DEF) && | |
| (gres_slurmd_conf->config_flags & | |
| (GRES_CONF_ENV_SET | GRES_CONF_UUID))) | |
| gres_ctx->config_flags |= | |
| (gres_slurmd_conf->config_flags & | |
| (GRES_CONF_ENV_SET | GRES_CONF_UUID)); | |
| /* | |
| * Since there could be multiple types of the same plugin we | |
| * need to only make sure we load it once. | |
| */ | |
| if (!(gres_ctx->config_flags & GRES_CONF_LOADED)) { | |
| /* | |
| * Ignore return code, as we will still support the gres | |
| * with or without the plugin. | |
| */ | |
| if (_load_plugin(gres_ctx) == SLURM_SUCCESS) | |
| gres_ctx->config_flags |= GRES_CONF_LOADED; | |
| } | |
| foreach_gres_conf->rec_count++; | |
| orig_has_file = gres_slurmd_conf->config_flags & GRES_CONF_HAS_FILE; | |
| if (foreach_gres_conf->new_has_file == -1) { | |
| if (gres_slurmd_conf->config_flags & GRES_CONF_HAS_FILE) | |
| foreach_gres_conf->new_has_file = 1; | |
| else | |
| foreach_gres_conf->new_has_file = 0; | |
| } else if ((foreach_gres_conf->new_has_file && !orig_has_file) || | |
| (!foreach_gres_conf->new_has_file && orig_has_file)) { | |
| fatal("gres.conf for %s, some records have \"File\" specification while others do not", | |
| gres_ctx->gres_name); | |
| } | |
| orig_has_type = gres_slurmd_conf->config_flags & | |
| GRES_CONF_HAS_TYPE; | |
| if (foreach_gres_conf->new_has_type == -1) { | |
| if (gres_slurmd_conf->config_flags & | |
| GRES_CONF_HAS_TYPE) { | |
| foreach_gres_conf->new_has_type = 1; | |
| } else | |
| foreach_gres_conf->new_has_type = 0; | |
| } else if ((foreach_gres_conf->new_has_type && !orig_has_type) || | |
| (!foreach_gres_conf->new_has_type && orig_has_type)) { | |
| fatal("gres.conf for %s, some records have \"Type=\" specification while others do not", | |
| gres_ctx->gres_name); | |
| } | |
| if (!foreach_gres_conf->new_has_file && | |
| !foreach_gres_conf->new_has_type && | |
| (foreach_gres_conf->rec_count > 1)) { | |
| fatal("gres.conf duplicate records for %s", | |
| gres_ctx->gres_name); | |
| } | |
| if (foreach_gres_conf->new_has_file) | |
| gres_ctx->config_flags |= GRES_CONF_HAS_FILE; | |
| return 0; | |
| } | |
| static void _validate_gres_conf(list_t *gres_conf_list, | |
| slurm_gres_context_t *gres_ctx) | |
| { | |
| foreach_gres_conf_t gres_conf = { | |
| .gres_ctx = gres_ctx, | |
| .new_has_file = -1, | |
| .new_has_type = -1, | |
| .rec_count = 0, | |
| }; | |
| (void)list_for_each_nobreak(gres_conf_list, _foreach_gres_conf, | |
| &gres_conf); | |
| if (!(gres_ctx->config_flags & GRES_CONF_LOADED)) { | |
| /* | |
| * This means there was no gres.conf line for this gres found. | |
| * We still need to try to load it for AutoDetect's sake. | |
| * If we fail loading we will treat it as a count | |
| * only GRES since the stepd will try to load it elsewise. | |
| */ | |
| if (_load_plugin(gres_ctx) != SLURM_SUCCESS) | |
| gres_ctx->config_flags |= GRES_CONF_COUNT_ONLY; | |
| } else | |
| /* Remove as this is only really used locally */ | |
| gres_ctx->config_flags &= (~GRES_CONF_LOADED); | |
| } | |
| /* | |
| * Keep track of which gres.conf lines have a count greater than expected | |
| * according to the current slurm.conf GRES. Modify the count of | |
| * gres_slurmd_conf to keep track of this. Any gres.conf records | |
| * with a count > 0 means that slurm.conf did not account for it completely. | |
| * | |
| * gres_slurmd_conf - (in/out) pointer to conf we are looking at. | |
| * This should be a temporary copy that we can modify. | |
| * conf_cnt->count - (in) The count of the current slurm.conf GRES record. | |
| * conf_cnt->type_name - (in) The type of the current slurm.conf GRES record. | |
| */ | |
| static int _foreach_compare_conf_counts(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x; | |
| conf_cnt_t *conf_cnt = arg; | |
| /* Note: plugin type filter already applied */ | |
| /* Check that type is the same */ | |
| if (gres_slurmd_conf->type_name && | |
| xstrcasecmp(gres_slurmd_conf->type_name, conf_cnt->type_name)) | |
| return 0; | |
| /* Keep track of counts */ | |
| if (gres_slurmd_conf->count > conf_cnt->count) { | |
| gres_slurmd_conf->count -= conf_cnt->count; | |
| /* This slurm.conf GRES specification is now used up */ | |
| return -1; | |
| } else { | |
| conf_cnt->count -= gres_slurmd_conf->count; | |
| gres_slurmd_conf->count = 0; | |
| } | |
| return 0; | |
| } | |
| static int _lite_copy_gres_slurmd_conf(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x; | |
| check_conf_t *check_conf = arg; | |
| gres_slurmd_conf_t *gres_slurmd_conf_tmp; | |
| if (gres_slurmd_conf->plugin_id != check_conf->gres_ctx->plugin_id) | |
| return 0; | |
| gres_slurmd_conf_tmp = xmalloc(sizeof(*gres_slurmd_conf_tmp)); | |
| gres_slurmd_conf_tmp->name = xstrdup(gres_slurmd_conf->name); | |
| gres_slurmd_conf_tmp->type_name = xstrdup(gres_slurmd_conf->type_name); | |
| gres_slurmd_conf_tmp->count = gres_slurmd_conf->count; | |
| list_append(check_conf->gres_conf_list, gres_slurmd_conf_tmp); | |
| return 0; | |
| } | |
| static int _foreach_slurm_conf_mismatch_comp(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_node = x; | |
| check_conf_t *check_conf = arg; | |
| gres_node_state_t *gres_ns; | |
| conf_cnt_t conf_cnt = { 0 }; | |
| if (gres_state_node->plugin_id != check_conf->gres_ctx->plugin_id) | |
| return 0; | |
| /* Determine if typed or untyped, and act accordingly */ | |
| gres_ns = gres_state_node->gres_data; | |
| if (!gres_ns->type_name) { | |
| conf_cnt.count = gres_ns->gres_cnt_config; | |
| conf_cnt.type_name = NULL; | |
| (void) list_for_each(check_conf->gres_conf_list, | |
| _foreach_compare_conf_counts, | |
| &conf_cnt); | |
| return 0; | |
| } | |
| for (int i = 0; i < gres_ns->type_cnt; ++i) { | |
| conf_cnt.count = gres_ns->type_cnt_avail[i]; | |
| conf_cnt.type_name = gres_ns->type_name[i]; | |
| (void) list_for_each(check_conf->gres_conf_list, | |
| _foreach_compare_conf_counts, | |
| &conf_cnt); | |
| } | |
| return 0; | |
| } | |
| int _print_slurm_conf_mismatch(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x; | |
| if (gres_slurmd_conf->count > 0) | |
| warning("A line in gres.conf for GRES %s%s%s has %"PRIu64" more configured than expected in slurm.conf. Ignoring extra GRES.", | |
| gres_slurmd_conf->name, | |
| (gres_slurmd_conf->type_name) ? ":" : "", | |
| (gres_slurmd_conf->type_name) ? | |
| gres_slurmd_conf->type_name : "", | |
| gres_slurmd_conf->count); | |
| return 0; | |
| } | |
| /* | |
| * Loop through each entry in gres.conf and see if there is a corresponding | |
| * entry in slurm.conf. If so, see if the counts line up. If there are more | |
| * devices specified in gres.conf than in slurm.conf, emit errors. | |
| * | |
| * slurm_conf_list - (in) The slurm.conf GRES list. | |
| * gres_conf_list - (in) The gres.conf GRES list. | |
| * gres_ctx - (in) Which GRES plugin we are currently working in. | |
| */ | |
| static void _check_conf_mismatch(list_t *slurm_conf_list, list_t *gres_conf_list, | |
| slurm_gres_context_t *gres_ctx) | |
| { | |
| check_conf_t check_conf = { | |
| .gres_ctx = gres_ctx, | |
| }; | |
| /* E.g. slurm_conf_list will be NULL in the case of --gpu-bind */ | |
| if (!slurm_conf_list || !gres_conf_list) | |
| return; | |
| /* | |
| * Duplicate the gres.conf list with records relevant to this GRES | |
| * plugin only so we can mangle records. Only add records under the | |
| * current plugin. | |
| */ | |
| check_conf.gres_conf_list = list_create(destroy_gres_slurmd_conf); | |
| (void) list_for_each(gres_conf_list, | |
| _lite_copy_gres_slurmd_conf, | |
| &check_conf); | |
| /* | |
| * Loop through the slurm.conf list and see if there are more gres.conf | |
| * GRES than expected. | |
| */ | |
| (void) list_for_each(slurm_conf_list, | |
| _foreach_slurm_conf_mismatch_comp, | |
| &check_conf); | |
| /* | |
| * Loop through gres_conf_list_tmp to print errors for gres.conf | |
| * records that were not completely accounted for in slurm.conf. | |
| */ | |
| (void) list_for_each(check_conf.gres_conf_list, | |
| _print_slurm_conf_mismatch, | |
| NULL); | |
| FREE_NULL_LIST(check_conf.gres_conf_list); | |
| } | |
| /* | |
| * Match the type of a GRES from slurm.conf to a GRES in the gres.conf list. If | |
| * a match is found, pop it off the gres.conf list and return it. | |
| * | |
| * gres_context - (in) Which GRES plugin we are currently working in. | |
| * type_name - (in) The type of the slurm.conf GRES record. If null, then | |
| * it's an untyped GRES. | |
| * | |
| * Returns the first gres.conf record from gres_conf_list with the same type | |
| * name as the slurm.conf record. | |
| */ | |
| static int _match_type(void *x, void *key) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x; | |
| conf_cnt_t *conf_cnt = key; | |
| if (gres_slurmd_conf->plugin_id != conf_cnt->gres_ctx->plugin_id) | |
| return 0; | |
| /* | |
| * If type_name is NULL we will take the first matching | |
| * gres_slurmd_conf that we find. This means we also will | |
| * remove the type from the gres_slurmd_conf to match 18.08 | |
| * stylings. | |
| */ | |
| if (!conf_cnt->type_name) { | |
| /* | |
| * Don't remove the type of a shared gres without File. | |
| * Support entries like "Name=shard Type=rtx_5060_ti Count=16". | |
| */ | |
| if (!gres_id_shared(gres_slurmd_conf->config_flags) || | |
| gres_slurmd_conf->file) { | |
| xfree(gres_slurmd_conf->type_name); | |
| gres_slurmd_conf->config_flags &= ~GRES_CONF_HAS_TYPE; | |
| } | |
| } else if (xstrcasecmp(gres_slurmd_conf->type_name, | |
| conf_cnt->type_name)) | |
| return 0; | |
| return 1; | |
| } | |
| /* | |
| * Add a GRES conf record with count == 0 to gres_list. | |
| * | |
| * new_list - (in/out) The gres list to add to. | |
| * gres_ctx - (in) The GRES plugin to add a GRES record for. | |
| * count - (in) The cpu count configured for the node. | |
| */ | |
| static void _add_gres_config_empty(merge_gres_t *merge_gres) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = | |
| xmalloc(sizeof(*gres_slurmd_conf)); | |
| gres_slurmd_conf->cpu_cnt = merge_gres->cpu_cnt; | |
| gres_slurmd_conf->name = xstrdup(merge_gres->gres_ctx->gres_name); | |
| gres_slurmd_conf->plugin_id = merge_gres->gres_ctx->plugin_id; | |
| list_append(merge_gres->new_list, gres_slurmd_conf); | |
| } | |
| /* | |
| * Truncate the File hostrange string of a GRES record to be at most | |
| * new_count entries. The extra entries will be removed. | |
| * | |
| * gres_slurmd_conf - (in/out) The GRES record to modify. | |
| * count - (in) The new number of entries in File | |
| */ | |
| static void _set_file_subset(gres_slurmd_conf_t *gres_slurmd_conf, | |
| uint64_t new_count) | |
| { | |
| /* Convert file to hostrange */ | |
| hostlist_t *hl = hostlist_create(gres_slurmd_conf->file); | |
| unsigned long old_count = hostlist_count(hl); | |
| if (new_count >= old_count) { | |
| hostlist_destroy(hl); | |
| /* Nothing to do */ | |
| return; | |
| } | |
| /* Remove all but the first entries */ | |
| for (int i = old_count; i > new_count; --i) { | |
| hostlist_drop(hl); | |
| } | |
| debug3("%s: Truncating %s:%s File from (%ld) %s", __func__, | |
| gres_slurmd_conf->name, gres_slurmd_conf->type_name, old_count, | |
| gres_slurmd_conf->file); | |
| /* Set file to the new subset */ | |
| xfree(gres_slurmd_conf->file); | |
| gres_slurmd_conf->file = hostlist_ranged_string_xmalloc(hl); | |
| debug3("%s: to (%"PRIu64") %s", __func__, new_count, | |
| gres_slurmd_conf->file); | |
| hostlist_destroy(hl); | |
| } | |
| /* | |
| * A continuation of _merge_gres() depending on if the slurm.conf GRES is typed | |
| * or not. | |
| * | |
| * gres_conf_list - (in) The gres.conf list. | |
| * new_list - (out) The new merged [slurm|gres].conf list. | |
| * count - (in) The count of the slurm.conf GRES record. | |
| * type_name - (in) The type of the slurm.conf GRES record, if it exists. | |
| * gres_context - (in) Which GRES plugin we are working in. | |
| * cpu_cnt - (in) A count of CPUs on the node. | |
| */ | |
| static void _merge_gres2(merge_gres_t *merge_gres, | |
| uint64_t count, char *type_name) | |
| { | |
| bool per_dev_shared = false; | |
| gres_slurmd_conf_t *match; | |
| gres_slurmd_conf_t gres_slurmd_conf = { | |
| .config_flags = GRES_CONF_GENERATED, | |
| .cpu_cnt = merge_gres->cpu_cnt, | |
| .name = merge_gres->gres_ctx->gres_name, | |
| .type_name = type_name, | |
| }; | |
| conf_cnt_t conf_cnt = { | |
| .count = count, | |
| .gres_ctx = merge_gres->gres_ctx, | |
| .type_name = type_name, | |
| }; | |
| /* If slurm.conf count is initially 0, don't waste time on it */ | |
| if (count == 0) | |
| return; | |
| /* | |
| * There can be multiple gres.conf GRES lines contained within a | |
| * single slurm.conf GRES line, due to different values of Cores | |
| * and Links. Append them to the list where possible. | |
| */ | |
| while ((match = list_remove_first( | |
| merge_gres->gres_conf_list, _match_type, &conf_cnt))) { | |
| list_append(merge_gres->new_list, match); | |
| debug3("%s: From gres.conf, using %s:%s:%"PRIu64":%s", __func__, | |
| match->name, match->type_name, match->count, | |
| match->file); | |
| /* | |
| * A shared record (mps or shard) without File and with Type, | |
| * applies Count to each device, not to each node. This | |
| * allows support for defining shards/mps for a specific model | |
| * of gpu. Never truncate it. | |
| */ | |
| if (!match->file && match->type_name && | |
| gres_id_shared(match->config_flags)) { | |
| if (type_name) { | |
| count = 0; | |
| break; | |
| } | |
| per_dev_shared = true; | |
| continue; | |
| } | |
| /* | |
| * See if we need to merge with any more gres.conf records. | |
| * NOTE: _set_file_subset() won't run on a MultipleFiles GRES, | |
| * since match->count will always be 1 and count is always >= 1 | |
| */ | |
| if (match->count > count) { | |
| /* | |
| * Truncate excess count of gres.conf to match total | |
| * count of slurm.conf. | |
| */ | |
| match->count = count; | |
| /* | |
| * Truncate excess file of gres.conf to match total | |
| * count of slurm.conf. | |
| */ | |
| if (match->file) | |
| _set_file_subset(match, count); | |
| /* Floor to 0 to break out of loop. */ | |
| count = 0; | |
| } else | |
| /* | |
| * Subtract this gres.conf line count from the | |
| * slurm.conf total. | |
| */ | |
| count -= match->count; | |
| /* | |
| * All devices outlined by this slurm.conf record have now been | |
| * merged with gres.conf records and added to new_list, so exit. | |
| */ | |
| if (count == 0) | |
| break; | |
| } | |
| /* Per-device records absorb the total: no untyped leftover. */ | |
| if (per_dev_shared || !count) | |
| return; | |
| /* | |
| * There are leftover GRES specified in this slurm.conf record that are | |
| * not accounted for in gres.conf that still need to be added. | |
| */ | |
| /* Set default env flags, and allow AutoDetect to override */ | |
| if (!xstrcasecmp(merge_gres->gres_ctx->gres_name, "gpu")) { | |
| if (merge_gres->gres_ctx->config_flags & | |
| (GRES_CONF_ENV_SET | GRES_CONF_UUID)) | |
| gres_slurmd_conf.config_flags |= | |
| merge_gres->gres_ctx->config_flags; | |
| else | |
| gres_slurmd_conf.config_flags |= | |
| (GRES_CONF_ENV_SET | GRES_CONF_ENV_DEF); | |
| } | |
| if (merge_gres->gres_ctx->config_flags & GRES_CONF_COUNT_ONLY) | |
| gres_slurmd_conf.config_flags |= GRES_CONF_COUNT_ONLY; | |
| if (merge_gres->gres_ctx->config_flags & GRES_CONF_EXPLICIT) | |
| gres_slurmd_conf.config_flags |= GRES_CONF_EXPLICIT; | |
| gres_slurmd_conf.count = count; | |
| add_gres_to_list(merge_gres->new_list, &gres_slurmd_conf); | |
| } | |
| /* | |
| * Merge a single slurm.conf GRES specification with any relevant gres.conf | |
| * records and append the result to new_list. | |
| * | |
| * gres_conf_list - (in) The gres.conf list. | |
| * new_list - (out) The new merged [slurm|gres].conf list. | |
| * ptr - (in) A slurm.conf GRES record. | |
| * gres_ctx - (in) Which GRES plugin we are working in. | |
| * cpu_cnt - (in) A count of CPUs on the node. | |
| */ | |
| static int _merge_gres(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_node = x; | |
| merge_gres_t *merge_gres = arg; | |
| gres_node_state_t *gres_ns; | |
| if (gres_state_node->plugin_id != merge_gres->gres_ctx->plugin_id) | |
| return 0; | |
| gres_ns = gres_state_node->gres_data; | |
| /* If this GRES has no types, merge in the single untyped GRES */ | |
| if (gres_ns->type_cnt == 0) { | |
| _merge_gres2(merge_gres, | |
| gres_ns->gres_cnt_config, NULL); | |
| return 0; | |
| } | |
| /* If this GRES has types, merge in each typed GRES */ | |
| for (int i = 0; i < gres_ns->type_cnt; i++) { | |
| _merge_gres2(merge_gres, | |
| gres_ns->type_cnt_avail[i], | |
| gres_ns->type_name[i]); | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Merge slurm.conf and gres.conf GRES configuration. | |
| * gres.conf can only work within what is outlined in slurm.conf. Every | |
| * gres.conf device that does not match up to a device in slurm.conf is | |
| * discarded with an error. If no gres conf found for what is specified in | |
| * slurm.conf, create a zero-count conf record. | |
| * | |
| * node_conf - (in) node configuration info (cpu count). | |
| * gres_conf_list - (in/out) GRES data from gres.conf. This becomes the new | |
| * merged slurm.conf/gres.conf list. | |
| * slurm_conf_list - (in) GRES data from slurm.conf. | |
| */ | |
| static void _merge_config(node_config_load_t *node_conf, list_t *gres_conf_list, | |
| list_t *slurm_conf_list) | |
| { | |
| merge_gres_t merge_gres = { | |
| .cpu_cnt = node_conf->cpu_cnt, | |
| .gres_conf_list = gres_conf_list, | |
| .new_list = list_create(destroy_gres_slurmd_conf), | |
| }; | |
| for (int i = 0; i < gres_context_cnt; i++) { | |
| merge_gres.gres_ctx = &gres_context[i]; | |
| /* Copy GRES configuration from slurm.conf */ | |
| if (slurm_conf_list) { | |
| if (list_for_each(slurm_conf_list, | |
| _merge_gres, | |
| &merge_gres) > 0) | |
| continue; | |
| } | |
| /* Add GRES record with zero count */ | |
| _add_gres_config_empty(&merge_gres); | |
| } | |
| /* Set gres_conf_list to be the new merged list */ | |
| list_flush(gres_conf_list); | |
| list_transfer(gres_conf_list, merge_gres.new_list); | |
| FREE_NULL_LIST(merge_gres.new_list); | |
| } | |
| static void _pack_gres_context(slurm_gres_context_t *gres_ctx, buf_t *buffer) | |
| { | |
| /* gres_ctx->cur_plugin: DON'T PACK will be filled in on the other | |
| * side */ | |
| pack32(gres_ctx->config_flags, buffer); | |
| packstr(gres_ctx->gres_name, buffer); | |
| packstr(gres_ctx->gres_name_colon, buffer); | |
| pack32((uint32_t)gres_ctx->gres_name_colon_len, buffer); | |
| packstr(gres_ctx->gres_type, buffer); | |
| gres_send_stepd(buffer, gres_ctx->np_gres_devices); | |
| /* gres_ctx->ops: DON'T PACK will be filled in on the other side */ | |
| pack32(gres_ctx->plugin_id, buffer); | |
| /* gres_ctx->plugin_list: DON'T PACK will be filled in on the other | |
| * side */ | |
| pack64(gres_ctx->total_cnt, buffer); | |
| } | |
| static int _unpack_gres_context(slurm_gres_context_t *gres_ctx, buf_t *buffer) | |
| { | |
| uint32_t uint32_tmp; | |
| /* Free structs pre-initialized by gres_init() */ | |
| xfree(gres_ctx->gres_name); | |
| xfree(gres_ctx->gres_name_colon); | |
| xfree(gres_ctx->gres_type); | |
| /* gres_ctx->cur_plugin: filled in later with _load_plugin() */ | |
| safe_unpack32(&gres_ctx->config_flags, buffer); | |
| safe_unpackstr(&gres_ctx->gres_name, buffer); | |
| safe_unpackstr(&gres_ctx->gres_name_colon, buffer); | |
| safe_unpack32(&uint32_tmp, buffer); | |
| gres_ctx->gres_name_colon_len = (int)uint32_tmp; | |
| safe_unpackstr(&gres_ctx->gres_type, buffer); | |
| gres_recv_stepd(buffer, &gres_ctx->np_gres_devices); | |
| /* gres_ctx->ops: filled in later with _load_plugin() */ | |
| safe_unpack32(&gres_ctx->plugin_id, buffer); | |
| /* gres_ctx->plugin_list: filled in later with _load_plugin() */ | |
| safe_unpack64(&gres_ctx->total_cnt, buffer); | |
| return SLURM_SUCCESS; | |
| unpack_error: | |
| error("%s: unpack_error", __func__); | |
| return SLURM_ERROR; | |
| } | |
| static void _pack_gres_slurmd_conf(void *in, uint16_t protocol_version, | |
| buf_t *buffer) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = (gres_slurmd_conf_t *)in; | |
| /* | |
| * Ignore protocol_version at the time of writing this only deals with | |
| * communication from the slurmd to a new stepd which should always be | |
| * the same version. This function is called from slurm_pack_list which | |
| * requires protocol_version. | |
| */ | |
| /* Pack gres_slurmd_conf_t */ | |
| pack32(gres_slurmd_conf->config_flags, buffer); | |
| pack64(gres_slurmd_conf->count, buffer); | |
| pack32(gres_slurmd_conf->cpu_cnt, buffer); | |
| packstr(gres_slurmd_conf->cpus, buffer); | |
| pack_bit_str_hex(gres_slurmd_conf->cpus_bitmap, buffer); | |
| packstr(gres_slurmd_conf->file, buffer); | |
| packstr(gres_slurmd_conf->links, buffer); | |
| packstr(gres_slurmd_conf->name, buffer); | |
| packstr(gres_slurmd_conf->type_name, buffer); | |
| packstr(gres_slurmd_conf->unique_id, buffer); | |
| pack32(gres_slurmd_conf->plugin_id, buffer); | |
| } | |
| static int _unpack_gres_slurmd_conf(void **object, uint16_t protocol_version, | |
| buf_t *buffer) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = | |
| xmalloc(sizeof(*gres_slurmd_conf)); | |
| /* | |
| * Ignore protocol_version at the time of writing this only deals with | |
| * communication from the slurmd to a new stepd which should always be | |
| * the same version. This function is called from slurm_unpack_list | |
| * which requires protocol_version. | |
| */ | |
| /* Unpack gres_slurmd_conf_t */ | |
| safe_unpack32(&gres_slurmd_conf->config_flags, buffer); | |
| safe_unpack64(&gres_slurmd_conf->count, buffer); | |
| safe_unpack32(&gres_slurmd_conf->cpu_cnt, buffer); | |
| safe_unpackstr(&gres_slurmd_conf->cpus, buffer); | |
| unpack_bit_str_hex(&gres_slurmd_conf->cpus_bitmap, buffer); | |
| safe_unpackstr(&gres_slurmd_conf->file, buffer); | |
| safe_unpackstr(&gres_slurmd_conf->links, buffer); | |
| safe_unpackstr(&gres_slurmd_conf->name, buffer); | |
| safe_unpackstr(&gres_slurmd_conf->type_name, buffer); | |
| safe_unpackstr(&gres_slurmd_conf->unique_id, buffer); | |
| safe_unpack32(&gres_slurmd_conf->plugin_id, buffer); | |
| *object = gres_slurmd_conf; | |
| return SLURM_SUCCESS; | |
| unpack_error: | |
| destroy_gres_slurmd_conf(gres_slurmd_conf); | |
| *object = NULL; | |
| return SLURM_ERROR; | |
| } | |
| /* gres_context_lock should be locked before this */ | |
| static void _pack_context_buf(void) | |
| { | |
| FREE_NULL_BUFFER(gres_context_buf); | |
| gres_context_buf = init_buf(0); | |
| pack32(gres_context_cnt, gres_context_buf); | |
| if (gres_context_cnt <= 0) { | |
| debug3("%s: No GRES context count sent to stepd", __func__); | |
| return; | |
| } | |
| for (int i = 0; i < gres_context_cnt; i++) { | |
| slurm_gres_context_t *gres_ctx = &gres_context[i]; | |
| _pack_gres_context(gres_ctx, gres_context_buf); | |
| if (gres_ctx->ops.send_stepd) | |
| (*(gres_ctx->ops.send_stepd))(gres_context_buf); | |
| } | |
| } | |
| static int _unpack_context_buf(buf_t *buffer) | |
| { | |
| uint32_t cnt; | |
| safe_unpack32(&cnt, buffer); | |
| gres_context_cnt = cnt; | |
| if (!gres_context_cnt) | |
| return SLURM_SUCCESS; | |
| xrecalloc(gres_context, gres_context_cnt, sizeof(slurm_gres_context_t)); | |
| for (int i = 0; i < gres_context_cnt; i++) { | |
| slurm_gres_context_t *gres_ctx = &gres_context[i]; | |
| if (_unpack_gres_context(gres_ctx, buffer) != SLURM_SUCCESS) | |
| goto unpack_error; | |
| (void)_load_plugin(gres_ctx); | |
| if (gres_ctx->ops.recv_stepd) | |
| (*(gres_ctx->ops.recv_stepd))(buffer); | |
| } | |
| return SLURM_SUCCESS; | |
| unpack_error: | |
| error("%s: failed", __func__); | |
| return SLURM_ERROR; | |
| } | |
| /* gres_context_lock should be locked before this */ | |
| static void _pack_gres_conf(void) | |
| { | |
| int len = 0; | |
| FREE_NULL_BUFFER(gres_conf_buf); | |
| gres_conf_buf = init_buf(0); | |
| pack32(autodetect_flags, gres_conf_buf); | |
| /* If there is no list to send, let the stepd know */ | |
| if (!gres_conf_list || !(len = list_count(gres_conf_list))) { | |
| pack32(len, gres_conf_buf); | |
| return; | |
| } | |
| pack32(len, gres_conf_buf); | |
| if (slurm_pack_list(gres_conf_list, _pack_gres_slurmd_conf, | |
| gres_conf_buf, SLURM_PROTOCOL_VERSION) | |
| != SLURM_SUCCESS) { | |
| error("%s: Failed to pack gres_conf_list", __func__); | |
| return; | |
| } | |
| } | |
| static int _unpack_gres_conf(buf_t *buffer) | |
| { | |
| uint32_t cnt; | |
| safe_unpack32(&cnt, buffer); | |
| autodetect_flags = cnt; | |
| safe_unpack32(&cnt, buffer); | |
| if (!cnt) | |
| return SLURM_SUCCESS; | |
| if (slurm_unpack_list(&gres_conf_list, _unpack_gres_slurmd_conf, | |
| destroy_gres_slurmd_conf, buffer, | |
| SLURM_PROTOCOL_VERSION) != SLURM_SUCCESS) | |
| goto unpack_error; | |
| return SLURM_SUCCESS; | |
| unpack_error: | |
| error("%s: failed", __func__); | |
| return SLURM_ERROR; | |
| } | |
| /* Fills major and minor information for a gres_device_t dev */ | |
| static int _set_gres_device_desc(gres_device_t *dev) | |
| { | |
| struct stat fs; | |
| dev->dev_desc.type = DEV_TYPE_NONE; | |
| dev->dev_desc.major = NO_VAL; | |
| dev->dev_desc.minor = NO_VAL; | |
| if (stat(dev->path, &fs) < 0) { | |
| error("%s: stat(%s): %m", __func__, dev->path); | |
| return SLURM_ERROR; | |
| } | |
| dev->dev_desc.major = major(fs.st_rdev); | |
| dev->dev_desc.minor = minor(fs.st_rdev); | |
| log_flag(GRES, "%s : %s major %d, minor %d", __func__, dev->path, | |
| dev->dev_desc.major, dev->dev_desc.minor); | |
| if (S_ISBLK(fs.st_mode)) | |
| dev->dev_desc.type = DEV_TYPE_BLOCK; | |
| else if (S_ISCHR(fs.st_mode)) | |
| dev->dev_desc.type = DEV_TYPE_CHAR; | |
| else { | |
| error("%s is not a valid character or block device, fix your gres.conf", | |
| dev->path); | |
| return SLURM_ERROR; | |
| } | |
| return SLURM_SUCCESS; | |
| } | |
| /* | |
| * Creates and initializes a gres_device_t from a path, an bitmap index and a | |
| * unique_id. At failure return NULL. | |
| */ | |
| static gres_device_t *_init_gres_device(int index, char *one_name, | |
| char *unique_id) | |
| { | |
| int tmp, digit = -1; | |
| gres_device_t *gres_device = xmalloc(sizeof(gres_device_t)); | |
| gres_device->dev_num = -1; | |
| gres_device->index = index; | |
| gres_device->path = xstrdup(one_name); | |
| gres_device->unique_id = xstrdup(unique_id); | |
| if (_set_gres_device_desc(gres_device) != SLURM_SUCCESS) { | |
| destroy_gres_device(gres_device); | |
| return NULL; | |
| } | |
| tmp = strlen(one_name); | |
| for (int i = 1; i <= tmp; i++) { | |
| if (isdigit(one_name[tmp - i])) { | |
| digit = tmp - i; | |
| continue; | |
| } | |
| break; | |
| } | |
| if (digit >= 0) | |
| gres_device->dev_num = atoi(one_name + digit); | |
| else | |
| gres_device->dev_num = -1; | |
| return gres_device; | |
| } | |
| /* Load the specific GRES plugins here */ | |
| static int _load_specific_gres_plugins(node_config_load_t *node_conf) | |
| { | |
| int rc; | |
| if (running_in_slurmctld()) | |
| return SLURM_SUCCESS; | |
| if ((rc = gpu_plugin_init(node_conf)) != SLURM_SUCCESS) | |
| return rc; | |
| return rc; | |
| } | |
| static int _foreach_fill_in_gres_devices(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x; | |
| foreach_fill_in_gres_devices_t *fill_in_gres_devices = arg; | |
| node_config_load_t *config = fill_in_gres_devices->config; | |
| hostlist_t *hl; | |
| char *one_name; | |
| if (!(gres_slurmd_conf->config_flags & GRES_CONF_HAS_FILE) || | |
| !gres_slurmd_conf->file || | |
| xstrcmp(gres_slurmd_conf->name, config->gres_name)) | |
| return 0; | |
| if (!(hl = hostlist_create(gres_slurmd_conf->file))) { | |
| error("can't parse gres.conf file record (%s)", | |
| gres_slurmd_conf->file); | |
| return 0; | |
| } | |
| while ((one_name = hostlist_shift(hl))) { | |
| /* We don't care about gres_devices in slurmctld */ | |
| if (config->in_slurmd) { | |
| gres_device_t *gres_device; | |
| if (!*fill_in_gres_devices->gres_devices) | |
| *fill_in_gres_devices->gres_devices = | |
| list_create(destroy_gres_device); | |
| /* | |
| * A device that fails to initialize gets no | |
| * gres_device_t but keeps its place in the | |
| * numbering the controller counts: its name | |
| * finishes this iteration like an initialized | |
| * one. A File name is checked for duplicates | |
| * and advances the device index, a MultipleFiles | |
| * record advances it once for all its names. | |
| */ | |
| if ((gres_device = _init_gres_device( | |
| fill_in_gres_devices->index, one_name, | |
| gres_slurmd_conf->unique_id))) { | |
| if (gres_slurmd_conf->cpus_bitmap) | |
| gres_device->cpus_bitmap = | |
| bit_copy(gres_slurmd_conf | |
| ->cpus_bitmap); | |
| if (gres_device->dev_num > | |
| fill_in_gres_devices->max_dev_num) | |
| fill_in_gres_devices->max_dev_num = | |
| gres_device->dev_num; | |
| list_append(*fill_in_gres_devices->gres_devices, | |
| gres_device); | |
| } | |
| } | |
| /* | |
| * Don't check for file duplicates or increment the | |
| * device bitmap index if this is a MultipleFiles GRES | |
| */ | |
| if (gres_slurmd_conf->config_flags & GRES_CONF_HAS_MULT) { | |
| xfree(one_name); | |
| continue; | |
| } | |
| if ((fill_in_gres_devices->rc == SLURM_SUCCESS) && | |
| list_find_first(fill_in_gres_devices->names_list, | |
| slurm_find_char_exact_in_list, | |
| one_name)) { | |
| error("%s duplicate device file name (%s)", | |
| config->gres_name, one_name); | |
| fill_in_gres_devices->rc = SLURM_ERROR; | |
| } | |
| list_append(fill_in_gres_devices->names_list, one_name); | |
| /* Increment device bitmap index */ | |
| fill_in_gres_devices->index++; | |
| } | |
| hostlist_destroy(hl); | |
| if (gres_slurmd_conf->config_flags & GRES_CONF_HAS_MULT) | |
| fill_in_gres_devices->index++; | |
| return 0; | |
| } | |
| static int _foreach_fill_in_gres_devices_dev_id(void *x, void *arg) | |
| { | |
| gres_device_t *gres_device = x; | |
| foreach_fill_in_gres_devices_t *fill_in_gres_devices = arg; | |
| if (gres_device->dev_num == -1) | |
| gres_device->dev_num = ++fill_in_gres_devices->max_dev_num; | |
| if (slurm_conf.debug_flags & DEBUG_FLAG_GRES) { | |
| char *dev_id_str = gres_device_id2str(&gres_device->dev_desc); | |
| log_flag(GRES, "%s device number %d(%s):%s", | |
| fill_in_gres_devices->config->gres_name, | |
| gres_device->dev_num, | |
| gres_device->path, | |
| dev_id_str); | |
| xfree(dev_id_str); | |
| } | |
| return 0; | |
| } | |
| extern int gres_node_config_load(list_t *gres_conf_list, | |
| node_config_load_t *config, | |
| list_t **gres_devices) | |
| { | |
| foreach_fill_in_gres_devices_t fill_in_gres_devices = { | |
| .config = config, | |
| .gres_devices = gres_devices, | |
| .index = 0, | |
| .max_dev_num = -1, | |
| .names_list = list_create(xfree_ptr), | |
| .rc = SLURM_SUCCESS, | |
| }; | |
| xassert(gres_conf_list); | |
| xassert(gres_devices); | |
| (void) list_for_each(gres_conf_list, _foreach_fill_in_gres_devices, | |
| &fill_in_gres_devices); | |
| FREE_NULL_LIST(fill_in_gres_devices.names_list); | |
| if (*gres_devices) | |
| (void) list_for_each(*gres_devices, | |
| _foreach_fill_in_gres_devices_dev_id, | |
| &fill_in_gres_devices); | |
| return fill_in_gres_devices.rc; | |
| } | |
| static int _match_unique_id(void *x, void *key) | |
| { | |
| gres_slurmd_conf_t *conf = x; | |
| gres_slurmd_conf_t *match = key; | |
| return (conf->plugin_id == match->plugin_id) && | |
| !xstrcmp(conf->unique_id, match->unique_id); | |
| } | |
| static int _find_dup_unique_id(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *conf = x; | |
| find_dup_unique_id_args_t *args = arg; | |
| if (!conf->unique_id) | |
| return 0; | |
| if (!list_find_first(args->seen, _match_unique_id, conf)) { | |
| list_append(args->seen, conf); | |
| return 0; | |
| } | |
| args->dup = conf; | |
| return -1; | |
| } | |
| extern gres_slurmd_conf_t *gres_find_duplicate_unique_id(list_t *gres_conf_list) | |
| { | |
| find_dup_unique_id_args_t args = { 0 }; | |
| args.seen = list_create(NULL); | |
| (void) list_for_each(gres_conf_list, _find_dup_unique_id, &args); | |
| FREE_NULL_LIST(args.seen); | |
| return args.dup; | |
| } | |
| /* | |
| * Make sure no two gres.conf records of the same GRES name share a UUID. | |
| * A duplicate would make a device impossible to name unambiguously, both for | |
| * the vendor env vars and for drain/resume by UUID. | |
| * | |
| * Only gres.conf-supplied UUIDs are seen here; AutoDetect does not run | |
| * until node_config_load(), well after this. | |
| * | |
| * RET SLURM_SUCCESS or ESLURM_UNSUPPORTED_GRES (duplicate found) | |
| */ | |
| static int _validate_unique_ids(list_t *gres_conf_list) | |
| { | |
| gres_slurmd_conf_t *dup = gres_find_duplicate_unique_id(gres_conf_list); | |
| if (!dup) | |
| return SLURM_SUCCESS; | |
| /* | |
| * This runs in slurmctld too (for cloud nodes), where fatal() would | |
| * kill the whole controller over one node's bad config. Reject just | |
| * that node there instead. | |
| */ | |
| if (!running_in_slurmctld()) | |
| fatal("Duplicate UUID=%s for GRES %s in gres.conf. Each device must have a unique UUID", | |
| dup->unique_id, dup->name); | |
| error("Duplicate UUID=%s for GRES %s in gres.conf on node %s. Each device must have a unique UUID", | |
| dup->unique_id, dup->name, gres_node_name); | |
| return ESLURM_UNSUPPORTED_GRES; | |
| } | |
| /* | |
| * Parse gres.conf into gres_conf_list and update autodetect_flags. Refreshes | |
| * gres_node_name to match node_name. Caller must hold gres_context_lock. | |
| * | |
| * IN node_name - Name of the node to load the GRES config for | |
| * IN cpu_cnt - number of CPUs configured for node_name | |
| * RET SLURM_SUCCESS or ESLURM_UNSUPPORTED_GRES | |
| */ | |
| static int _parse_gres_conf_locked(char *node_name, uint32_t cpu_cnt) | |
| { | |
| static s_p_options_t _gres_conf_options[] = { | |
| {"AutoDetect", S_P_STRING}, | |
| {"Name", S_P_ARRAY, _parse_gres_config, NULL}, | |
| {"NodeName", S_P_ARRAY, _parse_gres_config_node, NULL}, | |
| {NULL} | |
| }; | |
| list_t *tmp_gres_conf_list = list_create(destroy_gres_slurmd_conf); | |
| int count = 0, i, rc = SLURM_SUCCESS; | |
| struct stat config_stat; | |
| s_p_hashtbl_t *tbl; | |
| gres_slurmd_conf_t **gres_array; | |
| char *gres_conf_file = NULL; | |
| char *autodetect_string = NULL; | |
| if (xstrcmp(gres_node_name, node_name)) { | |
| xfree(gres_node_name); | |
| gres_node_name = xstrdup(node_name); | |
| } | |
| gres_autodetect_flags_set_gpu(GRES_AUTODETECT_UNSET); | |
| autodetect_flags &= ~GRES_AUTODETECT_TPU; | |
| gres_conf_file = get_extra_conf_path("gres.conf"); | |
| if (stat(gres_conf_file, &config_stat) < 0) { | |
| info("Can not stat gres.conf file (%s), using slurm.conf data", | |
| gres_conf_file); | |
| } else { | |
| gres_cpu_cnt = cpu_cnt; | |
| tbl = s_p_hashtbl_create(_gres_conf_options); | |
| if (s_p_parse_file(tbl, NULL, gres_conf_file, 0, NULL) == | |
| SLURM_ERROR) | |
| fatal("error opening/reading %s", gres_conf_file); | |
| /* Overwrite unspecified local AutoDetect with global default */ | |
| if (s_p_get_string(&autodetect_string, "Autodetect", tbl)) { | |
| _handle_global_autodetect(autodetect_string); | |
| xfree(autodetect_string); | |
| } | |
| if (s_p_get_array((void ***) &gres_array, | |
| &count, "Name", tbl)) { | |
| for (i = 0; i < count; i++) { | |
| list_append(tmp_gres_conf_list, gres_array[i]); | |
| gres_array[i] = NULL; | |
| } | |
| } | |
| if (s_p_get_array((void ***) &gres_array, | |
| &count, "NodeName", tbl)) { | |
| for (i = 0; i < count; i++) { | |
| list_append(tmp_gres_conf_list, gres_array[i]); | |
| gres_array[i] = NULL; | |
| } | |
| } | |
| s_p_hashtbl_destroy(tbl); | |
| } | |
| rc = _validate_unique_ids(tmp_gres_conf_list); | |
| if (rc != SLURM_SUCCESS) | |
| goto fini; | |
| FREE_NULL_LIST(gres_conf_list); | |
| gres_conf_list = tmp_gres_conf_list; | |
| tmp_gres_conf_list = NULL; | |
| fini: | |
| FREE_NULL_LIST(tmp_gres_conf_list); | |
| xfree(gres_conf_file); | |
| return rc; | |
| } | |
| extern int gres_parse_conf(char *node_name, uint32_t cpu_cnt) | |
| { | |
| int rc; | |
| if (cpu_cnt == 0) { | |
| error("%s: Invalid cpu_cnt of 0 for node %s", | |
| __func__, node_name); | |
| return ESLURM_INVALID_CPU_COUNT; | |
| } | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| if (gres_context_cnt == 0) { | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return SLURM_SUCCESS; | |
| } | |
| rc = _parse_gres_conf_locked(node_name, cpu_cnt); | |
| if (rc == SLURM_SUCCESS) | |
| gres_conf_parsed_early = true; | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return rc; | |
| } | |
| /* Match a gres.conf record that holds devices of the key's name and type */ | |
| static int _find_same_type(void *x, void *key) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x, *other = key; | |
| return (gres_slurmd_conf->count && | |
| (gres_slurmd_conf->plugin_id == other->plugin_id) && | |
| !xstrcmp(gres_slurmd_conf->type_name, other->type_name)); | |
| } | |
| /* | |
| * Every record of a name and type feeds one chunk domain, so they must all set | |
| * the same ChunkSizes and ChunkOrder, or all set neither. | |
| */ | |
| static int _foreach_validate_chunk_conf(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x, *first; | |
| if (!gres_slurmd_conf->count) | |
| return 0; | |
| /* A record matches itself, so this always finds one */ | |
| first = list_find_first(arg, _find_same_type, gres_slurmd_conf); | |
| xassert(first); | |
| if (!first || (first == gres_slurmd_conf) || | |
| (!xstrcmp(gres_slurmd_conf->chunk_sizes, first->chunk_sizes) && | |
| !xstrcmp(gres_slurmd_conf->chunk_order, first->chunk_order))) | |
| return 0; | |
| fatal("Invalid GRES record name=%s type=%s: ChunkSizes and ChunkOrder must match the other records of the same GRES name and type on this node", | |
| gres_slurmd_conf->name, gres_slurmd_conf->type_name); | |
| } | |
| /* | |
| * Load this node's configuration (how many resources it has, topology, etc.) | |
| * | |
| * IN cpu_cnt - Number of CPUs configured for node node_name. | |
| * IN node_name - Name of the node to load the GRES config for. | |
| * IN gres_list - Node's GRES information as loaded from slurm.conf by slurmd | |
| * IN xcpuinfo_abs_to_mac - Pointer to xcpuinfo_abs_to_mac() funct. If | |
| * specified, Slurm will convert gres_slurmd_conf->cpus_bitmap (a bitmap | |
| * derived from gres.conf's "Cores" range string) into machine format | |
| * (normal slrumd/stepd operation). If not, it will remain unconverted (for | |
| * testing purposes or when unused). | |
| * IN xcpuinfo_mac_to_abs - Pointer to xcpuinfo_mac_to_abs() funct. Used to | |
| * convert CPU affinities from machine format (as collected from NVML and | |
| * others) into abstract format, for sanity checking purposes. | |
| * NOTE: Called from slurmd (and from slurmctld for each cloud node) | |
| */ | |
| extern int gres_g_node_config_load(uint32_t cpu_cnt, char *node_name, | |
| list_t *gres_list, | |
| void *xcpuinfo_abs_to_mac, | |
| void *xcpuinfo_mac_to_abs) | |
| { | |
| int i, rc, rc2; | |
| bool in_slurmd = running_in_slurmd(); | |
| node_config_load_t node_conf = { | |
| .cpu_cnt = cpu_cnt, | |
| .in_slurmd = in_slurmd, | |
| .xcpuinfo_mac_to_abs = xcpuinfo_mac_to_abs, | |
| }; | |
| if (cpu_cnt == 0) { | |
| error("%s: Invalid cpu_cnt of 0 for node %s", | |
| __func__, node_name); | |
| return ESLURM_INVALID_CPU_COUNT; | |
| } | |
| if (xcpuinfo_abs_to_mac) | |
| xcpuinfo_ops.xcpuinfo_abs_to_mac = xcpuinfo_abs_to_mac; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| if (gres_context_cnt == 0) { | |
| rc = SLURM_SUCCESS; | |
| goto fini; | |
| } | |
| if (gres_conf_parsed_early && !xstrcmp(gres_node_name, node_name)) { | |
| gres_conf_parsed_early = false; | |
| } else { | |
| gres_conf_parsed_early = false; | |
| rc = _parse_gres_conf_locked(node_name, cpu_cnt); | |
| if (rc != SLURM_SUCCESS) | |
| goto fini; | |
| } | |
| /* Validate gres.conf and slurm.conf somewhat before merging */ | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| _validate_slurm_conf(gres_list, &gres_context[i]); | |
| _validate_gres_conf(gres_conf_list, &gres_context[i]); | |
| _check_conf_mismatch(gres_list, gres_conf_list, | |
| &gres_context[i]); | |
| } | |
| /* Merge slurm.conf and gres.conf together into gres_conf_list */ | |
| _merge_config(&node_conf, gres_conf_list, gres_list); | |
| if ((rc = _load_specific_gres_plugins(&node_conf)) != SLURM_SUCCESS) { | |
| goto fini; | |
| } | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| node_conf.gres_name = gres_context[i].gres_name; | |
| if (gres_context[i].ops.node_config_load) | |
| rc2 = (*(gres_context[i].ops.node_config_load))( | |
| gres_conf_list, &node_conf); | |
| else if (gres_context[i].config_flags & GRES_CONF_HAS_FILE) { | |
| rc2 = gres_node_config_load( | |
| gres_conf_list, &node_conf, | |
| &gres_context[i].np_gres_devices); | |
| } else | |
| continue; | |
| if (rc == SLURM_SUCCESS) | |
| rc = rc2; | |
| } | |
| /* Postprocess gres_conf_list after all plugins' node_config_load */ | |
| /* Remove every GPU with an empty File */ | |
| (void) list_delete_all(gres_conf_list, _find_fileless_gres, | |
| &gpu_plugin_id); | |
| list_for_each(gres_conf_list, _log_gres_slurmd_conf, NULL); | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| list_for_each(gres_conf_list, _post_plugin_gres_conf, | |
| &gres_context[i]); | |
| } | |
| /* Run last, so AutoDetect and the plugins have had their say */ | |
| list_for_each(gres_conf_list, _foreach_validate_chunk_conf, | |
| gres_conf_list); | |
| fini: | |
| /* | |
| * We no longer need the gpu plugin unless this option is set: | |
| * AcctGatherEnergyType=acct_gather_energy/gpu | |
| * Note: slurmstepds may still load gpu plugin for gpu_g_usage_read() | |
| * unless JobAcctGatherParams=DisableGPUAcct is set | |
| */ | |
| if (!in_slurmd || !xstrstr(slurm_conf.acct_gather_energy_type, "gpu")) | |
| gpu_plugin_fini(); | |
| _pack_context_buf(); | |
| _pack_gres_conf(); | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return rc; | |
| } | |
| static int _copy_gres_slurmd_conf(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *src = x, *dst = xmalloc(sizeof(*dst)); | |
| list_t *copy = arg; | |
| *dst = *src; | |
| dst->chunk_order = xstrdup(src->chunk_order); | |
| dst->chunk_sizes = xstrdup(src->chunk_sizes); | |
| dst->cpus = xstrdup(src->cpus); | |
| if (src->cpus_bitmap) | |
| dst->cpus_bitmap = bit_copy(src->cpus_bitmap); | |
| dst->dmem_region = xstrdup(src->dmem_region); | |
| dst->file = xstrdup(src->file); | |
| dst->links = xstrdup(src->links); | |
| dst->name = xstrdup(src->name); | |
| dst->pci_addr = xstrdup(src->pci_addr); | |
| dst->type_name = xstrdup(src->type_name); | |
| dst->unique_id = xstrdup(src->unique_id); | |
| list_append(copy, dst); | |
| return 0; | |
| } | |
| extern list_t *gres_get_node_conf_list(void) | |
| { | |
| list_t *copy = NULL; | |
| slurm_mutex_lock(&gres_context_lock); | |
| if (gres_conf_list) { | |
| copy = list_create(destroy_gres_slurmd_conf); | |
| (void) list_for_each(gres_conf_list, _copy_gres_slurmd_conf, | |
| copy); | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return copy; | |
| } | |
| /* | |
| * Pack this node's gres configuration into a buffer | |
| * IN/OUT buffer - message buffer to pack | |
| */ | |
| extern int gres_node_config_pack(buf_t *buffer) | |
| { | |
| int rc = SLURM_SUCCESS; | |
| uint32_t magic = GRES_MAGIC; | |
| uint16_t rec_cnt = 0, version = SLURM_PROTOCOL_VERSION; | |
| list_itr_t *iter; | |
| gres_slurmd_conf_t *gres_slurmd_conf; | |
| pack16(version, buffer); | |
| if (gres_conf_list) | |
| rec_cnt = list_count(gres_conf_list); | |
| pack16(rec_cnt, buffer); | |
| if (rec_cnt) { | |
| /* | |
| * It might be tempting to convert this to slurm_pack_list, | |
| * The problem with that is how we unpack things in the function | |
| * below this. It uses 'node_name' all throughout which can not | |
| * be passed to slurm_unpack_list. This function is not called | |
| * very often (only when the slurmd registers). The efforts to | |
| * make this work are just not worth it. | |
| */ | |
| iter = list_iterator_create(gres_conf_list); | |
| while ((gres_slurmd_conf = | |
| (gres_slurmd_conf_t *) list_next(iter))) { | |
| pack32(magic, buffer); | |
| pack64(gres_slurmd_conf->count, buffer); | |
| pack32(gres_slurmd_conf->cpu_cnt, buffer); | |
| pack32(gres_slurmd_conf->config_flags, buffer); | |
| pack32(gres_slurmd_conf->plugin_id, buffer); | |
| packstr(gres_slurmd_conf->chunk_order, buffer); | |
| packstr(gres_slurmd_conf->chunk_sizes, buffer); | |
| packstr(gres_slurmd_conf->cpus, buffer); | |
| packstr(gres_slurmd_conf->links, buffer); | |
| packstr(gres_slurmd_conf->name, buffer); | |
| packstr(gres_slurmd_conf->type_name, buffer); | |
| packstr(gres_slurmd_conf->unique_id, buffer); | |
| } | |
| list_iterator_destroy(iter); | |
| } | |
| return rc; | |
| } | |
| static int _add_to_gres_conf_list(gres_slurmd_conf_t *conf, char *node_name) | |
| { | |
| slurm_gres_context_t *gres_ctx; | |
| bool new_has_file; | |
| bool orig_has_file; | |
| if (!conf->count) | |
| goto empty; | |
| log_flag(GRES, "Node:%s Gres:%s Type:%s UUID:%s Flags:%s CPU_IDs:%s CPU#:%u Count:%"PRIu64" Links:%s ChunkSizes:%s ChunkOrder:%s", | |
| node_name, conf->name, conf->type_name, conf->unique_id, | |
| gres_flags2str(conf->config_flags), conf->cpus, conf->cpu_cnt, | |
| conf->count, conf->links, conf->chunk_sizes, | |
| conf->chunk_order); | |
| if (!(gres_ctx = _find_context_by_id(conf->plugin_id))) { | |
| /* | |
| * GresPlugins is inconsistently configured. | |
| * Not a fatal error, but skip this data. | |
| */ | |
| error("%s: No plugin configured to process GRES data from node %s (Name:%s Type:%s PluginID:%u Count:%"PRIu64")", | |
| __func__, node_name, conf->name, conf->type_name, | |
| conf->plugin_id, conf->count); | |
| return SLURM_ERROR; | |
| } | |
| if (xstrcmp(gres_ctx->gres_name, conf->name)) { | |
| /* | |
| * Should have been caught in | |
| * gres_init() | |
| */ | |
| error("%s: gres/%s duplicate plugin ID with %s, unable to process", | |
| __func__, conf->name, | |
| gres_ctx->gres_name); | |
| return SLURM_ERROR; | |
| } | |
| new_has_file = conf->config_flags & GRES_CONF_HAS_FILE; | |
| orig_has_file = gres_ctx->config_flags & GRES_CONF_HAS_FILE; | |
| if (orig_has_file && !new_has_file && conf->count) { | |
| error("%s: gres/%s lacks \"File=\" parameter for node %s", | |
| __func__, conf->name, node_name); | |
| conf->config_flags |= GRES_CONF_HAS_FILE; | |
| } | |
| if (new_has_file && (conf->count > MAX_GRES_BITMAP) && | |
| !gres_id_shared(conf->config_flags)) { | |
| /* | |
| * Avoid over-subscribing memory with | |
| * huge bitmaps | |
| */ | |
| error("%s: gres/%s has \"File=\" plus very large " | |
| "\"Count\" (%"PRIu64") for node %s, " | |
| "resetting value to %d", | |
| __func__, conf->name, conf->count, | |
| node_name, MAX_GRES_BITMAP); | |
| conf->count = MAX_GRES_BITMAP; | |
| } | |
| /* | |
| * If one node in the bunch said a gres has removed | |
| * GRES_CONF_ONE_SHARING then remove it from the | |
| * context. | |
| */ | |
| if ((gres_ctx->config_flags & GRES_CONF_LOADED) && | |
| gres_id_shared(conf->config_flags)) { | |
| bool gc_one_sharing = | |
| gres_ctx->config_flags & GRES_CONF_ONE_SHARING; | |
| bool got_one_sharing = | |
| conf->config_flags & GRES_CONF_ONE_SHARING; | |
| if (gc_one_sharing == got_one_sharing) { | |
| } else if (!gc_one_sharing && got_one_sharing) { | |
| log_flag(GRES, "gres/%s was already set up to share all ignoring one_sharing from %s", | |
| conf->name, node_name); | |
| conf->config_flags &= ~GRES_CONF_ONE_SHARING; | |
| } else if (!got_one_sharing) { | |
| log_flag(GRES, "gres/%s was already set up to only share one, but we just found the opposite from %s. Removing flag.", | |
| conf->name, node_name); | |
| gres_ctx->config_flags &= ~GRES_CONF_ONE_SHARING; | |
| } | |
| } | |
| /* | |
| * If we read in from state we want to take the slurmd's view | |
| * over our state. | |
| */ | |
| if (gres_ctx->config_flags & GRES_CONF_FROM_STATE) | |
| gres_ctx->config_flags = conf->config_flags; | |
| else | |
| gres_ctx->config_flags |= conf->config_flags; | |
| /* | |
| * On the slurmctld we need to load the plugins to | |
| * correctly set env vars. We want to call this only | |
| * after we have the config_flags so we can tell if we | |
| * are CountOnly or not. | |
| */ | |
| if (!(gres_ctx->config_flags & GRES_CONF_LOADED)) { | |
| (void) _load_plugin(gres_ctx); | |
| gres_ctx->config_flags |= GRES_CONF_LOADED; | |
| } | |
| empty: | |
| if (gres_links_validate(conf->links) < -1) { | |
| error("%s: Ignoring invalid Links=%s for Name=%s", | |
| __func__, conf->links, conf->name); | |
| xfree(conf->links); | |
| } | |
| list_append(gres_conf_list, conf); | |
| return SLURM_SUCCESS; | |
| } | |
| /* | |
| * Unpack this node's configuration from a buffer (built/packed by slurmd) | |
| * IN/OUT buffer - message buffer to unpack | |
| * IN node_name - name of node whose data is being unpacked | |
| */ | |
| extern int gres_node_config_unpack(buf_t *buffer, char *node_name) | |
| { | |
| int i, rc = SLURM_SUCCESS; | |
| uint32_t magic = 0; | |
| uint16_t rec_cnt = 0, protocol_version = 0; | |
| gres_slurmd_conf_t *conf = NULL; | |
| bool locked = false; | |
| xassert(gres_context_cnt >= 0); | |
| FREE_NULL_LIST(gres_conf_list); | |
| gres_conf_list = list_create(destroy_gres_slurmd_conf); | |
| safe_unpack16(&protocol_version, buffer); | |
| safe_unpack16(&rec_cnt, buffer); | |
| if (rec_cnt == 0) | |
| return SLURM_SUCCESS; | |
| if (rec_cnt > NO_VAL16) | |
| goto unpack_error; | |
| slurm_mutex_lock(&gres_context_lock); | |
| locked = true; | |
| if (protocol_version < SLURM_MIN_PROTOCOL_VERSION) { | |
| error("%s: protocol_version %hu not supported", | |
| __func__, protocol_version); | |
| goto unpack_error; | |
| } | |
| for (i = 0; i < rec_cnt; i++) { | |
| if (protocol_version >= SLURM_26_11_PROTOCOL_VERSION) { | |
| safe_unpack32(&magic, buffer); | |
| if (magic != GRES_MAGIC) | |
| goto unpack_error; | |
| conf = xmalloc(sizeof(gres_slurmd_conf_t)); | |
| safe_unpack64(&conf->count, buffer); | |
| safe_unpack32(&conf->cpu_cnt, buffer); | |
| safe_unpack32(&conf->config_flags, buffer); | |
| safe_unpack32(&conf->plugin_id, buffer); | |
| safe_unpackstr(&conf->chunk_order, buffer); | |
| safe_unpackstr(&conf->chunk_sizes, buffer); | |
| safe_unpackstr(&conf->cpus, buffer); | |
| safe_unpackstr(&conf->links, buffer); | |
| safe_unpackstr(&conf->name, buffer); | |
| safe_unpackstr(&conf->type_name, buffer); | |
| safe_unpackstr(&conf->unique_id, buffer); | |
| } else if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) { | |
| safe_unpack32(&magic, buffer); | |
| if (magic != GRES_MAGIC) | |
| goto unpack_error; | |
| conf = xmalloc(sizeof(gres_slurmd_conf_t)); | |
| safe_unpack64(&conf->count, buffer); | |
| safe_unpack32(&conf->cpu_cnt, buffer); | |
| safe_unpack32(&conf->config_flags, buffer); | |
| safe_unpack32(&conf->plugin_id, buffer); | |
| safe_unpackstr(&conf->cpus, buffer); | |
| safe_unpackstr(&conf->links, buffer); | |
| safe_unpackstr(&conf->name, buffer); | |
| safe_unpackstr(&conf->type_name, buffer); | |
| safe_unpackstr(&conf->unique_id, buffer); | |
| } | |
| /* slurmd drops configured Links, this covers AutoDetect */ | |
| if (conf && conf->chunk_sizes) | |
| xfree(conf->links); | |
| if (conf && _add_to_gres_conf_list(conf, node_name)) | |
| destroy_gres_slurmd_conf(conf); | |
| conf = NULL; | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return rc; | |
| unpack_error: | |
| error("%s: unpack error from node %s", __func__, node_name); | |
| if (conf) | |
| destroy_gres_slurmd_conf(conf); | |
| if (locked) | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return SLURM_ERROR; | |
| } | |
| static void _gres_state_delete_members(void *x) | |
| { | |
| gres_state_t *gres_ptr = (gres_state_t *) x; | |
| if (!gres_ptr) | |
| return; | |
| xfree(gres_ptr->gres_name); | |
| xassert(!gres_ptr->gres_data); /* This must be freed beforehand */ | |
| xfree(gres_ptr); | |
| } | |
| static void _gres_node_state_delete_topo(gres_node_state_t *gres_ns) | |
| { | |
| int i; | |
| for (i = 0; i < gres_ns->topo_cnt; i++) { | |
| if (gres_ns->topo_gres_bitmap) | |
| FREE_NULL_BITMAP(gres_ns->topo_gres_bitmap[i]); | |
| if (gres_ns->topo_core_bitmap) | |
| FREE_NULL_BITMAP(gres_ns->topo_core_bitmap[i]); | |
| if (gres_ns->topo_res_core_bitmap) | |
| FREE_NULL_BITMAP(gres_ns->topo_res_core_bitmap[i]); | |
| xfree(gres_ns->topo_type_name[i]); | |
| } | |
| xfree(gres_ns->topo_gres_bitmap); | |
| xfree(gres_ns->topo_core_bitmap); | |
| xfree(gres_ns->topo_gres_cnt_alloc); | |
| xfree(gres_ns->topo_gres_cnt_avail); | |
| xfree(gres_ns->topo_res_core_bitmap); | |
| xfree(gres_ns->topo_type_id); | |
| xfree(gres_ns->topo_type_name); | |
| } | |
| /* Drop any chunk state, so a removed or changed ChunkSizes is not kept */ | |
| static void _clear_chunk_config(gres_node_state_t *gres_ns) | |
| { | |
| for (int i = 0; i < gres_ns->chunk_domain_cnt; i++) { | |
| xfree(gres_ns->chunk_domains[i].order); | |
| xfree(gres_ns->chunk_domains[i].order_str); | |
| xfree(gres_ns->chunk_domains[i].sizes); | |
| xfree(gres_ns->chunk_domains[i].sizes_str); | |
| } | |
| xfree(gres_ns->chunk_domains); | |
| gres_ns->chunk_domain_cnt = 0; | |
| } | |
| static void _gres_node_state_delete(gres_node_state_t *gres_ns) | |
| { | |
| int i; | |
| FREE_NULL_BITMAP(gres_ns->gres_bit_alloc); | |
| xfree(gres_ns->gres_used); | |
| FREE_NULL_LIST(gres_ns->drain_groups); | |
| FREE_NULL_LIST(gres_ns->type_drain); | |
| drained_tres_count_clear(&gres_ns->untyped_drain); | |
| FREE_NULL_BITMAP(gres_ns->gres_bit_drain); | |
| for (i = 0; i < gres_ns->dev_uuid_cnt; i++) | |
| xfree(gres_ns->dev_uuid[i]); | |
| xfree(gres_ns->dev_uuid); | |
| if (gres_ns->links_cnt) { | |
| for (i = 0; i < gres_ns->link_len; i++) | |
| xfree(gres_ns->links_cnt[i]); | |
| xfree(gres_ns->links_cnt); | |
| } | |
| _clear_chunk_config(gres_ns); | |
| _gres_node_state_delete_topo(gres_ns); | |
| for (i = 0; i < gres_ns->type_cnt; i++) { | |
| xfree(gres_ns->type_name[i]); | |
| } | |
| xfree(gres_ns->type_cnt_alloc); | |
| xfree(gres_ns->type_cnt_avail); | |
| xfree(gres_ns->type_id); | |
| xfree(gres_ns->type_name); | |
| xfree(gres_ns); | |
| } | |
| /* | |
| * Delete an element placed on gres_list by _node_config_validate() | |
| * free associated memory | |
| */ | |
| static void _gres_node_list_delete(void *list_element) | |
| { | |
| gres_state_t *gres_state_node; | |
| gres_node_state_t *gres_ns; | |
| gres_state_node = (gres_state_t *) list_element; | |
| gres_ns = (gres_node_state_t *) gres_state_node->gres_data; | |
| _gres_node_state_delete(gres_ns); | |
| gres_state_node->gres_data = NULL; | |
| _gres_state_delete_members(gres_state_node); | |
| } | |
| extern void gres_add_type(char *type, gres_node_state_t *gres_ns, | |
| uint64_t tmp_gres_cnt) | |
| { | |
| int i; | |
| uint32_t type_id; | |
| if (!xstrcasecmp(type, "no_consume")) { | |
| gres_ns->no_consume = true; | |
| return; | |
| } | |
| type_id = gres_build_id(type); | |
| for (i = 0; i < gres_ns->type_cnt; i++) { | |
| if (gres_ns->type_id[i] != type_id) | |
| continue; | |
| gres_ns->type_cnt_avail[i] += tmp_gres_cnt; | |
| break; | |
| } | |
| if (i >= gres_ns->type_cnt) { | |
| gres_ns->type_cnt++; | |
| gres_ns->type_cnt_alloc = | |
| xrealloc(gres_ns->type_cnt_alloc, | |
| sizeof(uint64_t) * gres_ns->type_cnt); | |
| gres_ns->type_cnt_avail = | |
| xrealloc(gres_ns->type_cnt_avail, | |
| sizeof(uint64_t) * gres_ns->type_cnt); | |
| gres_ns->type_id = | |
| xrealloc(gres_ns->type_id, | |
| sizeof(uint32_t) * gres_ns->type_cnt); | |
| gres_ns->type_name = | |
| xrealloc(gres_ns->type_name, | |
| sizeof(char *) * gres_ns->type_cnt); | |
| gres_ns->type_cnt_avail[i] += tmp_gres_cnt; | |
| gres_ns->type_id[i] = type_id; | |
| gres_ns->type_name[i] = xstrdup(type); | |
| } | |
| } | |
| /* | |
| * Compute the total GRES count for a particular gres_name. | |
| * Note that a given gres_name can appear multiple times in the orig_config | |
| * string for multiple types (e.g. "gres=gpu:kepler:1,gpu:tesla:2"). | |
| * IN/OUT gres_ns - set gres_cnt_config field in this structure | |
| * IN orig_config - gres configuration from slurm.conf | |
| * IN gres_name - name of the gres type (e.g. "gpu") | |
| * IN gres_name_colon - gres name with appended colon | |
| * IN gres_name_colon_len - size of gres_name_colon | |
| * RET - Total configured count for this GRES type | |
| */ | |
| static void _get_gres_cnt(gres_node_state_t *gres_ns, char *orig_config, | |
| char *gres_name, char *gres_name_colon, | |
| int gres_name_colon_len) | |
| { | |
| char *node_gres_config, *tok, *last_tok = NULL; | |
| char *sub_tok, *last_sub_tok = NULL; | |
| char *num, *paren, *last_num = NULL; | |
| uint64_t gres_config_cnt = 0, tmp_gres_cnt = 0, mult; | |
| int i; | |
| xassert(gres_ns); | |
| if (orig_config == NULL) { | |
| gres_ns->gres_cnt_config = 0; | |
| return; | |
| } | |
| for (i = 0; i < gres_ns->type_cnt; i++) { | |
| gres_ns->type_cnt_avail[i] = 0; | |
| } | |
| node_gres_config = xstrdup(orig_config); | |
| tok = strtok_r(node_gres_config, ",", &last_tok); | |
| while (tok) { | |
| if (!xstrcmp(tok, gres_name)) { | |
| gres_config_cnt = 1; | |
| break; | |
| } | |
| if (!xstrncmp(tok, gres_name_colon, gres_name_colon_len)) { | |
| paren = strrchr(tok, '('); | |
| if (paren) /* Ignore socket binding info */ | |
| paren[0] = '\0'; | |
| num = strrchr(tok, ':'); | |
| if (!num) { | |
| error("Bad GRES configuration: %s", tok); | |
| break; | |
| } | |
| tmp_gres_cnt = strtoll(num + 1, &last_num, 10); | |
| if ((num[1] < '0') || (num[1] > '9')) { | |
| /* | |
| * Type name, no count (e.g. "gpu:tesla"). | |
| * assume count of 1. | |
| */ | |
| tmp_gres_cnt = 1; | |
| } else if ((mult = suffix_mult(last_num)) != NO_VAL64) { | |
| tmp_gres_cnt *= mult; | |
| num[0] = '\0'; | |
| } else { | |
| error("Bad GRES configuration: %s", tok); | |
| break; | |
| } | |
| gres_config_cnt += tmp_gres_cnt; | |
| sub_tok = strtok_r(tok, ":", &last_sub_tok); | |
| if (sub_tok) /* Skip GRES name */ | |
| sub_tok = strtok_r(NULL, ":", &last_sub_tok); | |
| while (sub_tok) { | |
| gres_add_type(sub_tok, gres_ns, | |
| tmp_gres_cnt); | |
| sub_tok = strtok_r(NULL, ":", &last_sub_tok); | |
| } | |
| } | |
| tok = strtok_r(NULL, ",", &last_tok); | |
| } | |
| xfree(node_gres_config); | |
| gres_ns->gres_cnt_config = gres_config_cnt; | |
| _gres_drain_cnt_clamp(gres_ns); | |
| } | |
| static int _find_gres_type(gres_node_state_t *gres_ns, uint32_t type_id) | |
| { | |
| int type_index = -1; | |
| for (int i = 0; i < gres_ns->type_cnt; i++) { | |
| if(type_id == gres_ns->type_id[i]) { | |
| type_index = i; | |
| break; | |
| } | |
| } | |
| return type_index; | |
| } | |
| /* | |
| * The type_id of the device at this index, from the topology that maps a range | |
| * of device indexes to a type. RET 0 when the GRES is untyped, or when no | |
| * topology covers the index. | |
| */ | |
| static uint32_t _gres_device_topo_type_id(gres_node_state_t *gres_ns, int index) | |
| { | |
| if (!gres_ns->topo_cnt || !gres_ns->topo_gres_bitmap || | |
| !gres_ns->topo_type_id) | |
| return 0; | |
| for (int t = 0; t < gres_ns->topo_cnt; t++) { | |
| bitstr_t *topo_bits = gres_ns->topo_gres_bitmap[t]; | |
| if (topo_bits && (index < bit_size(topo_bits)) && | |
| bit_test(topo_bits, index)) | |
| return gres_ns->topo_type_id[t]; | |
| } | |
| return 0; | |
| } | |
| static int _find_gres_type_name(gres_node_state_t *gres_ns, char *type_name) | |
| { | |
| for (int i = 0; i < gres_ns->type_cnt; i++) { | |
| if (!xstrcasecmp(gres_ns->type_name[i], type_name)) | |
| return i; | |
| } | |
| return -1; | |
| } | |
| static int _valid_gres_type(char *gres_name, gres_node_state_t *gres_ns, | |
| bool config_overrides, char **reason_down) | |
| { | |
| int i, j; | |
| uint64_t model_cnt; | |
| int num_type_rem = 0; | |
| if (gres_ns->type_cnt == 0) | |
| return SLURM_SUCCESS; | |
| for (i = 0; i < gres_ns->type_cnt; i++) { | |
| model_cnt = 0; | |
| if (gres_ns->type_cnt) { | |
| for (j = 0; j < gres_ns->type_cnt; j++) { | |
| if (gres_ns->type_id[i] == | |
| gres_ns->type_id[j]) | |
| model_cnt += | |
| gres_ns->type_cnt_avail[j]; | |
| } | |
| } else { | |
| for (j = 0; j < gres_ns->topo_cnt; j++) { | |
| if (gres_ns->topo_type_id[i] == | |
| gres_ns->topo_type_id[j]) | |
| model_cnt += gres_ns-> | |
| topo_gres_cnt_avail[j]; | |
| } | |
| } | |
| if (config_overrides) { | |
| gres_ns->type_cnt_avail[i] = model_cnt; | |
| } else if (model_cnt < gres_ns->type_cnt_avail[i]) { | |
| if (reason_down) { | |
| xstrfmtcat(*reason_down, | |
| "%s:%s count too low " | |
| "(%"PRIu64" < %"PRIu64")", | |
| gres_name, gres_ns->type_name[i], | |
| model_cnt, | |
| gres_ns->type_cnt_avail[i]); | |
| } | |
| return SLURM_ERROR; | |
| } | |
| } | |
| /* | |
| * Remove types with 0 available. This happens when updating the type | |
| * of a gres in slurm.conf during a reconfig | |
| */ | |
| for (int i = 0; i < gres_ns->type_cnt; i++) { | |
| if (gres_ns->type_cnt_avail[i]) | |
| continue; | |
| num_type_rem++; | |
| } | |
| if (num_type_rem) { | |
| int tmp_cnt; | |
| uint64_t *tmp_type_cnt_alloc, *tmp_type_cnt_avail; | |
| uint32_t *tmp_type_id; | |
| char **tmp_type_name; | |
| tmp_cnt = gres_ns->type_cnt - num_type_rem; | |
| tmp_type_id = xcalloc(tmp_cnt, sizeof(*tmp_type_id)); | |
| tmp_type_cnt_alloc = | |
| xcalloc(tmp_cnt, sizeof(*tmp_type_cnt_alloc)); | |
| tmp_type_cnt_avail = | |
| xcalloc(tmp_cnt, sizeof(*tmp_type_cnt_avail)); | |
| tmp_type_name = | |
| xcalloc(tmp_cnt, sizeof(*tmp_type_name)); | |
| for (int j = 0, i = 0; i < gres_ns->type_cnt; i++) { | |
| if (!gres_ns->type_cnt_avail[i]) { | |
| drained_tres_count_list_set( | |
| &gres_ns->type_drain, | |
| gres_ns->type_name[i], 0, NULL, false); | |
| xfree(gres_ns->type_name[i]); | |
| continue; | |
| } | |
| tmp_type_cnt_alloc[j] = | |
| gres_ns->type_cnt_alloc[i]; | |
| tmp_type_cnt_avail[j] = | |
| gres_ns->type_cnt_avail[i]; | |
| tmp_type_id[j] = gres_ns->type_id[i]; | |
| tmp_type_name[j] = gres_ns->type_name[i]; | |
| j++; | |
| } | |
| xfree(gres_ns->type_cnt_alloc); | |
| xfree(gres_ns->type_cnt_avail); | |
| xfree(gres_ns->type_id); | |
| xfree(gres_ns->type_name); | |
| gres_ns->type_cnt_alloc = tmp_type_cnt_alloc; | |
| gres_ns->type_cnt_avail = tmp_type_cnt_avail; | |
| gres_ns->type_id = tmp_type_id; | |
| gres_ns->type_name = tmp_type_name; | |
| gres_ns->type_cnt -= num_type_rem; | |
| /* | |
| * No types left and no device bitmap. Drop the untyped pool. | |
| * Device UUID groups stay, and the clamp below leaves their | |
| * total alone. | |
| */ | |
| if (!gres_ns->type_cnt && !gres_ns->gres_bit_drain) | |
| drained_tres_count_set(&gres_ns->untyped_drain, 0, NULL, | |
| false); | |
| _gres_drain_cnt_clamp(gres_ns); | |
| } | |
| for (int i = 0; i < gres_ns->topo_cnt; i++) { | |
| if (_find_gres_type(gres_ns, gres_ns->topo_type_id[i]) < 0) { | |
| if (reason_down && (*reason_down == NULL)) { | |
| xstrfmtcat(*reason_down, | |
| "%s type (%s) reported but not configured", | |
| gres_name, | |
| gres_ns->topo_type_name[i]); | |
| } | |
| return SLURM_ERROR; | |
| } | |
| } | |
| return SLURM_SUCCESS; | |
| } | |
| static gres_node_state_t *_build_gres_node_state(void) | |
| { | |
| gres_node_state_t *gres_ns; | |
| gres_ns = xmalloc(sizeof(gres_node_state_t)); | |
| gres_ns->gres_cnt_config = NO_VAL64; | |
| gres_ns->gres_cnt_found = NO_VAL64; | |
| return gres_ns; | |
| } | |
| /* | |
| * Build a node's gres record based only upon the slurm.conf contents | |
| */ | |
| static void _node_config_init(char *orig_config, slurm_gres_context_t *gres_ctx, | |
| gres_state_t *gres_state_node) | |
| { | |
| gres_node_state_t *gres_ns; | |
| if (!gres_state_node->gres_data) | |
| gres_state_node->gres_data = _build_gres_node_state(); | |
| gres_ns = (gres_node_state_t *) gres_state_node->gres_data; | |
| /* If the resource isn't configured for use with this node */ | |
| if ((orig_config == NULL) || (orig_config[0] == '\0')) { | |
| gres_ns->gres_cnt_config = 0; | |
| return; | |
| } | |
| _get_gres_cnt(gres_ns, orig_config, | |
| gres_ctx->gres_name, | |
| gres_ctx->gres_name_colon, | |
| gres_ctx->gres_name_colon_len); | |
| gres_ctx->total_cnt += gres_ns->gres_cnt_config; | |
| /* Use count from recovered state, if higher */ | |
| gres_ns->gres_cnt_avail = MAX(gres_ns->gres_cnt_avail, | |
| gres_ns->gres_cnt_config); | |
| if ((gres_ns->gres_bit_alloc != NULL) && | |
| (gres_ns->gres_cnt_avail > | |
| bit_size(gres_ns->gres_bit_alloc)) && | |
| !gres_id_shared(gres_ctx->config_flags)) { | |
| _gres_bit_alloc_resize(gres_ns, gres_ns->gres_cnt_avail); | |
| } | |
| } | |
| /* Set up the shared/sharing pointers for easy look up later */ | |
| static void _set_alt_gres(gres_state_t *gres_state_node_shared, | |
| gres_state_t *gres_state_node_sharing) | |
| { | |
| if (gres_state_node_shared) { | |
| if (!gres_state_node_sharing) { | |
| error("we have a shared gres of '%s' but no gres that is sharing", | |
| gres_state_node_shared->gres_name); | |
| } else { | |
| gres_node_state_t *gres_ns_shared = | |
| gres_state_node_shared->gres_data; | |
| gres_node_state_t *gres_ns_sharing = | |
| gres_state_node_sharing->gres_data; | |
| gres_ns_shared->alt_gres = gres_state_node_sharing; | |
| gres_ns_sharing->alt_gres = gres_state_node_shared; | |
| } | |
| } | |
| } | |
| /* | |
| * Build a node's gres record based only upon the slurm.conf contents | |
| * IN orig_config - Gres information supplied from slurm.conf | |
| * IN/OUT gres_list - List of Gres records for this node to track usage | |
| */ | |
| extern void gres_init_node_config(char *orig_config, list_t **gres_list) | |
| { | |
| gres_state_t *gres_state_node, *gres_state_node_sharing = NULL, | |
| *gres_state_node_shared = NULL; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| if ((gres_context_cnt > 0) && (*gres_list == NULL)) { | |
| *gres_list = list_create(_gres_node_list_delete); | |
| } | |
| for (int i = 0; i < gres_context_cnt; i++) { | |
| gres_node_state_t *gres_ns; | |
| /* Find or create gres_state entry on the list */ | |
| gres_state_node = list_find_first(*gres_list, gres_find_id, | |
| &gres_context[i].plugin_id); | |
| if (gres_state_node == NULL) { | |
| gres_state_node = gres_create_state( | |
| &gres_context[i], GRES_STATE_SRC_CONTEXT_PTR, | |
| GRES_STATE_TYPE_NODE, _build_gres_node_state()); | |
| list_append(*gres_list, gres_state_node); | |
| } | |
| _node_config_init(orig_config, &gres_context[i], | |
| gres_state_node); | |
| gres_ns = gres_state_node->gres_data; | |
| if (gres_ns && gres_ns->gres_cnt_config) { | |
| if (gres_id_sharing(gres_state_node->plugin_id)) | |
| gres_state_node_sharing = gres_state_node; | |
| else if (gres_id_shared(gres_state_node->config_flags)) | |
| gres_state_node_shared = gres_state_node; | |
| } | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| _set_alt_gres(gres_state_node_shared, gres_state_node_sharing); | |
| } | |
| static int _foreach_get_tot_from_slurmd_conf(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x; | |
| tot_from_slurmd_conf_t *slurmd_conf_tot = arg; | |
| if (gres_slurmd_conf->plugin_id != slurmd_conf_tot->plugin_id) | |
| return 0; | |
| slurmd_conf_tot->config_flags |= gres_slurmd_conf->config_flags; | |
| slurmd_conf_tot->gres_cnt += gres_slurmd_conf->count; | |
| slurmd_conf_tot->rec_cnt++; | |
| if (gres_slurmd_conf->cpus || gres_slurmd_conf->type_name || | |
| gres_slurmd_conf->unique_id || gres_slurmd_conf->chunk_sizes) | |
| slurmd_conf_tot->has_topo_info = true; | |
| return 0; | |
| } | |
| /* | |
| * Determine GRES availability on some node | |
| * | |
| * tot_from_slurmd_conf_t: | |
| * plugin_id IN - plugin number to search for | |
| * config_flags OUT - config flags from slurmd | |
| * topo_cnt OUT - count of gres.conf records of this ID found by slurmd | |
| * (each can have different topology) | |
| * config_type_cnt OUT - Count of records for this GRES found in configuration, | |
| * each of this represents a different Type of of GRES with | |
| * this name (e.g. GPU model) | |
| * gres_cnt OUT - total number of GRES available of this ID on this node in (sum | |
| * across all records of this ID) | |
| */ | |
| static void _get_tot_from_slurmd_conf(tot_from_slurmd_conf_t *slurmd_conf_tot) | |
| { | |
| xassert(slurmd_conf_tot); | |
| slurmd_conf_tot->config_flags = 0; | |
| slurmd_conf_tot->has_topo_info = false; | |
| slurmd_conf_tot->config_type_cnt = 0; | |
| slurmd_conf_tot->topo_cnt = 0; | |
| slurmd_conf_tot->gres_cnt = 0; | |
| slurmd_conf_tot->rec_cnt = 0; | |
| if (gres_conf_list == NULL) | |
| return; | |
| (void) list_for_each(gres_conf_list, _foreach_get_tot_from_slurmd_conf, | |
| slurmd_conf_tot); | |
| slurmd_conf_tot->config_type_cnt = slurmd_conf_tot->rec_cnt; | |
| } | |
| /* Convert comma-delimited array of link counts to an integer array */ | |
| static int _links_str2array(char *links, char *node_name, | |
| gres_node_state_t *gres_ns, | |
| int gres_inx, int gres_cnt, | |
| char **reason_down) | |
| { | |
| char *start_ptr, *end_ptr = NULL, *tmp = NULL; | |
| int i = 0, rc = SLURM_SUCCESS; | |
| if (!links) /* No "Links=" data */ | |
| return SLURM_SUCCESS; | |
| if (gres_inx >= gres_ns->link_len) { | |
| tmp = xstrdup_printf("Invalid GRES index (%d >= %d)", | |
| gres_inx, gres_cnt); | |
| rc = SLURM_ERROR; | |
| goto end_it; | |
| } | |
| start_ptr = links; | |
| while (1) { | |
| gres_ns->links_cnt[gres_inx][i] = | |
| strtol(start_ptr, &end_ptr, 10); | |
| if (gres_ns->links_cnt[gres_inx][i] < -2) { | |
| tmp = xstrdup_printf("Invalid GRES Links value (%s) on node %s: Link value '%d' < -2", | |
| links, node_name, | |
| gres_ns->links_cnt[gres_inx][i]); | |
| gres_ns->links_cnt[gres_inx][i] = 0; | |
| rc = SLURM_ERROR; | |
| goto end_it; | |
| } | |
| if (end_ptr[0] == '\0') | |
| return SLURM_SUCCESS; | |
| if (end_ptr[0] != ',') { | |
| tmp = xstrdup_printf("Invalid GRES Links value (%s) on node %s: end_ptr[0]='%c' != ','", | |
| links, node_name, end_ptr[0]); | |
| rc = SLURM_ERROR; | |
| goto end_it; | |
| } | |
| if (++i >= gres_ns->link_len) { | |
| tmp = xstrdup_printf("Invalid GRES Links value (%s) on node %s: i=%d >= link_len=%d.", | |
| links, node_name, | |
| i, gres_ns->link_len); | |
| rc = SLURM_ERROR; | |
| goto end_it; | |
| } | |
| start_ptr = end_ptr + 1; | |
| } | |
| end_it: | |
| if (rc) { | |
| error("%s: %s If using AutoDetect the amount of GPUs configured in slurm.conf does not match what was detected. If this is intentional, please turn off AutoDetect and manually specify them in gres.conf.", | |
| __func__, tmp); | |
| if (reason_down && !(*reason_down)) { | |
| *reason_down = tmp; | |
| tmp = NULL; | |
| } else | |
| xfree(tmp); | |
| /* create zeroed-out links array (NVLINK_NONE == 0) */ | |
| memset(gres_ns->links_cnt[gres_inx], 0, gres_cnt * sizeof(int)); | |
| } | |
| return rc; | |
| } | |
| /* | |
| * Build the chunk domain of one Type on a node. Aligned chunks need a device | |
| * count that the largest chunk size divides evenly. | |
| * IN build - the Type's ChunkSizes, ChunkOrder and devices | |
| * IN node_name - node name, for logging | |
| * IN is_shared - true for a shared GRES (gres/shard, gres/mps) | |
| * IN/OUT gres_ns - node state to add the domain to | |
| * OUT reason_down - failure reason, if not already set | |
| * RET SLURM_SUCCESS or SLURM_ERROR | |
| */ | |
| static int _build_chunk_config(chunk_build_t *build, char *node_name, | |
| bool is_shared, gres_node_state_t *gres_ns, | |
| char **reason_down) | |
| { | |
| gres_chunk_domain_t *dom = NULL; | |
| char *tmp = NULL; | |
| uint32_t *sizes = NULL; | |
| int *order = NULL, *devs = NULL, sizes_cnt = 0, order_cnt = 0; | |
| uint64_t dev_cnt = bit_set_count(build->devs); | |
| /* | |
| * alt_gres pairs a sharing GRES with its shared one. Shared | |
| * allocations are tracked on the shared state, so the chunk selection | |
| * cannot see the devices they consume and would hand out a chunk that | |
| * is already carved up. | |
| */ | |
| if (is_shared || gres_ns->alt_gres) { | |
| tmp = xstrdup("ChunkSizes is not supported for shared GRES, or for a GRES that has one configured on it"); | |
| goto end_it; | |
| } | |
| if (!dev_cnt) { | |
| tmp = xstrdup("ChunkSizes is configured but no devices were found"); | |
| goto end_it; | |
| } | |
| if (dev_cnt > GRES_MAX_CHUNK_SIZE) { | |
| tmp = xstrdup_printf("ChunkSizes supports at most %d devices, but %"PRIu64" are configured", | |
| GRES_MAX_CHUNK_SIZE, dev_cnt); | |
| goto end_it; | |
| } | |
| if (gres_chunk_sizes_parse(build->sizes, &sizes, &sizes_cnt) != | |
| SLURM_SUCCESS) { | |
| tmp = xstrdup_printf("Invalid ChunkSizes value (%s)", | |
| build->sizes); | |
| goto end_it; | |
| } | |
| if (sizes[sizes_cnt - 1] > dev_cnt) { | |
| tmp = xstrdup_printf("ChunkSizes value (%s) is larger than the %"PRIu64" devices configured", | |
| build->sizes, dev_cnt); | |
| goto end_it; | |
| } | |
| if (dev_cnt % sizes[sizes_cnt - 1]) { | |
| tmp = xstrdup_printf("ChunkSizes largest value %u does not divide the %"PRIu64" devices configured evenly", | |
| sizes[sizes_cnt - 1], dev_cnt); | |
| goto end_it; | |
| } | |
| if (build->order) { | |
| if (gres_chunk_order_parse(build->order, &order, &order_cnt) != | |
| SLURM_SUCCESS) { | |
| tmp = xstrdup_printf("Invalid ChunkOrder value (%s)", | |
| build->order); | |
| goto end_it; | |
| } | |
| if ((uint64_t) order_cnt != dev_cnt) { | |
| tmp = xstrdup_printf("ChunkOrder value (%s) has %d entries but %"PRIu64" devices are configured", | |
| build->order, order_cnt, dev_cnt); | |
| goto end_it; | |
| } | |
| } else { | |
| /* No ChunkOrder, so chunk position is enumeration order */ | |
| order = xcalloc(dev_cnt, sizeof(int)); | |
| for (int i = 0; i < (int) dev_cnt; i++) | |
| order[i] = i; | |
| order_cnt = dev_cnt; | |
| } | |
| /* ChunkOrder numbers the Type's devices, so map them to indexes */ | |
| devs = xcalloc(order_cnt, sizeof(int)); | |
| for (int i = 0, inx = -1; i < order_cnt; i++) | |
| devs[i] = inx = bit_ffs_from_bit(build->devs, inx + 1); | |
| for (int i = 0; i < order_cnt; i++) | |
| order[i] = devs[order[i]]; | |
| xfree(devs); | |
| xrecalloc(gres_ns->chunk_domains, (gres_ns->chunk_domain_cnt + 1), | |
| sizeof(*gres_ns->chunk_domains)); | |
| dom = &gres_ns->chunk_domains[gres_ns->chunk_domain_cnt++]; | |
| dom->order = order; | |
| dom->order_len = order_cnt; | |
| dom->sizes = sizes; | |
| dom->sizes_cnt = sizes_cnt; | |
| dom->type_id = build->type_id; | |
| dom->sizes_str = xstrdup(build->sizes); | |
| dom->order_str = xstrdup(build->order); | |
| log_flag(GRES, "%s: node %s: built %d device chunks from ChunkSizes=%s ChunkOrder=%s", | |
| __func__, node_name, sizes_cnt, build->sizes, | |
| build->order ? build->order : "(identity)"); | |
| return SLURM_SUCCESS; | |
| end_it: | |
| xfree(sizes); | |
| xfree(order); | |
| error("%s: %s on node %s", __func__, tmp, node_name); | |
| if (reason_down && !(*reason_down)) { | |
| *reason_down = tmp; | |
| tmp = NULL; | |
| } else { | |
| xfree(tmp); | |
| } | |
| return SLURM_ERROR; | |
| } | |
| static bool _valid_gres_types(char *gres_name, gres_node_state_t *gres_ns, | |
| char **reason_down) | |
| { | |
| bool rc = true; | |
| uint64_t gres_cnt_found = 0, gres_sum; | |
| int topo_inx, type_inx; | |
| if ((gres_ns->type_cnt == 0) || (gres_ns->topo_cnt == 0)) | |
| return rc; | |
| for (type_inx = 0; type_inx < gres_ns->type_cnt; type_inx++) { | |
| gres_cnt_found = 0; | |
| for (topo_inx = 0; topo_inx < gres_ns->topo_cnt; topo_inx++) { | |
| if (gres_ns->topo_type_id[topo_inx] != | |
| gres_ns->type_id[type_inx]) | |
| continue; | |
| gres_sum = gres_cnt_found + | |
| gres_ns->topo_gres_cnt_avail[topo_inx]; | |
| if (gres_sum > gres_ns->type_cnt_avail[type_inx]) { | |
| gres_ns->topo_gres_cnt_avail[topo_inx] -= | |
| (gres_sum - | |
| gres_ns->type_cnt_avail[type_inx]); | |
| } | |
| gres_cnt_found += | |
| gres_ns->topo_gres_cnt_avail[topo_inx]; | |
| } | |
| if (gres_cnt_found < gres_ns->type_cnt_avail[type_inx]) { | |
| rc = false; | |
| break; | |
| } | |
| } | |
| if (!rc && reason_down && (*reason_down == NULL)) { | |
| xstrfmtcat(*reason_down, | |
| "%s:%s count too low (%"PRIu64" < %"PRIu64")", | |
| gres_name, gres_ns->type_name[type_inx], | |
| gres_cnt_found, gres_ns->type_cnt_avail[type_inx]); | |
| } | |
| return rc; | |
| } | |
| /* | |
| * Count-only GRES keep their drain in type_drain and untyped_drain. A | |
| * device bitmap or a device group owns the scalar instead. | |
| */ | |
| static bool _gres_count_only_drain(gres_node_state_t *gres_ns) | |
| { | |
| return !gres_ns->gres_bit_drain && !gres_ns->drain_groups; | |
| } | |
| /* type < 0 is the untyped pool. The caller borrows the record. */ | |
| static drain_count_t *_gres_cnt_pool(gres_node_state_t *gres_ns, int type) | |
| { | |
| if (type < 0) | |
| return &gres_ns->untyped_drain; | |
| if ((type >= gres_ns->type_cnt) || !gres_ns->type_name[type]) | |
| return NULL; | |
| return drained_tres_count_find(gres_ns->type_drain, | |
| gres_ns->type_name[type]); | |
| } | |
| static void _gres_cnt_pool_set(gres_node_state_t *gres_ns, int type, | |
| uint64_t count, const char *reason, | |
| bool replace_reason) | |
| { | |
| if (type < 0) { | |
| drained_tres_count_set(&gres_ns->untyped_drain, count, reason, | |
| replace_reason); | |
| return; | |
| } | |
| if ((type >= gres_ns->type_cnt) || !gres_ns->type_name[type]) | |
| return; | |
| drained_tres_count_list_set(&gres_ns->type_drain, | |
| gres_ns->type_name[type], count, reason, | |
| replace_reason); | |
| } | |
| static uint64_t _gres_cnt_pool_count(gres_node_state_t *gres_ns, int type) | |
| { | |
| drain_count_t *pool = _gres_cnt_pool(gres_ns, type); | |
| return pool ? pool->count : 0; | |
| } | |
| /* | |
| * Clamp the drain counts of count-tracked (non-file) GRES to what is still | |
| * configured, and re-sum the per-type pools into the scalar total, after the | |
| * drained or configured counts change. Device-tracked GRES derive these counts | |
| * from their bitmaps elsewhere, and their groups are left alone. Does not | |
| * touch gres_cnt_avail. | |
| */ | |
| static void _gres_drain_cnt_clamp(gres_node_state_t *gres_ns) | |
| { | |
| if (!_gres_count_only_drain(gres_ns)) { | |
| xfree(gres_ns->gres_used); | |
| return; | |
| } | |
| if (gres_ns->type_cnt) { | |
| /* | |
| * Typed GRES: clamp each type's pool to what that type still | |
| * has available, and re-sum the pools into the scalar total. | |
| * A pool whose count falls to zero is deleted with its reason. | |
| */ | |
| gres_ns->gres_cnt_drain = 0; | |
| for (int i = 0; i < gres_ns->type_cnt; i++) { | |
| drain_count_t *pool; | |
| uint64_t count; | |
| pool = drained_tres_count_find(gres_ns->type_drain, | |
| gres_ns->type_name[i]); | |
| if (!pool) | |
| continue; | |
| count = MIN(pool->count, gres_ns->type_cnt_avail[i]); | |
| /* | |
| * replace_reason is false, so a surviving pool keeps | |
| * its reason. Zero deletes the pool. | |
| */ | |
| drained_tres_count_list_set(&gres_ns->type_drain, | |
| gres_ns->type_name[i], | |
| count, NULL, false); | |
| gres_ns->gres_cnt_drain += count; | |
| } | |
| } else if (gres_ns->gres_cnt_config != NO_VAL64) { | |
| uint64_t count = 0; | |
| /* | |
| * Untyped GRES: one embedded pool, clamped to the configured | |
| * count. The scalar total is that pool. | |
| */ | |
| if (gres_ns->untyped_drain.count) { | |
| count = MIN(gres_ns->untyped_drain.count, | |
| gres_ns->gres_cnt_config); | |
| drained_tres_count_set(&gres_ns->untyped_drain, count, | |
| NULL, false); | |
| } | |
| gres_ns->gres_cnt_drain = count; | |
| } | |
| /* Nothing drained: clear an untyped pool left behind. */ | |
| if (!gres_ns->gres_cnt_drain) | |
| drained_tres_count_set(&gres_ns->untyped_drain, 0, NULL, false); | |
| /* gres_used is now stale; force it to be rebuilt on next use. */ | |
| xfree(gres_ns->gres_used); | |
| } | |
| /* | |
| * Width of the device index space the drain groups are indexed by, which is | |
| * that of the drain bitmap they parallel. | |
| */ | |
| static int _gres_drain_bits(gres_node_state_t *gres_ns) | |
| { | |
| return gres_ns->gres_bit_drain ? bit_size(gres_ns->gres_bit_drain) : 0; | |
| } | |
| /* A saved UUID map with an empty slot. That device is not on the node. */ | |
| static bool _gres_slot_absent(gres_node_state_t *gres_ns, int i) | |
| { | |
| if (!gres_ns->dev_uuid || (i < 0) || (i >= gres_ns->dev_uuid_cnt)) | |
| return false; | |
| return !gres_ns->dev_uuid[i] || !gres_ns->dev_uuid[i][0]; | |
| } | |
| /* | |
| * Drop every drain that an admin can clear. An empty UUID slot stays | |
| * drained until a device fills it. A present device's UUID record is | |
| * dropped with its bit, so a later registration does not restore the | |
| * drain. Count-only pools have no slot, so they are cleared. | |
| * gres_cnt_drain is then the absent slots that remain. | |
| */ | |
| static void _gres_drain_clear(gres_node_state_t *gres_ns) | |
| { | |
| if (gres_ns->gres_bit_drain) { | |
| int n = bit_size(gres_ns->gres_bit_drain); | |
| for (int i = 0; i < n; i++) { | |
| if (!bit_test(gres_ns->gres_bit_drain, i)) | |
| continue; | |
| if (_gres_slot_absent(gres_ns, i)) | |
| continue; | |
| if (gres_ns->dev_uuid && (i < gres_ns->dev_uuid_cnt) && | |
| gres_ns->dev_uuid[i] && gres_ns->dev_uuid[i][0]) | |
| drained_tres_group_id_clear( | |
| &gres_ns->drain_groups, | |
| gres_ns->dev_uuid[i]); | |
| _gres_drain_clear_bit(gres_ns, i); | |
| } | |
| gres_ns->gres_cnt_drain = | |
| bit_set_count(gres_ns->gres_bit_drain); | |
| } else { | |
| gres_ns->gres_cnt_drain = 0; | |
| } | |
| FREE_NULL_LIST(gres_ns->type_drain); | |
| drained_tres_count_clear(&gres_ns->untyped_drain); | |
| /* The drained devices are part of what gres_used renders. */ | |
| xfree(gres_ns->gres_used); | |
| } | |
| /* | |
| * Record the reason a count-only pool was drained. The pool already holds the | |
| * count; this does not create one. type < 0 is the untyped pool. | |
| */ | |
| static void _gres_type_drain_reason_set(gres_node_state_t *gres_ns, int type, | |
| char *reason) | |
| { | |
| drain_count_t *pool = _gres_cnt_pool(gres_ns, type); | |
| if (!pool || !pool->count) | |
| return; | |
| /* Keep the reason that is there, so the pool stays packable. */ | |
| if (!reason || !reason[0]) | |
| return; | |
| if (!xstrcmp(pool->reason, reason)) | |
| return; | |
| xfree(pool->reason); | |
| pool->reason = xstrdup(reason); | |
| } | |
| /* Reason a count-only GRES type was drained, never NULL. */ | |
| static char *_gres_type_drain_reason(gres_node_state_t *gres_ns, int type) | |
| { | |
| drain_count_t *pool = _gres_cnt_pool(gres_ns, type); | |
| char *reason = NULL; | |
| if (pool) | |
| reason = pool->reason; | |
| if (!reason && (type >= 0)) | |
| reason = gres_ns->untyped_drain.reason; | |
| return reason ? reason : DRAINED_TRES_NO_REASON; | |
| } | |
| static void _gres_drain_set_bit(gres_node_state_t *gres_ns, int bit, | |
| char *reason) | |
| { | |
| xassert(gres_ns->gres_bit_drain); | |
| xassert(bit < bit_size(gres_ns->gres_bit_drain)); | |
| if (!bit_test(gres_ns->gres_bit_drain, bit)) | |
| gres_ns->gres_cnt_drain++; | |
| bit_set(gres_ns->gres_bit_drain, bit); | |
| drained_tres_group_set(&gres_ns->drain_groups, bit, | |
| _gres_drain_bits(gres_ns), reason); | |
| } | |
| static void _gres_drain_clear_bit(gres_node_state_t *gres_ns, int bit) | |
| { | |
| xassert(gres_ns->gres_bit_drain); | |
| xassert(bit < bit_size(gres_ns->gres_bit_drain)); | |
| if (bit_test(gres_ns->gres_bit_drain, bit) && gres_ns->gres_cnt_drain) | |
| gres_ns->gres_cnt_drain--; | |
| bit_clear(gres_ns->gres_bit_drain, bit); | |
| drained_tres_group_clear(&gres_ns->drain_groups, bit); | |
| } | |
| static void _gres_bit_drain_resize(gres_node_state_t *gres_ns, | |
| uint64_t gres_bits) | |
| { | |
| if (!gres_bits) { | |
| FREE_NULL_BITMAP(gres_ns->gres_bit_drain); | |
| drained_tres_groups_resize(&gres_ns->drain_groups, 0); | |
| return; | |
| } | |
| if (!gres_ns->gres_bit_drain) | |
| gres_ns->gres_bit_drain = bit_alloc(gres_bits); | |
| else | |
| bit_realloc(gres_ns->gres_bit_drain, gres_bits); | |
| /* | |
| * Shrinking drops the reasons of the devices that went away with the | |
| * bits they were held by. A group that also holds a UUID survives that. | |
| */ | |
| drained_tres_groups_resize(&gres_ns->drain_groups, gres_bits); | |
| } | |
| /* | |
| * Drop the saved UUID map and allocate dev_cnt entries for a rebuild from | |
| * slurmd's report. Callers fill the entries from gres_slurmd_conf. | |
| * | |
| * dev_uuid == NULL means this GRES has no UUID map and keeps slurmd's | |
| * order. dev_cnt 0 leaves the pointer NULL. | |
| * | |
| * dev_uuid[i] == NULL, with the array itself non-NULL, is an absent device. | |
| * The UUID-order path keeps that entry in the saved map and does not | |
| * rebuild here. | |
| */ | |
| static void _dev_uuid_resize(gres_node_state_t *gres_ns, uint32_t dev_cnt) | |
| { | |
| for (int i = 0; i < gres_ns->dev_uuid_cnt; i++) | |
| xfree(gres_ns->dev_uuid[i]); | |
| xfree(gres_ns->dev_uuid); | |
| gres_ns->dev_uuid_cnt = 0; | |
| if (!dev_cnt) | |
| return; | |
| gres_ns->dev_uuid = xcalloc(dev_cnt, sizeof(char *)); | |
| gres_ns->dev_uuid_cnt = dev_cnt; | |
| } | |
| static int _count_group_ids(void *x, void *arg) | |
| { | |
| drain_group_t *group = x; | |
| uint32_t *cnt = arg; | |
| if (group->ids) | |
| *cnt += list_count(group->ids); | |
| return 0; | |
| } | |
| /* How many device UUIDs drain_groups holds. */ | |
| static uint32_t _gres_drain_uuid_cnt(gres_node_state_t *gres_ns) | |
| { | |
| uint32_t cnt = 0; | |
| if (gres_ns->drain_groups) | |
| (void) list_for_each(gres_ns->drain_groups, _count_group_ids, | |
| &cnt); | |
| return cnt; | |
| } | |
| /* | |
| * Resolve a device UUID to its current bitmap index using the dev_uuid | |
| * map. Sets *rc and returns -1 on failure: ESLURM_INVALID_GRES for an empty or | |
| * ambiguous (same UUID on >1 device) token, ESLURM_GRES_UUID_NOT_FOUND if no | |
| * device currently has that UUID. | |
| */ | |
| static int _gres_uuid_to_index(gres_node_state_t *gres_ns, const char *uuid, | |
| int *rc) | |
| { | |
| int found = -1; | |
| if (!uuid || !uuid[0]) { | |
| *rc = ESLURM_INVALID_GRES; | |
| return -1; | |
| } | |
| for (int i = 0; i < gres_ns->dev_uuid_cnt; i++) { | |
| if (gres_ns->dev_uuid[i] && | |
| !xstrcmp(gres_ns->dev_uuid[i], uuid)) { | |
| if (found >= 0) { | |
| *rc = ESLURM_INVALID_GRES; | |
| return -1; | |
| } | |
| found = i; | |
| } | |
| } | |
| if (found < 0) | |
| *rc = ESLURM_GRES_UUID_NOT_FOUND; | |
| return found; | |
| } | |
| static void _gres_bit_alloc_resize(gres_node_state_t *gres_ns, | |
| uint64_t gres_bits) | |
| { | |
| /* | |
| * gres_bit_drain is indexed like gres_bit_alloc and the two are | |
| * compared bit for bit, so it follows every change of width. The | |
| * drains of devices cut off go with them. | |
| */ | |
| if (gres_ns->gres_bit_drain && | |
| (gres_bits != bit_size(gres_ns->gres_bit_drain))) { | |
| _gres_bit_drain_resize(gres_ns, gres_bits); | |
| gres_ns->gres_cnt_drain = 0; | |
| if (gres_ns->gres_bit_drain) | |
| gres_ns->gres_cnt_drain = | |
| bit_set_count(gres_ns->gres_bit_drain); | |
| } | |
| if (!gres_bits) { | |
| FREE_NULL_BITMAP(gres_ns->gres_bit_alloc); | |
| return; | |
| } | |
| if (!gres_ns->gres_bit_alloc) | |
| gres_ns->gres_bit_alloc = bit_alloc(gres_bits); | |
| else if (gres_bits != bit_size(gres_ns->gres_bit_alloc)) | |
| bit_realloc(gres_ns->gres_bit_alloc, gres_bits); | |
| } | |
| /* | |
| * Job scheduling handles gres affinity on a socket basis internally. | |
| * However, the interface for setting affinity is to specify cores. This can | |
| * lead to the faulty expectation that the core affinity will be respected by | |
| * the Slurm scheduler. | |
| * | |
| * Therefore this check was added to avoid users setting the cores limit and | |
| * expecting Slurm to respect it (which it doesn't and never has). | |
| * | |
| * In addition to misleading users, a bug can arise where steps and jobs don't | |
| * line up because steps do look at the cores rather than the sockets like the | |
| * jobs. (i.e. job allocates a core the the step rejects), if we just wanted to | |
| * solve this bug we would just expand the cpu list to fill the socket here | |
| * instead of throwing an error. | |
| */ | |
| static int _check_core_range_matches_sock(bitstr_t *tmp_bitmap, | |
| rebuild_topo_t *rebuild_topo, | |
| gres_slurmd_conf_t *gres_slurmd_conf) | |
| { | |
| int total_core_cnt = bit_set_count(tmp_bitmap); | |
| slurm_gres_context_t *gres_ctx = rebuild_topo->gres_ctx; | |
| gres_node_state_t *gres_ns = rebuild_topo->gres_ns; | |
| char *gres_cores_str, *tmp; | |
| int first, last, core_cnt; | |
| if (total_core_cnt == bit_size(tmp_bitmap)) | |
| return SLURM_SUCCESS; | |
| for (int i = 0; (i < rebuild_topo->sock_cnt); i++) { | |
| first = i * rebuild_topo->cores_per_sock; | |
| last = (i + 1) * rebuild_topo->cores_per_sock; | |
| core_cnt = bit_set_count_range(tmp_bitmap, first, last); | |
| if (!core_cnt || /* No cores set */ | |
| ((core_cnt == rebuild_topo->cores_per_sock) && | |
| (core_cnt == total_core_cnt))) /* Cores match 1 socket */ | |
| continue; | |
| gres_cores_str = bit_fmt_full(tmp_bitmap); | |
| if (gres_slurmd_conf->config_flags & GRES_CONF_AUTODETECT) { | |
| tmp = xstrdup_printf( | |
| "%s GRES autodetected core affinity %s on node %s doesn't match socket boundaries. (Socket %d is cores %d-%d). " | |
| "See Socket Affinity section of gres.html for more information.", | |
| gres_ctx->gres_type, gres_cores_str, | |
| rebuild_topo->node_name, i, first, (last - 1)); | |
| } else { | |
| tmp = xstrdup_printf( | |
| "%s GRES core specification %s for node %s doesn't match socket boundaries. (Socket %d is cores %d-%d)", | |
| gres_ctx->gres_type, gres_cores_str, | |
| rebuild_topo->node_name, i, first, (last - 1)); | |
| } | |
| xfree(gres_cores_str); | |
| FREE_NULL_BITMAP(gres_ns->topo_core_bitmap[rebuild_topo | |
| ->topo_cnt]); | |
| rebuild_topo->rc = EINVAL; | |
| error("%s: %s", __func__, tmp); | |
| if (rebuild_topo->reason_down && !(*rebuild_topo->reason_down)) | |
| xstrfmtcat(*rebuild_topo->reason_down, "%s", tmp); | |
| xfree(tmp); | |
| return SLURM_ERROR; | |
| } | |
| return SLURM_SUCCESS; | |
| } | |
| /* | |
| * Add a record to the chunk configuration of its Type. Every record of a Type | |
| * must set the same ChunkSizes and ChunkOrder, or all set neither. | |
| * IN gres_slurmd_conf - record with devices on this node | |
| * IN/OUT rebuild_topo - collected chunk configuration | |
| * RET devices of the record's Type to add to, NULL if it sets no ChunkSizes | |
| */ | |
| static bitstr_t *_add_chunk_build(gres_slurmd_conf_t *gres_slurmd_conf, | |
| rebuild_topo_t *rebuild_topo) | |
| { | |
| uint32_t type_id = gres_build_id(gres_slurmd_conf->type_name); | |
| chunk_build_t *build = NULL; | |
| for (int i = 0; i < rebuild_topo->chunk_build_cnt; i++) { | |
| if (rebuild_topo->chunk_builds[i].type_id == type_id) { | |
| build = &rebuild_topo->chunk_builds[i]; | |
| break; | |
| } | |
| } | |
| if (!build) { | |
| xrecalloc(rebuild_topo->chunk_builds, | |
| (rebuild_topo->chunk_build_cnt + 1), | |
| sizeof(*rebuild_topo->chunk_builds)); | |
| build = &rebuild_topo->chunk_builds | |
| [rebuild_topo->chunk_build_cnt++]; | |
| build->type_id = type_id; | |
| build->sizes = gres_slurmd_conf->chunk_sizes; | |
| build->order = gres_slurmd_conf->chunk_order; | |
| if (build->sizes) | |
| build->devs = bit_alloc(rebuild_topo->dev_cnt); | |
| } else if (xstrcmp(build->sizes, gres_slurmd_conf->chunk_sizes) || | |
| xstrcmp(build->order, gres_slurmd_conf->chunk_order)) { | |
| rebuild_topo->chunk_mismatch = true; | |
| } | |
| return build->devs; | |
| } | |
| static int _foreach_rebuild_topo(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x; | |
| rebuild_topo_t *rebuild_topo = arg; | |
| slurm_gres_context_t *gres_ctx = rebuild_topo->gres_ctx; | |
| gres_node_state_t *gres_ns = rebuild_topo->gres_ns; | |
| int topo_cnt = rebuild_topo->topo_cnt; | |
| bitstr_t *chunk_devs = NULL; | |
| if (gres_slurmd_conf->plugin_id != gres_ctx->plugin_id) | |
| return 0; | |
| if (gres_ns->gres_bit_alloc && !gres_id_shared(gres_ctx->config_flags)) | |
| gres_ns->topo_gres_cnt_alloc[topo_cnt] = 0; | |
| gres_ns->topo_gres_cnt_avail[topo_cnt] = gres_slurmd_conf->count; | |
| if (gres_slurmd_conf->cpus) { | |
| /* NOTE: gres_slurmd_conf->cpus is cores */ | |
| bitstr_t *tmp_bitmap = bit_alloc(rebuild_topo->core_cnt); | |
| int ret = bit_unfmt(tmp_bitmap, gres_slurmd_conf->cpus); | |
| if (ret != SLURM_SUCCESS) { | |
| error("%s: %s: invalid GRES core specification (%s) on node %s", | |
| __func__, gres_ctx->gres_type, | |
| gres_slurmd_conf->cpus, | |
| rebuild_topo->node_name); | |
| FREE_NULL_BITMAP(tmp_bitmap); | |
| rebuild_topo->rc = ESLURM_INVALID_GRES; | |
| return -1; | |
| } else { | |
| FREE_NULL_BITMAP( | |
| gres_ns->topo_core_bitmap[topo_cnt]); | |
| gres_ns->topo_core_bitmap[topo_cnt] = tmp_bitmap; | |
| } | |
| if (_check_core_range_matches_sock(tmp_bitmap, rebuild_topo, | |
| gres_slurmd_conf)) | |
| return -1; | |
| rebuild_topo->cpus_config = rebuild_topo->core_cnt; | |
| } else if (rebuild_topo->cpus_config && !rebuild_topo->cpu_config_err) { | |
| rebuild_topo->cpu_config_err = true; | |
| error("%s: %s: has CPUs configured for only some of the records on node %s", | |
| __func__, gres_ctx->gres_type, rebuild_topo->node_name); | |
| } | |
| if (gres_slurmd_conf->links) { | |
| if (gres_ns->links_cnt && | |
| (gres_ns->link_len != rebuild_topo->tot_gres_cnt)) { | |
| /* Size changed, need to rebuild */ | |
| for (int j = 0; j < gres_ns->link_len; j++) | |
| xfree(gres_ns->links_cnt[j]); | |
| xfree(gres_ns->links_cnt); | |
| } | |
| if (!gres_ns->links_cnt) { | |
| gres_ns->link_len = rebuild_topo->tot_gres_cnt; | |
| gres_ns->links_cnt = xcalloc(rebuild_topo->tot_gres_cnt, | |
| sizeof(int *)); | |
| for (int j = 0; j < rebuild_topo->tot_gres_cnt; j++) { | |
| gres_ns->links_cnt[j] = | |
| xcalloc(rebuild_topo->tot_gres_cnt, | |
| sizeof(int)); | |
| } | |
| } | |
| } | |
| /* | |
| * A Type's domain can span several records, so collect it first. Skip | |
| * a node with no usable devices, which would give a zero-size bitmap. | |
| */ | |
| if (gres_slurmd_conf->count && rebuild_topo->dev_cnt) | |
| chunk_devs = _add_chunk_build(gres_slurmd_conf, rebuild_topo); | |
| if (gres_id_shared(gres_slurmd_conf->config_flags)) { | |
| /* If running jobs recovered then already set */ | |
| if (!gres_ns->topo_gres_bitmap[topo_cnt]) { | |
| gres_ns->topo_gres_bitmap[topo_cnt] = | |
| bit_alloc(rebuild_topo->dev_cnt); | |
| bit_set(gres_ns->topo_gres_bitmap[topo_cnt], | |
| rebuild_topo->gres_inx); | |
| } | |
| rebuild_topo->gres_inx++; | |
| } else if (!rebuild_topo->dev_cnt) { | |
| /* | |
| * Slurmd found GRES, but slurmctld can't use | |
| * them. Avoid creating zero-size bitmaps. | |
| */ | |
| rebuild_topo->has_file = false; | |
| } else { | |
| FREE_NULL_BITMAP(gres_ns->topo_gres_bitmap[topo_cnt]); | |
| gres_ns->topo_gres_bitmap[topo_cnt] = | |
| bit_alloc(rebuild_topo->dev_cnt); | |
| for (int j = 0; j < gres_slurmd_conf->count; j++) { | |
| if (rebuild_topo->gres_inx >= rebuild_topo->dev_cnt) { | |
| /* Ignore excess GRES on node */ | |
| break; | |
| } | |
| bit_set(gres_ns->topo_gres_bitmap[topo_cnt], | |
| rebuild_topo->gres_inx); | |
| if (chunk_devs) | |
| bit_set(chunk_devs, rebuild_topo->gres_inx); | |
| if (gres_ns->gres_bit_alloc && | |
| bit_test(gres_ns->gres_bit_alloc, | |
| rebuild_topo->gres_inx)) { | |
| /* Set by recovered job */ | |
| gres_ns->topo_gres_cnt_alloc[topo_cnt]++; | |
| } | |
| if (_links_str2array( | |
| gres_slurmd_conf->links, | |
| rebuild_topo->node_name, gres_ns, | |
| rebuild_topo->gres_inx, | |
| rebuild_topo->tot_gres_cnt, | |
| rebuild_topo->reason_down) != SLURM_SUCCESS) | |
| rebuild_topo->rc = EINVAL; | |
| /* | |
| * Record the reported UUID at slurmd's index. This runs | |
| * when the saved UUID order is not being kept. The map | |
| * exists only when every device reported a UUID, so | |
| * this fills every entry. | |
| */ | |
| if (gres_ns->dev_uuid && | |
| (rebuild_topo->gres_inx < gres_ns->dev_uuid_cnt)) { | |
| xfree(gres_ns->dev_uuid[ | |
| rebuild_topo->gres_inx]); | |
| gres_ns->dev_uuid[rebuild_topo->gres_inx] = | |
| xstrdup(gres_slurmd_conf->unique_id); | |
| } | |
| rebuild_topo->gres_inx++; | |
| } | |
| } | |
| gres_ns->topo_type_id[topo_cnt] = | |
| gres_build_id(gres_slurmd_conf->type_name); | |
| xfree(gres_ns->topo_type_name[topo_cnt]); | |
| gres_ns->topo_type_name[topo_cnt] = | |
| xstrdup(gres_slurmd_conf->type_name); | |
| rebuild_topo->topo_cnt++; | |
| if (rebuild_topo->topo_cnt >= gres_ns->topo_cnt) | |
| return -1; | |
| return 0; | |
| } | |
| static int _foreach_rebuild_topo_no_cpus(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x; | |
| rebuild_topo_t *rebuild_topo = arg; | |
| slurm_gres_context_t *gres_ctx = rebuild_topo->gres_ctx; | |
| gres_node_state_t *gres_ns = rebuild_topo->gres_ns; | |
| if (gres_slurmd_conf->plugin_id != gres_ctx->plugin_id) | |
| return 0; | |
| for (int j = 0; j < rebuild_topo->topo_cnt; j++) { | |
| if (gres_ns->topo_core_bitmap[j]) | |
| continue; | |
| gres_ns->topo_core_bitmap[j] = | |
| bit_alloc(rebuild_topo->core_cnt); | |
| bit_set_all(gres_ns->topo_core_bitmap[j]); | |
| } | |
| return 0; | |
| } | |
| static int _foreach_add_gres_info(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x; | |
| add_gres_info_t *add_gres_info = arg; | |
| slurm_gres_context_t *gres_ctx = add_gres_info->gres_ctx; | |
| gres_node_state_t *gres_ns = add_gres_info->gres_ns; | |
| uint32_t type_id; | |
| int i; | |
| if (gres_slurmd_conf->plugin_id != gres_ctx->plugin_id) | |
| return 0; | |
| type_id = gres_build_id(gres_slurmd_conf->type_name); | |
| for (i = 0; i < gres_ns->type_cnt; i++) { | |
| if (type_id == gres_ns->type_id[i]) | |
| break; | |
| } | |
| if (i < gres_ns->type_cnt) { | |
| /* Update count as needed */ | |
| gres_ns->type_cnt_avail[i] = gres_slurmd_conf->count; | |
| } else { | |
| gres_add_type(gres_slurmd_conf->type_name, | |
| gres_ns, | |
| gres_slurmd_conf->count); | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Compare a registered gres.conf record with the chunk domain of its Type. | |
| * RET -1 if they differ, 0 otherwise | |
| */ | |
| static int _foreach_chunk_changed(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x; | |
| chunk_changed_args_t *args = arg; | |
| gres_node_state_t *gres_ns = args->gres_ns; | |
| uint32_t type_id; | |
| int d; | |
| if ((gres_slurmd_conf->plugin_id != args->plugin_id) || | |
| !gres_slurmd_conf->count) | |
| return 0; | |
| type_id = gres_build_id(gres_slurmd_conf->type_name); | |
| for (d = 0; d < gres_ns->chunk_domain_cnt; d++) { | |
| if (gres_ns->chunk_domains[d].type_id == type_id) | |
| break; | |
| } | |
| /* A Type without a domain only differs if it now has chunks */ | |
| if (d == gres_ns->chunk_domain_cnt) | |
| return gres_slurmd_conf->chunk_sizes ? -1 : 0; | |
| if (xstrcmp(gres_ns->chunk_domains[d].sizes_str, | |
| gres_slurmd_conf->chunk_sizes) || | |
| xstrcmp(gres_ns->chunk_domains[d].order_str, | |
| gres_slurmd_conf->chunk_order)) | |
| return -1; | |
| args->dev_cnt[d] += gres_slurmd_conf->count; | |
| return 0; | |
| } | |
| /* | |
| * Test whether a node's registered chunk configuration differs from its chunk | |
| * domains: a Type gained or lost ChunkSizes, changed ChunkSizes or ChunkOrder, | |
| * or now has a different device count. | |
| * IN gres_ns - node state holding the domains | |
| * IN plugin_id - GRES the registered records are for | |
| * RET true if the domains must be rebuilt | |
| */ | |
| static bool _chunk_config_changed(gres_node_state_t *gres_ns, | |
| uint32_t plugin_id) | |
| { | |
| chunk_changed_args_t args = { | |
| .gres_ns = gres_ns, | |
| .plugin_id = plugin_id, | |
| }; | |
| bool changed; | |
| if (gres_ns->chunk_domain_cnt) | |
| args.dev_cnt = xcalloc(gres_ns->chunk_domain_cnt, | |
| sizeof(*args.dev_cnt)); | |
| changed = (list_for_each(gres_conf_list, _foreach_chunk_changed, | |
| &args) < 0); | |
| for (int d = 0; !changed && (d < gres_ns->chunk_domain_cnt); d++) { | |
| if (args.dev_cnt[d] != gres_ns->chunk_domains[d].order_len) | |
| changed = true; | |
| } | |
| xfree(args.dev_cnt); | |
| return changed; | |
| } | |
| static bool _uuid_eq(const char *a, const char *b) | |
| { | |
| if (!a || !a[0] || !b || !b[0]) | |
| return false; | |
| return !xstrcmp(a, b); | |
| } | |
| static bool _uuid_in(char **list, int n, const char *uuid) | |
| { | |
| for (int i = 0; i < n; i++) { | |
| if (_uuid_eq(list[i], uuid)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| /* True when uuid is a saved device after index i. */ | |
| static bool _uuid_later(char **saved, int n, int i, const char *uuid) | |
| { | |
| for (int k = i + 1; k < n; k++) { | |
| if (_uuid_eq(saved[k], uuid)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| static bool _not_found_reason(const char *reason) | |
| { | |
| const char *mark; | |
| if (!reason) | |
| return false; | |
| mark = xstrstr(reason, ":not_found"); | |
| return mark && !mark[strlen(":not_found")]; | |
| } | |
| static char *_uuid_reason(gres_node_state_t *gres_ns, const char *uuid) | |
| { | |
| drain_group_t *group = | |
| drained_tres_group_by_id(gres_ns->drain_groups, uuid); | |
| return group ? group->reason : NULL; | |
| } | |
| static int _find_dev_without_uuid(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *conf = x; | |
| uint32_t *plugin_id = arg; | |
| if (conf->plugin_id != *plugin_id) | |
| return 0; | |
| return (conf->count != 1) || !conf->unique_id || !conf->unique_id[0]; | |
| } | |
| /* | |
| * A UUID map can tell an absent device apart only when every device of this | |
| * GRES reported its own UUID. Otherwise the GRES gets no map at all, rather | |
| * than a map whose UUID-less devices read as absent. | |
| */ | |
| static bool _all_devs_have_uuid(uint32_t plugin_id) | |
| { | |
| return gres_conf_list && | |
| !list_find_first_ro(gres_conf_list, _find_dev_without_uuid, | |
| &plugin_id); | |
| } | |
| static int _uuid_rep_add(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *conf = x; | |
| uuid_rep_t *rep = arg; | |
| if (conf->plugin_id != rep->plugin_id) | |
| return 0; | |
| if (rep->n && _uuid_in(rep->uuid, rep->n, conf->unique_id)) { | |
| rep->bad = true; | |
| return 0; | |
| } | |
| if (rep->n == rep->cap) { | |
| rep->cap = rep->cap ? rep->cap * 2 : 4; | |
| rep->uuid = xrealloc(rep->uuid, rep->cap * sizeof(char *)); | |
| } | |
| rep->uuid[rep->n++] = conf->unique_id; | |
| return 0; | |
| } | |
| /* | |
| * Fit slurmd's report into the saved UUID order. | |
| * A saved UUID that matches the next reported UUID keeps its index. | |
| * A NULL index is filled only by a UUID that is not one of the later | |
| * saved devices. Anything else (a remaining UUID slid down, a new UUID | |
| * left over) is refused and *out is not returned. | |
| * RET 0 and *out set, or -1. | |
| */ | |
| static int _uuid_align(char **saved, int saved_n, char **rep, int rep_n, | |
| char ***out) | |
| { | |
| char **next = xcalloc(saved_n, sizeof(char *)); | |
| int j = 0; | |
| for (int i = 0; i < saved_n; i++) { | |
| if ((j < rep_n) && _uuid_eq(saved[i], rep[j])) { | |
| next[i] = xstrdup(rep[j]); | |
| j++; | |
| continue; | |
| } | |
| if (!saved[i] || !saved[i][0]) { | |
| if ((j < rep_n) && | |
| !_uuid_later(saved, saved_n, i, rep[j])) { | |
| next[i] = xstrdup(rep[j]); | |
| j++; | |
| } | |
| continue; | |
| } | |
| if (_uuid_in(rep, rep_n, saved[i])) { | |
| for (int k = 0; k < saved_n; k++) | |
| xfree(next[k]); | |
| xfree(next); | |
| return -1; | |
| } | |
| if ((j < rep_n) && !_uuid_in(saved, saved_n, rep[j])) { | |
| next[i] = xstrdup(rep[j]); | |
| j++; | |
| continue; | |
| } | |
| /* saved[i] left. The report UUID stays for a later slot. */ | |
| } | |
| if (j != rep_n) { | |
| for (int k = 0; k < saved_n; k++) | |
| xfree(next[k]); | |
| xfree(next); | |
| return -1; | |
| } | |
| *out = next; | |
| return 0; | |
| } | |
| /* Report UUIDs are saved non-NULL UUIDs in order, skipping some. */ | |
| static bool _uuid_is_subseq(char **saved, int saved_n, char **rep, int rep_n) | |
| { | |
| int j = 0; | |
| if (!rep_n) | |
| return false; | |
| for (int i = 0; i < saved_n && j < rep_n; i++) { | |
| if (!saved[i] || !saved[i][0]) | |
| continue; | |
| if (!xstrcmp(saved[i], rep[j])) | |
| j++; | |
| } | |
| return j == rep_n; | |
| } | |
| /* | |
| * Saved order has no holes and the report is that order plus new UUIDs | |
| * appended after it. | |
| */ | |
| static bool _uuid_is_append(char **saved, int saved_n, char **rep, int rep_n) | |
| { | |
| if (!saved_n || (rep_n <= saved_n)) | |
| return false; | |
| for (int i = 0; i < saved_n; i++) { | |
| if (!saved[i] || !saved[i][0] || xstrcmp(saved[i], rep[i])) | |
| return false; | |
| } | |
| for (int j = saved_n; j < rep_n; j++) { | |
| if (_uuid_in(saved, saved_n, rep[j])) | |
| return false; | |
| } | |
| return true; | |
| } | |
| static bool _held_uuid_changed(gres_node_state_t *gres_ns, char **next) | |
| { | |
| if (!gres_ns->gres_bit_alloc || !gres_ns->gres_cnt_alloc) | |
| return false; | |
| for (int i = 0; i < gres_ns->dev_uuid_cnt; i++) { | |
| if ((i >= bit_size(gres_ns->gres_bit_alloc)) || | |
| !bit_test(gres_ns->gres_bit_alloc, i)) | |
| continue; | |
| /* | |
| * An empty slot is that device, now absent. A different | |
| * UUID in a held slot is a move, which the caller refuses. | |
| */ | |
| if (!next[i] || !next[i][0]) | |
| continue; | |
| if (xstrcmp(gres_ns->dev_uuid[i], next[i])) | |
| return true; | |
| } | |
| return false; | |
| } | |
| static void _uuid_set_absent_bit(gres_node_state_t *gres_ns, int i, | |
| const char *reason) | |
| { | |
| _gres_bit_drain_resize(gres_ns, gres_ns->dev_uuid_cnt); | |
| if ((i < bit_size(gres_ns->gres_bit_drain)) && | |
| !bit_test(gres_ns->gres_bit_drain, i)) | |
| _gres_drain_set_bit(gres_ns, i, (char *) reason); | |
| } | |
| /* | |
| * Apply slurmd's UUID order to the saved map. | |
| * RET 1 the saved order was kept and the caller must not rebuild, | |
| * 0 this registration is not a UUID-order update, | |
| * -1 the report moved a remaining device or added one past the holes. | |
| */ | |
| static int _uuid_map_apply(gres_node_state_t *gres_ns, uint32_t plugin_id, | |
| char *gres_name, uint64_t config_cnt) | |
| { | |
| uuid_rep_t rep = { | |
| .plugin_id = plugin_id, | |
| }; | |
| char **next = NULL; | |
| char *not_found; | |
| bool changed = false, holes = false; | |
| int rc = 0; | |
| if (!gres_ns->dev_uuid || !gres_ns->dev_uuid_cnt || | |
| !_all_devs_have_uuid(plugin_id)) | |
| return 0; | |
| (void) list_for_each(gres_conf_list, _uuid_rep_add, &rep); | |
| /* | |
| * With drain_absent_gres, a report with no device of this GRES (for | |
| * example a GPU driver that failed to load) empties every saved slot | |
| * like any other absence, so the node stays up with them drained. | |
| */ | |
| if (rep.bad || (!rep.n && !(slurm_conf.conf_flags & | |
| CONF_FLAG_DRAIN_ABSENT_GRES))) { | |
| xfree(rep.uuid); | |
| return 0; | |
| } | |
| /* | |
| * Match the normal registration path, which ignores devices over the | |
| * configured count. Without this cap the first registration builds a | |
| * map of the configured devices, then the same report is rejected when | |
| * it is compared with that map on the next registration. | |
| */ | |
| if ((config_cnt != NO_VAL64) && (config_cnt < (uint64_t) rep.n)) | |
| rep.n = config_cnt; | |
| not_found = xstrdup_printf("gres/%s:not_found", gres_name); | |
| /* | |
| * The configured count changed to the devices slurmd reported, and | |
| * that report does not reorder the ones that remain. Rebuild from | |
| * the report. A same-count reorder, or an append while an index is | |
| * empty, falls through and is refused. | |
| */ | |
| if ((config_cnt > 0) && (config_cnt != NO_VAL64) && | |
| ((uint64_t) rep.n == config_cnt)) { | |
| bool shrink = (config_cnt < gres_ns->dev_uuid_cnt) && | |
| _uuid_is_subseq(gres_ns->dev_uuid, | |
| gres_ns->dev_uuid_cnt, rep.uuid, | |
| rep.n); | |
| bool grow = (config_cnt > gres_ns->dev_uuid_cnt) && | |
| _uuid_is_append(gres_ns->dev_uuid, | |
| gres_ns->dev_uuid_cnt, rep.uuid, | |
| rep.n); | |
| if (shrink || grow) { | |
| xfree(not_found); | |
| xfree(rep.uuid); | |
| return 0; | |
| } | |
| } | |
| if (_uuid_align(gres_ns->dev_uuid, gres_ns->dev_uuid_cnt, rep.uuid, | |
| rep.n, &next)) { | |
| rc = -1; | |
| goto out; | |
| } | |
| if (_held_uuid_changed(gres_ns, next)) { | |
| rc = -1; | |
| goto out; | |
| } | |
| for (int i = 0; i < gres_ns->dev_uuid_cnt; i++) { | |
| if (xstrcmp(gres_ns->dev_uuid[i], next[i])) | |
| changed = true; | |
| if (!next[i]) | |
| holes = true; | |
| } | |
| if (!changed && !holes) { | |
| rc = 0; | |
| goto out; | |
| } | |
| for (int i = 0; i < gres_ns->dev_uuid_cnt; i++) { | |
| char *was = gres_ns->dev_uuid[i]; | |
| char *reason; | |
| if (was && !next[i]) { | |
| reason = _uuid_reason(gres_ns, was); | |
| if (!reason) | |
| drained_tres_group_id_set( | |
| &gres_ns->drain_groups, was, not_found); | |
| reason = _uuid_reason(gres_ns, was); | |
| xfree(gres_ns->dev_uuid[i]); | |
| _uuid_set_absent_bit(gres_ns, i, | |
| reason ? reason : not_found); | |
| continue; | |
| } | |
| if (next[i] && | |
| _not_found_reason(_uuid_reason(gres_ns, next[i]))) | |
| drained_tres_group_id_clear(&gres_ns->drain_groups, | |
| next[i]); | |
| /* | |
| * The previous UUID is no longer the device at this index. | |
| * An absent index keeps its UUID; a replacement does not. | |
| */ | |
| if (was && next[i] && xstrcmp(was, next[i])) | |
| drained_tres_group_id_clear(&gres_ns->drain_groups, | |
| was); | |
| xfree(gres_ns->dev_uuid[i]); | |
| gres_ns->dev_uuid[i] = next[i]; | |
| next[i] = NULL; | |
| if (!gres_ns->dev_uuid[i]) | |
| _uuid_set_absent_bit(gres_ns, i, not_found); | |
| else if (_uuid_reason(gres_ns, gres_ns->dev_uuid[i])) | |
| _uuid_set_absent_bit( | |
| gres_ns, i, | |
| _uuid_reason(gres_ns, gres_ns->dev_uuid[i])); | |
| else if (gres_ns->gres_bit_drain && | |
| (i < bit_size(gres_ns->gres_bit_drain)) && | |
| bit_test(gres_ns->gres_bit_drain, i)) | |
| _gres_drain_clear_bit(gres_ns, i); | |
| } | |
| for (int i = 0; i < gres_ns->dev_uuid_cnt; i++) | |
| xfree(next[i]); | |
| xfree(next); | |
| next = NULL; | |
| /* A same-count change with no hole is the new order. Keep going. */ | |
| rc = holes ? 1 : 0; | |
| out: | |
| if (next) { | |
| for (int i = 0; i < gres_ns->dev_uuid_cnt; i++) | |
| xfree(next[i]); | |
| xfree(next); | |
| } | |
| xfree(not_found); | |
| xfree(rep.uuid); | |
| return rc; | |
| } | |
| static bool _drain_absent_on(void) | |
| { | |
| return slurm_conf.conf_flags & CONF_FLAG_DRAIN_ABSENT_GRES; | |
| } | |
| /* | |
| * The configured count changed and the report is the saved order with | |
| * devices removed or appended. Move drains onto the indexes the report | |
| * will use, and drop a drain whose UUID is no longer configured. Called | |
| * before the rebuild replaces dev_uuid. | |
| */ | |
| static void _uuid_reseat_drains(gres_node_state_t *gres_ns, uint32_t plugin_id, | |
| uint64_t dev_cnt) | |
| { | |
| uuid_rep_t rep = { | |
| .plugin_id = plugin_id, | |
| }; | |
| char **reasons = NULL; | |
| if (!gres_ns->dev_uuid || !gres_ns->dev_uuid_cnt || | |
| !_all_devs_have_uuid(plugin_id) || | |
| (dev_cnt == gres_ns->dev_uuid_cnt)) | |
| return; | |
| (void) list_for_each(gres_conf_list, _uuid_rep_add, &rep); | |
| if ((dev_cnt != NO_VAL64) && (dev_cnt < (uint64_t) rep.n)) | |
| rep.n = dev_cnt; | |
| if (rep.bad || ((uint64_t) rep.n != dev_cnt)) | |
| goto out; | |
| if ((dev_cnt < gres_ns->dev_uuid_cnt) && | |
| !_uuid_is_subseq(gres_ns->dev_uuid, gres_ns->dev_uuid_cnt, rep.uuid, | |
| rep.n)) | |
| goto out; | |
| if ((dev_cnt > gres_ns->dev_uuid_cnt) && | |
| !_uuid_is_append(gres_ns->dev_uuid, gres_ns->dev_uuid_cnt, rep.uuid, | |
| rep.n)) | |
| goto out; | |
| if (dev_cnt > gres_ns->dev_uuid_cnt) { | |
| _gres_bit_drain_resize(gres_ns, dev_cnt); | |
| goto out; | |
| } | |
| reasons = xcalloc(rep.n, sizeof(char *)); | |
| for (int j = 0; j < rep.n; j++) { | |
| char *reason = _uuid_reason(gres_ns, rep.uuid[j]); | |
| if (reason) | |
| reasons[j] = xstrdup(reason); | |
| } | |
| for (int i = 0; i < gres_ns->dev_uuid_cnt; i++) { | |
| char *uuid = gres_ns->dev_uuid[i]; | |
| if (uuid && uuid[0] && !_uuid_in(rep.uuid, rep.n, uuid)) | |
| drained_tres_group_id_clear(&gres_ns->drain_groups, | |
| uuid); | |
| } | |
| if (gres_ns->gres_bit_drain) { | |
| int size = bit_size(gres_ns->gres_bit_drain); | |
| for (int i = 0; i < size; i++) { | |
| if (bit_test(gres_ns->gres_bit_drain, i)) | |
| _gres_drain_clear_bit(gres_ns, i); | |
| } | |
| } | |
| _gres_bit_drain_resize(gres_ns, dev_cnt); | |
| for (int j = 0; j < rep.n; j++) { | |
| if (!reasons[j]) | |
| continue; | |
| drained_tres_group_id_set(&gres_ns->drain_groups, rep.uuid[j], | |
| reasons[j]); | |
| _gres_drain_set_bit(gres_ns, j, reasons[j]); | |
| xfree(reasons[j]); | |
| } | |
| xfree(reasons); | |
| out: | |
| xfree(rep.uuid); | |
| } | |
| static int _find_conf_by_plugin_id(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *conf = x; | |
| return (conf->plugin_id == *(uint32_t *) arg) && conf->count; | |
| } | |
| /* | |
| * True when this registration reports no device of the GRES that | |
| * shared_ns shares, while that GRES keeps a saved UUID map. With | |
| * drain_absent_gres, _uuid_map_apply() then empties and drains every slot | |
| * of it, whichever GRES is validated first. | |
| */ | |
| static bool _sharing_all_absent(gres_node_state_t *shared_ns) | |
| { | |
| gres_state_t *sharing = shared_ns->alt_gres; | |
| gres_node_state_t *sharing_ns; | |
| if (!_drain_absent_on() || !sharing || !sharing->gres_data) | |
| return false; | |
| sharing_ns = sharing->gres_data; | |
| if (!sharing_ns->dev_uuid || !sharing_ns->dev_uuid_cnt) | |
| return false; | |
| return !gres_conf_list || | |
| !list_find_first_ro(gres_conf_list, _find_conf_by_plugin_id, | |
| &sharing->plugin_id); | |
| } | |
| /* | |
| * True when a device GRES has no UUID map but this registration could build | |
| * one: it reports devices of the GRES and each has its own UUID. That is the | |
| * case after State=RESUME drops a map. | |
| */ | |
| static bool _uuid_map_missing(gres_node_state_t *gres_ns, | |
| slurm_gres_context_t *gres_ctx) | |
| { | |
| if (gres_ns->dev_uuid || gres_id_shared(gres_ctx->config_flags) || | |
| !(gres_ctx->config_flags & GRES_CONF_HAS_FILE)) | |
| return false; | |
| if (!gres_conf_list || | |
| !list_find_first_ro(gres_conf_list, _find_conf_by_plugin_id, | |
| &gres_ctx->plugin_id)) | |
| return false; | |
| return _all_devs_have_uuid(gres_ctx->plugin_id); | |
| } | |
| static int _cmp_topo_type(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x; | |
| topo_type_cmp_t *cmp = arg; | |
| if (gres_slurmd_conf->plugin_id != cmp->plugin_id) | |
| return 0; | |
| if (cmp->inx >= cmp->gres_ns->topo_cnt) | |
| return -1; | |
| if (cmp->gres_ns->topo_type_id[cmp->inx++] != | |
| gres_build_id(gres_slurmd_conf->type_name)) { | |
| cmp->changed = true; | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * True when slurmd reports a different type at an index of the saved | |
| * topology, as when GPUs move to other device files at the same count. | |
| */ | |
| static bool _topo_types_changed(gres_node_state_t *gres_ns, uint32_t plugin_id) | |
| { | |
| topo_type_cmp_t cmp = { | |
| .gres_ns = gres_ns, | |
| .plugin_id = plugin_id, | |
| }; | |
| if (!gres_conf_list || !gres_ns->topo_cnt || !gres_ns->topo_type_id) | |
| return false; | |
| (void) list_for_each_ro(gres_conf_list, _cmp_topo_type, &cmp); | |
| return cmp.changed; | |
| } | |
| static int _node_config_validate(node_record_t *node_ptr, | |
| gres_state_t *gres_state_node, int cpu_cnt, | |
| int core_cnt, int sock_cnt, int cores_per_sock, | |
| bool config_overrides, char **reason_down, | |
| bool *uuid_order_drain, | |
| slurm_gres_context_t *gres_ctx) | |
| { | |
| int i, rc = SLURM_SUCCESS; | |
| uint64_t dev_cnt; | |
| bool updated_config = false; | |
| gres_node_state_t *gres_ns; | |
| bool has_file, has_type, first_time = false, rebuild_topo = false; | |
| tot_from_slurmd_conf_t slurmd_conf_tot = { | |
| .plugin_id = gres_ctx->plugin_id, | |
| }; | |
| char *orig_config = node_ptr->config_ptr->gres; | |
| char *node_name = node_ptr->name; | |
| xassert(core_cnt); | |
| if (gres_state_node->gres_data == NULL) | |
| gres_state_node->gres_data = _build_gres_node_state(); | |
| gres_ns = (gres_node_state_t *) gres_state_node->gres_data; | |
| if (gres_ns->node_feature) | |
| return rc; | |
| _get_tot_from_slurmd_conf(&slurmd_conf_tot); | |
| /* If the gres is sharing we need to have topo configured. */ | |
| if (slurmd_conf_tot.has_topo_info || | |
| (gres_id_sharing(slurmd_conf_tot.plugin_id) && gres_ns->alt_gres)) | |
| slurmd_conf_tot.topo_cnt = slurmd_conf_tot.rec_cnt; | |
| /* | |
| * Check existing config_flags before overriding from | |
| * slurmd_conf_tot.config_flags. | |
| */ | |
| if (gres_state_node->config_flags & GRES_CONF_UPDATE_CONFIG) | |
| updated_config = true; | |
| /* Make sure these are insync after we get it from the slurmd */ | |
| gres_state_node->config_flags = slurmd_conf_tot.config_flags; | |
| if (!gres_id_shared(gres_ctx->config_flags)) { | |
| int map_rc = _uuid_map_apply(gres_ns, gres_ctx->plugin_id, | |
| gres_ctx->gres_name, | |
| gres_ns->gres_cnt_config); | |
| if (map_rc < 0) { | |
| /* | |
| * Keep the saved order when slurmd moves a remaining | |
| * UUID or appends one past an empty slot. The caller | |
| * drains the node and a fitting registration clears | |
| * that automatic drain. | |
| */ | |
| if (uuid_order_drain) | |
| *uuid_order_drain = true; | |
| return SLURM_SUCCESS; | |
| } | |
| if (map_rc > 0) { | |
| if (!_drain_absent_on()) { | |
| if (reason_down && !*reason_down && | |
| (slurmd_conf_tot.gres_cnt < | |
| gres_ns->gres_cnt_config)) { | |
| xstrfmtcat( | |
| *reason_down, | |
| "%s count reported lower than configured (%" PRIu64 | |
| " < %" PRIu64 ")", | |
| gres_ctx->gres_type, | |
| slurmd_conf_tot.gres_cnt, | |
| gres_ns->gres_cnt_config); | |
| } | |
| return EINVAL; | |
| } | |
| return SLURM_SUCCESS; | |
| } | |
| } else if (_sharing_all_absent(gres_ns)) { | |
| /* | |
| * Every device this GRES shares is absent and drained, so its | |
| * capacity is unusable. Keep its saved count and topology for | |
| * when the devices are back, whatever slurmd reported for it. | |
| */ | |
| return SLURM_SUCCESS; | |
| } | |
| if (gres_ns->gres_cnt_config > slurmd_conf_tot.gres_cnt) { | |
| if (reason_down && (*reason_down == NULL)) { | |
| xstrfmtcat(*reason_down, | |
| "%s count reported lower than configured " | |
| "(%"PRIu64" < %"PRIu64")", | |
| gres_ctx->gres_type, | |
| slurmd_conf_tot.gres_cnt, | |
| gres_ns->gres_cnt_config); | |
| } | |
| rc = EINVAL; | |
| } | |
| if ((slurmd_conf_tot.gres_cnt > gres_ns->gres_cnt_config)) { | |
| debug("%s: %s: Ignoring excess count on node %s (%" | |
| PRIu64" > %"PRIu64")", | |
| __func__, gres_ctx->gres_type, node_name, | |
| slurmd_conf_tot.gres_cnt, | |
| gres_ns->gres_cnt_config); | |
| slurmd_conf_tot.gres_cnt = gres_ns->gres_cnt_config; | |
| } | |
| /* | |
| * A cloud node can have jobs holding count-only GRES before its | |
| * slurmd first registers, so its first count is not a change. | |
| */ | |
| if (gres_ns->gres_cnt_found != slurmd_conf_tot.gres_cnt) { | |
| if (gres_ns->gres_cnt_found != NO_VAL64) { | |
| info("%s: %s: Count changed on node %s (%"PRIu64" != %"PRIu64")", | |
| __func__, gres_ctx->gres_type, node_name, | |
| gres_ns->gres_cnt_found, | |
| slurmd_conf_tot.gres_cnt); | |
| } | |
| /* | |
| * A count never recorded, or last recorded as 0, that now | |
| * registers as the configured count is discovery, not a change | |
| * under the jobs using it. | |
| */ | |
| if (((gres_ns->gres_cnt_found == NO_VAL64) || | |
| (gres_ns->gres_cnt_found == 0)) && | |
| (slurmd_conf_tot.gres_cnt == gres_ns->gres_cnt_config) && | |
| (gres_ns->gres_cnt_config > 0) && | |
| (slurmd_conf_tot.gres_cnt > 0)) { | |
| gres_ns->gres_cnt_found = slurmd_conf_tot.gres_cnt; | |
| updated_config = true; | |
| first_time = true; | |
| } else if ((gres_ns->gres_cnt_found != NO_VAL64) && | |
| (gres_ns->gres_cnt_alloc != 0)) { | |
| if (reason_down && (*reason_down == NULL)) { | |
| xstrfmtcat(*reason_down, | |
| "%s count changed and jobs are using them " | |
| "(%"PRIu64" != %"PRIu64")", | |
| gres_ctx->gres_type, | |
| gres_ns->gres_cnt_found, | |
| slurmd_conf_tot.gres_cnt); | |
| } | |
| rc = EINVAL; | |
| } else if ((slurmd_conf_tot.gres_cnt == 0) && | |
| (gres_ns->gres_cnt_config > 0)) { | |
| /* | |
| * slurmd found none. The "count reported lower than | |
| * configured" check above already rejects the | |
| * registration, so keep the previous count rather than | |
| * record 0. | |
| */ | |
| } else { | |
| gres_ns->gres_cnt_found = slurmd_conf_tot.gres_cnt; | |
| updated_config = true; | |
| first_time = true; | |
| } | |
| } else if ((slurmd_conf_tot.config_flags & GRES_CONF_HAS_FILE) && | |
| (gres_ns->gres_cnt_found > 0) && | |
| (gres_ns->gres_cnt_found != NO_VAL64) && | |
| !gres_ns->gres_bit_alloc) { | |
| /* | |
| * The count is already right but the device bitmap was never | |
| * built, so finish validating to build it. slurmctld reading | |
| * fake_gpus.conf from a config directory it shares with | |
| * slurmd, as the testsuite does, counts the GPUs at startup | |
| * without File. Without this a job cannot bind after a | |
| * slurmctld restart. | |
| */ | |
| updated_config = true; | |
| } | |
| if (!updated_config && gres_ns->type_cnt) { | |
| /* | |
| * This is needed to address the GRES specification in | |
| * gres.conf having a Type option, while the GRES specification | |
| * in slurm.conf does not. | |
| */ | |
| for (i = 0; i < gres_ns->type_cnt; i++) { | |
| if (gres_ns->type_cnt_avail[i]) | |
| continue; | |
| updated_config = true; | |
| break; | |
| } | |
| } | |
| /* | |
| * Chunk settings can change without the device count changing, so | |
| * rebuild when they differ from the node's domains. Skip it when the | |
| * count above was refused, which leaves gres_cnt_found unchanged. Keep | |
| * it above the type check below, so that check can still stop the | |
| * rebuild while jobs use a removed type. | |
| */ | |
| if (_chunk_config_changed(gres_ns, gres_ctx->plugin_id) && | |
| (gres_ns->gres_cnt_found == slurmd_conf_tot.gres_cnt)) | |
| updated_config = true; | |
| if (!first_time && gres_ns->type_cnt && gres_ns->topo_cnt) { | |
| for (i = 0; i < gres_ns->topo_cnt; i++) { | |
| int type_index = _find_gres_type(gres_ns, | |
| gres_ns->topo_type_id[i]); | |
| /* | |
| * On a reconfig if a type was removed from slurm.conf | |
| * its type_cnt_avail will be set to 0. If the type is | |
| * not found then the topo is from a previous invalid | |
| * registration. | |
| */ | |
| if ((type_index < 0) || | |
| (gres_ns->type_cnt_avail[type_index] == 0 && | |
| gres_ns->topo_gres_cnt_avail[i])) { | |
| if (gres_ns->gres_cnt_alloc != 0) { | |
| if (reason_down && | |
| (*reason_down == NULL)) { | |
| xstrfmtcat(*reason_down, | |
| "%s type changed and jobs are using them", | |
| gres_ctx->gres_type); | |
| } | |
| rc = EINVAL; | |
| updated_config = false; | |
| } else { | |
| updated_config = true; | |
| } | |
| } | |
| } | |
| } | |
| /* | |
| * The count is unchanged, so nothing above rebuilds the topology that | |
| * gives each index its type. | |
| */ | |
| if (!first_time && !updated_config && (rc == SLURM_SUCCESS) && | |
| _topo_types_changed(gres_ns, gres_ctx->plugin_id)) { | |
| if (gres_ns->gres_cnt_alloc) { | |
| if (reason_down && !*reason_down) | |
| xstrfmtcat( | |
| *reason_down, | |
| "%s type changed and jobs are using them", | |
| gres_ctx->gres_type); | |
| rc = EINVAL; | |
| } else { | |
| updated_config = true; | |
| } | |
| } | |
| /* A dropped UUID map is saved again from the next report. */ | |
| if (!updated_config && !(IS_NODE_INVALID_REG(node_ptr)) && | |
| !_uuid_map_missing(gres_ns, gres_ctx)) | |
| return rc; | |
| if (gres_id_sharing(slurmd_conf_tot.plugin_id) && gres_ns->alt_gres) { | |
| /* | |
| * Tell the shared gres to update itself if the sharing gres is | |
| * updated -- which will happen in a subsequent call to | |
| * _node_config_validate() since gres_node_config_validate() is | |
| * looping on all gres_contexts. | |
| */ | |
| gres_ns->alt_gres->config_flags |= GRES_CONF_UPDATE_CONFIG; | |
| } | |
| if ((slurmd_conf_tot.gres_cnt > gres_ns->gres_cnt_config) && | |
| config_overrides) { | |
| info("%s: %s: count on node %s inconsistent with slurmctld count (%"PRIu64" != %"PRIu64")", | |
| __func__, gres_ctx->gres_type, node_name, | |
| slurmd_conf_tot.gres_cnt, gres_ns->gres_cnt_config); | |
| slurmd_conf_tot.gres_cnt = gres_ns->gres_cnt_config; | |
| /* Ignore excess GRES */ | |
| } | |
| if ((slurmd_conf_tot.topo_cnt == 0) && | |
| (slurmd_conf_tot.topo_cnt != gres_ns->topo_cnt)) { | |
| /* Need to clear topology info */ | |
| _gres_node_state_delete_topo(gres_ns); | |
| _dev_uuid_resize(gres_ns, 0); | |
| gres_ns->topo_cnt = slurmd_conf_tot.topo_cnt; | |
| } | |
| has_file = gres_ctx->config_flags & GRES_CONF_HAS_FILE; | |
| has_type = gres_ctx->config_flags & GRES_CONF_HAS_TYPE; | |
| if (gres_id_shared(gres_ctx->config_flags)) | |
| dev_cnt = slurmd_conf_tot.topo_cnt; | |
| else | |
| dev_cnt = slurmd_conf_tot.gres_cnt; | |
| if (has_file && (slurmd_conf_tot.topo_cnt != gres_ns->topo_cnt) && | |
| (dev_cnt == 0)) { | |
| /* | |
| * Clear any vestigial GRES node state info. | |
| */ | |
| _gres_node_state_delete_topo(gres_ns); | |
| _dev_uuid_resize(gres_ns, 0); | |
| xfree(gres_ns->gres_bit_alloc); | |
| gres_ns->topo_cnt = 0; | |
| } else if (has_file && | |
| (slurmd_conf_tot.topo_cnt != gres_ns->topo_cnt)) { | |
| /* | |
| * Need to rebuild topology info. | |
| * Resize the data structures here. | |
| */ | |
| rebuild_topo = true; | |
| /* | |
| * Clear any vestigial GRES node state info. | |
| */ | |
| _gres_node_state_delete_topo(gres_ns); | |
| gres_ns->topo_gres_cnt_alloc = | |
| xrealloc(gres_ns->topo_gres_cnt_alloc, | |
| slurmd_conf_tot.topo_cnt * sizeof(uint64_t)); | |
| gres_ns->topo_gres_cnt_avail = | |
| xrealloc(gres_ns->topo_gres_cnt_avail, | |
| slurmd_conf_tot.topo_cnt * sizeof(uint64_t)); | |
| gres_ns->topo_gres_bitmap = | |
| xrealloc(gres_ns->topo_gres_bitmap, | |
| slurmd_conf_tot.topo_cnt * | |
| sizeof(bitstr_t *)); | |
| gres_ns->topo_core_bitmap = | |
| xrealloc(gres_ns->topo_core_bitmap, | |
| slurmd_conf_tot.topo_cnt * | |
| sizeof(bitstr_t *)); | |
| gres_ns->topo_res_core_bitmap = | |
| xrealloc(gres_ns->topo_res_core_bitmap, | |
| slurmd_conf_tot.topo_cnt * | |
| sizeof(bitstr_t *)); | |
| gres_ns->topo_type_id = xrealloc(gres_ns->topo_type_id, | |
| slurmd_conf_tot.topo_cnt * | |
| sizeof(uint32_t)); | |
| gres_ns->topo_type_name = xrealloc(gres_ns->topo_type_name, | |
| slurmd_conf_tot.topo_cnt * | |
| sizeof(char *)); | |
| if (gres_ns->gres_bit_alloc) | |
| _gres_bit_alloc_resize(gres_ns, dev_cnt); | |
| gres_ns->topo_cnt = slurmd_conf_tot.topo_cnt; | |
| } else if (gres_ns->topo_cnt) { | |
| /* | |
| * Need to rebuild topology info to recover state after | |
| * slurmctld restart with running jobs. The number of gpus, | |
| * cores, and type might have changed in slurm.conf | |
| */ | |
| rebuild_topo = true; | |
| } | |
| /* | |
| * Drop it here rather than in the rebuild below, or a node that loses | |
| * every device keeps chunk state naming devices that are gone. The | |
| * rebuild below builds it again as configured. | |
| */ | |
| _clear_chunk_config(gres_ns); | |
| if (rebuild_topo) { | |
| rebuild_topo_t rebuild_topo = { | |
| .core_cnt = core_cnt, | |
| .cores_per_sock = cores_per_sock, | |
| .dev_cnt = dev_cnt, | |
| .gres_ctx = gres_ctx, | |
| .gres_ns = gres_ns, | |
| .has_file = has_file, | |
| .node_name = node_name, | |
| .rc = rc, | |
| .reason_down = reason_down, | |
| .sock_cnt = sock_cnt, | |
| .tot_gres_cnt = slurmd_conf_tot.gres_cnt, | |
| }; | |
| bool chunk_err; | |
| /* | |
| * Size the device index -> UUID map to the current device count | |
| * so _foreach_rebuild_topo() can record each device's UUID at | |
| * its bitmap index. Only sharing/plain device GRES carry it; | |
| * shared GRES (shard/mps) inherit drain state from alt_gres. | |
| * A GRES with a device that reported no UUID gets no map. | |
| * Neither does a report short of the configured count: it | |
| * cannot say which device is absent, so the next full report | |
| * saves the order. | |
| */ | |
| if (!gres_id_shared(gres_ctx->config_flags)) { | |
| _uuid_reseat_drains(gres_ns, gres_ctx->plugin_id, | |
| dev_cnt); | |
| if (_all_devs_have_uuid(gres_ctx->plugin_id) && | |
| ((gres_ns->gres_cnt_config == NO_VAL64) || | |
| (dev_cnt >= gres_ns->gres_cnt_config))) | |
| _dev_uuid_resize(gres_ns, dev_cnt); | |
| else | |
| _dev_uuid_resize(gres_ns, 0); | |
| } | |
| (void) list_for_each(gres_conf_list, _foreach_rebuild_topo, | |
| &rebuild_topo); | |
| rc = rebuild_topo.rc; | |
| chunk_err = rebuild_topo.chunk_mismatch; | |
| if (chunk_err) { | |
| char *tmp = xstrdup_printf("%s: every record of a Type must set the same ChunkSizes and ChunkOrder", | |
| gres_ctx->gres_type); | |
| error("%s: %s on node %s", __func__, tmp, node_name); | |
| if (reason_down && !(*reason_down)) | |
| *reason_down = tmp; | |
| else | |
| xfree(tmp); | |
| rc = EINVAL; | |
| } | |
| for (int i = 0; i < rebuild_topo.chunk_build_cnt; i++) { | |
| chunk_build_t *build = &rebuild_topo.chunk_builds[i]; | |
| if (!chunk_err && build->devs && | |
| (_build_chunk_config( | |
| build, node_name, | |
| gres_id_shared(gres_ctx->config_flags), | |
| gres_ns, reason_down) != SLURM_SUCCESS)) { | |
| chunk_err = true; | |
| rc = EINVAL; | |
| } | |
| FREE_NULL_BITMAP(build->devs); | |
| } | |
| xfree(rebuild_topo.chunk_builds); | |
| /* All or nothing: no Type is enforced on a half-built node */ | |
| if (chunk_err) | |
| _clear_chunk_config(gres_ns); | |
| if (rc) | |
| gres_ns->topo_cnt = rebuild_topo.topo_cnt; | |
| has_file = rebuild_topo.has_file; | |
| if (rebuild_topo.cpu_config_err) { | |
| /* | |
| * Some GRES of this type have "CPUs" configured. Set | |
| * topo_core_bitmap for all others with all bits set. | |
| */ | |
| (void) list_for_each(gres_conf_list, | |
| _foreach_rebuild_topo_no_cpus, | |
| &rebuild_topo); | |
| } | |
| } else if (!has_file && has_type) { | |
| add_gres_info_t add_gres_info = { | |
| .gres_ctx = gres_ctx, | |
| .gres_ns = gres_ns, | |
| }; | |
| /* Add GRES Type information as needed */ | |
| (void) list_for_each(gres_conf_list, | |
| _foreach_add_gres_info, | |
| &add_gres_info); | |
| } | |
| if ((orig_config == NULL) || (orig_config[0] == '\0')) | |
| gres_ns->gres_cnt_config = 0; | |
| else if (gres_ns->gres_cnt_config == NO_VAL64) { | |
| /* This should have been filled in by _node_config_init() */ | |
| _get_gres_cnt(gres_ns, orig_config, | |
| gres_ctx->gres_name, | |
| gres_ctx->gres_name_colon, | |
| gres_ctx->gres_name_colon_len); | |
| } | |
| gres_ns->gres_cnt_avail = gres_ns->gres_cnt_config; | |
| if (has_file) { | |
| uint64_t gres_bits; | |
| if (gres_id_shared(gres_ctx->config_flags)) { | |
| gres_bits = slurmd_conf_tot.topo_cnt; | |
| } else { | |
| if (gres_ns->gres_cnt_avail > MAX_GRES_BITMAP) { | |
| error("%s: %s has \"File\" plus very large \"Count\" " | |
| "(%"PRIu64") for node %s, resetting value to %u", | |
| __func__, gres_ctx->gres_type, | |
| gres_ns->gres_cnt_avail, node_name, | |
| MAX_GRES_BITMAP); | |
| gres_ns->gres_cnt_avail = MAX_GRES_BITMAP; | |
| gres_ns->gres_cnt_found = MAX_GRES_BITMAP; | |
| } | |
| gres_bits = gres_ns->gres_cnt_avail; | |
| } | |
| _gres_bit_alloc_resize(gres_ns, gres_bits); | |
| } | |
| gres_validate_node_cores(gres_ns, core_cnt, node_name); | |
| /* Sanity check: Shared GRES should not have drain state */ | |
| if (gres_id_shared(gres_ctx->config_flags) && | |
| (gres_ns->gres_bit_drain || gres_ns->gres_cnt_drain)) { | |
| error("%s: %s:%s has drain state on node %s, clearing", | |
| __func__, gres_ctx->gres_name, gres_ctx->gres_type, | |
| node_name); | |
| _gres_drain_clear(gres_ns); | |
| } | |
| if ((slurmd_conf_tot.config_type_cnt > 1) && | |
| !_valid_gres_types(gres_ctx->gres_type, gres_ns, reason_down)){ | |
| rc = EINVAL; | |
| } else if (!config_overrides && | |
| (gres_ns->gres_cnt_found < gres_ns->gres_cnt_config)) { | |
| if (reason_down && (*reason_down == NULL)) { | |
| xstrfmtcat(*reason_down, | |
| "%s count too low (%"PRIu64" < %"PRIu64")", | |
| gres_ctx->gres_type, | |
| gres_ns->gres_cnt_found, | |
| gres_ns->gres_cnt_config); | |
| } | |
| rc = EINVAL; | |
| } else if (_valid_gres_type(gres_ctx->gres_type, gres_ns, | |
| config_overrides, reason_down)) { | |
| rc = EINVAL; | |
| } else if (config_overrides && gres_ns->topo_cnt && | |
| (gres_ns->gres_cnt_found != gres_ns->gres_cnt_config)) { | |
| error("%s on node %s configured for %"PRIu64" resources but " | |
| "%"PRIu64" found, ignoring topology support", | |
| gres_ctx->gres_type, node_name, | |
| gres_ns->gres_cnt_config, gres_ns->gres_cnt_found); | |
| if (gres_ns->topo_core_bitmap) { | |
| for (i = 0; i < gres_ns->topo_cnt; i++) { | |
| if (gres_ns->topo_core_bitmap) { | |
| FREE_NULL_BITMAP(gres_ns-> | |
| topo_core_bitmap[i]); | |
| } | |
| if (gres_ns->topo_gres_bitmap) { | |
| FREE_NULL_BITMAP(gres_ns-> | |
| topo_gres_bitmap[i]); | |
| } | |
| xfree(gres_ns->topo_type_name[i]); | |
| } | |
| xfree(gres_ns->topo_core_bitmap); | |
| xfree(gres_ns->topo_gres_bitmap); | |
| xfree(gres_ns->topo_gres_cnt_alloc); | |
| xfree(gres_ns->topo_gres_cnt_avail); | |
| xfree(gres_ns->topo_type_id); | |
| xfree(gres_ns->topo_type_name); | |
| } | |
| gres_ns->topo_cnt = 0; | |
| } | |
| _gres_drain_cnt_clamp(gres_ns); | |
| return rc; | |
| } | |
| /* The GPU count on a node changed. Update SHARED data structures to match */ | |
| static void _sync_node_shared_to_sharing(gres_state_t *sharing_gres_state_node) | |
| { | |
| gres_node_state_t *sharing_gres_ns, *shared_gres_ns; | |
| uint64_t sharing_cnt, shared_alloc = 0, shared_rem; | |
| int i; | |
| if (!sharing_gres_state_node) | |
| return; | |
| sharing_gres_ns = sharing_gres_state_node->gres_data; | |
| if (!sharing_gres_ns->alt_gres) | |
| return; | |
| shared_gres_ns = sharing_gres_ns->alt_gres->gres_data; | |
| sharing_cnt = sharing_gres_ns->gres_cnt_avail; | |
| if (shared_gres_ns->gres_bit_alloc) { | |
| if ((sharing_cnt == bit_size(shared_gres_ns->gres_bit_alloc)) && | |
| (sharing_cnt == shared_gres_ns->topo_cnt)) { | |
| debug3("No change for gres/'shared'"); | |
| return; | |
| } | |
| } | |
| if (sharing_cnt == 0) | |
| return; /* Still no SHARINGs */ | |
| /* Free any excess gres/'shared' topo records */ | |
| for (i = sharing_cnt; i < shared_gres_ns->topo_cnt; i++) { | |
| if (shared_gres_ns->topo_core_bitmap) | |
| FREE_NULL_BITMAP(shared_gres_ns->topo_core_bitmap[i]); | |
| if (shared_gres_ns->topo_res_core_bitmap) | |
| FREE_NULL_BITMAP( | |
| shared_gres_ns->topo_res_core_bitmap[i]); | |
| if (shared_gres_ns->topo_gres_bitmap) | |
| FREE_NULL_BITMAP(shared_gres_ns->topo_gres_bitmap[i]); | |
| xfree(shared_gres_ns->topo_type_name[i]); | |
| } | |
| if (shared_gres_ns->gres_cnt_avail == 0) { | |
| /* No gres/'shared' on this node */ | |
| shared_gres_ns->topo_cnt = 0; | |
| return; | |
| } | |
| if (!shared_gres_ns->gres_bit_alloc) { | |
| shared_gres_ns->gres_bit_alloc = bit_alloc(sharing_cnt); | |
| } else { | |
| bit_realloc(shared_gres_ns->gres_bit_alloc, sharing_cnt); | |
| } | |
| /* Add any additional required gres/'shared' topo records */ | |
| if (shared_gres_ns->topo_cnt) { | |
| shared_gres_ns->topo_core_bitmap = | |
| xrealloc(shared_gres_ns->topo_core_bitmap, | |
| sizeof(bitstr_t *) * sharing_cnt); | |
| shared_gres_ns->topo_res_core_bitmap = | |
| xrealloc(shared_gres_ns->topo_res_core_bitmap, | |
| sizeof(bitstr_t *) * sharing_cnt); | |
| shared_gres_ns->topo_gres_bitmap = | |
| xrealloc(shared_gres_ns->topo_gres_bitmap, | |
| sizeof(bitstr_t *) * sharing_cnt); | |
| shared_gres_ns->topo_gres_cnt_alloc = | |
| xrealloc(shared_gres_ns->topo_gres_cnt_alloc, | |
| sizeof(uint64_t) * sharing_cnt); | |
| shared_gres_ns->topo_gres_cnt_avail = | |
| xrealloc(shared_gres_ns->topo_gres_cnt_avail, | |
| sizeof(uint64_t) * sharing_cnt); | |
| shared_gres_ns->topo_type_id = | |
| xrealloc(shared_gres_ns->topo_type_id, | |
| sizeof(uint32_t) * sharing_cnt); | |
| shared_gres_ns->topo_type_name = | |
| xrealloc(shared_gres_ns->topo_type_name, | |
| sizeof(char *) * sharing_cnt); | |
| } else { | |
| shared_gres_ns->topo_core_bitmap = | |
| xcalloc(sharing_cnt, sizeof(bitstr_t *)); | |
| shared_gres_ns->topo_res_core_bitmap = | |
| xcalloc(sharing_cnt, sizeof(bitstr_t *)); | |
| shared_gres_ns->topo_gres_bitmap = | |
| xcalloc(sharing_cnt, sizeof(bitstr_t *)); | |
| shared_gres_ns->topo_gres_cnt_alloc = | |
| xcalloc(sharing_cnt, sizeof(uint64_t)); | |
| shared_gres_ns->topo_gres_cnt_avail = | |
| xcalloc(sharing_cnt, sizeof(uint64_t)); | |
| shared_gres_ns->topo_type_id = | |
| xcalloc(sharing_cnt, sizeof(uint32_t)); | |
| shared_gres_ns->topo_type_name = | |
| xcalloc(sharing_cnt, sizeof(char *)); | |
| } | |
| /* | |
| * Evenly distribute any remaining SHARED counts. | |
| * Counts get reset as needed when the node registers. | |
| */ | |
| for (i = 0; i < shared_gres_ns->topo_cnt; i++) | |
| shared_alloc += shared_gres_ns->topo_gres_cnt_avail[i]; | |
| if (shared_alloc >= shared_gres_ns->gres_cnt_avail) | |
| shared_rem = 0; | |
| else | |
| shared_rem = shared_gres_ns->gres_cnt_avail - shared_alloc; | |
| for (i = shared_gres_ns->topo_cnt; i < sharing_cnt; i++) { | |
| shared_gres_ns->topo_gres_bitmap[i] = bit_alloc(sharing_cnt); | |
| bit_set(shared_gres_ns->topo_gres_bitmap[i], i); | |
| shared_alloc = shared_rem / (sharing_cnt - i); | |
| shared_gres_ns->topo_gres_cnt_avail[i] = shared_alloc; | |
| shared_rem -= shared_alloc; | |
| } | |
| shared_gres_ns->topo_cnt = sharing_cnt; | |
| for (i = 0; i < shared_gres_ns->topo_cnt; i++) { | |
| if (shared_gres_ns->topo_gres_bitmap && | |
| shared_gres_ns->topo_gres_bitmap[i] && | |
| (sharing_cnt != | |
| bit_size(shared_gres_ns->topo_gres_bitmap[i]))) { | |
| bit_realloc(shared_gres_ns->topo_gres_bitmap[i], | |
| sharing_cnt); | |
| } | |
| } | |
| } | |
| /* | |
| * Validate a node's configuration and put a gres record onto a list | |
| * Called immediately after gres_node_config_unpack(). | |
| * IN node_ptr - With the relevant attributes for this function being: | |
| * ->name - name of the node for which the gres information applies | |
| * ->config_ptr->gres - Gres information supplied from merged | |
| * slurm.conf/gres.conf | |
| * ->gres - Updated gres info from slurm.conf | |
| * ->gres_list - List of Gres records for this node to track usage | |
| * IN threads_per_core - Count of CPUs (threads) per core on this node | |
| * IN cores_per_sock - Count of cores per socket on this node | |
| * IN sock_cnt - Count of sockets on this node | |
| * IN config_overrides - true: Don't validate hardware, use slurm.conf | |
| * configuration | |
| * false: Validate hardware config, but use slurm.conf | |
| * config | |
| * OUT reason_down - set to an explanation of failure, if any, don't set if NULL | |
| * OUT uuid_order_drain - set when a UUID report must drain the node instead of | |
| * marking it invalid; NULL skips that. | |
| */ | |
| extern int gres_node_config_validate(node_record_t *node_ptr, | |
| int threads_per_core, int cores_per_sock, | |
| int sock_cnt, bool config_overrides, | |
| char **reason_down, bool *uuid_order_drain) | |
| { | |
| int i, rc = SLURM_SUCCESS, rc2; | |
| gres_state_t *gres_state_node, *gres_gpu_ptr = NULL; | |
| int core_cnt = sock_cnt * cores_per_sock; | |
| int cpu_cnt = core_cnt * threads_per_core; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| if ((gres_context_cnt > 0) && (node_ptr->gres_list == NULL)) | |
| node_ptr->gres_list = list_create(_gres_node_list_delete); | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| /* Find or create gres_state entry on the list */ | |
| gres_state_node = | |
| list_find_first(node_ptr->gres_list, gres_find_id, | |
| &gres_context[i].plugin_id); | |
| if (gres_state_node == NULL) { | |
| gres_state_node = gres_create_state( | |
| &gres_context[i], GRES_STATE_SRC_CONTEXT_PTR, | |
| GRES_STATE_TYPE_NODE, _build_gres_node_state()); | |
| list_append(node_ptr->gres_list, gres_state_node); | |
| } | |
| rc2 = _node_config_validate(node_ptr, gres_state_node, cpu_cnt, | |
| core_cnt, sock_cnt, cores_per_sock, | |
| config_overrides, reason_down, | |
| uuid_order_drain, &gres_context[i]); | |
| rc = MAX(rc, rc2); | |
| if (gres_id_sharing(gres_state_node->plugin_id)) | |
| gres_gpu_ptr = gres_state_node; | |
| } | |
| _sync_node_shared_to_sharing(gres_gpu_ptr); | |
| _build_node_gres_str(&node_ptr->gres_list, &node_ptr->gres, | |
| cores_per_sock, sock_cnt); | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return rc; | |
| } | |
| static int _foreach_reset_uuid_map(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_node = x; | |
| gres_node_state_t *gres_ns = gres_state_node->gres_data; | |
| (void) arg; | |
| if (gres_ns && !gres_id_shared(gres_state_node->config_flags)) { | |
| /* | |
| * UUID drains are applied through the saved map, so they go | |
| * with it. Index drains stay for the resume to clear. | |
| */ | |
| drained_tres_groups_clear_ids(&gres_ns->drain_groups); | |
| _dev_uuid_resize(gres_ns, 0); | |
| } | |
| return 0; | |
| } | |
| extern void gres_node_reset_uuid_maps(list_t *gres_list) | |
| { | |
| if (!gres_list) | |
| return; | |
| slurm_mutex_lock(&gres_context_lock); | |
| (void) list_for_each(gres_list, _foreach_reset_uuid_map, NULL); | |
| slurm_mutex_unlock(&gres_context_lock); | |
| } | |
| /* Convert number to new value with suffix (e.g. 2096 -> 2K) */ | |
| static void _gres_scale_value(uint64_t gres_size, uint64_t *gres_scaled, | |
| char **suffix) | |
| { | |
| uint64_t tmp_gres_size = gres_size; | |
| int i; | |
| tmp_gres_size = gres_size; | |
| for (i = 0; i < 4; i++) { | |
| if ((tmp_gres_size != 0) && ((tmp_gres_size % 1024) == 0)) | |
| tmp_gres_size /= 1024; | |
| else | |
| break; | |
| } | |
| *gres_scaled = tmp_gres_size; | |
| if (i == 0) | |
| *suffix = ""; | |
| else if (i == 1) | |
| *suffix = "K"; | |
| else if (i == 2) | |
| *suffix = "M"; | |
| else if (i == 3) | |
| *suffix = "G"; | |
| else | |
| *suffix = "T"; | |
| } | |
| /* | |
| * Add a GRES from node_feature plugin | |
| * IN node_name - name of the node for which the gres information applies | |
| * IN gres_name - name of the GRES being added or updated from the plugin | |
| * IN gres_size - count of this GRES on this node | |
| * IN/OUT new_config - Updated GRES info from slurm.conf | |
| * IN/OUT gres_list - List of GRES records for this node to track usage | |
| */ | |
| extern void gres_node_feature(char *node_name, | |
| char *gres_name, uint64_t gres_size, | |
| char **new_config, list_t **gres_list) | |
| { | |
| char *new_gres = NULL, *tok, *save_ptr = NULL, *sep = "", *suffix = ""; | |
| gres_state_t *gres_state_node; | |
| gres_node_state_t *gres_ns; | |
| uint32_t plugin_id; | |
| uint64_t gres_scaled = 0, drain_unalloc; | |
| int gres_name_len; | |
| xassert(gres_name); | |
| gres_name_len = strlen(gres_name); | |
| plugin_id = gres_build_id(gres_name); | |
| if (*new_config) { | |
| tok = strtok_r(*new_config, ",", &save_ptr); | |
| while (tok) { | |
| if (!strncmp(tok, gres_name, gres_name_len) && | |
| ((tok[gres_name_len] == ':') || | |
| (tok[gres_name_len] == '\0'))) { | |
| /* Skip this record */ | |
| } else { | |
| xstrfmtcat(new_gres, "%s%s", sep, tok); | |
| sep = ","; | |
| } | |
| tok = strtok_r(NULL, ",", &save_ptr); | |
| } | |
| } | |
| _gres_scale_value(gres_size, &gres_scaled, &suffix); | |
| xstrfmtcat(new_gres, "%s%s:%"PRIu64"%s", | |
| sep, gres_name, gres_scaled, suffix); | |
| xfree(*new_config); | |
| *new_config = new_gres; | |
| slurm_mutex_lock(&gres_context_lock); | |
| if (gres_context_cnt > 0) { | |
| if (*gres_list == NULL) | |
| *gres_list = list_create(_gres_node_list_delete); | |
| gres_state_node = list_find_first(*gres_list, gres_find_id, | |
| &plugin_id); | |
| if (gres_state_node == NULL) { | |
| gres_state_node = xmalloc(sizeof(gres_state_t)); | |
| /* FIXME: no config_flags known at this moment */ | |
| /* gres_state_node->config_flags = ; */ | |
| gres_state_node->plugin_id = plugin_id; | |
| gres_state_node->gres_data = _build_gres_node_state(); | |
| gres_state_node->gres_name = xstrdup(gres_name); | |
| gres_state_node->state_type = GRES_STATE_TYPE_NODE; | |
| list_append(*gres_list, gres_state_node); | |
| } | |
| gres_ns = gres_state_node->gres_data; | |
| drain_unalloc = gres_node_cnt_drain_unalloc(gres_ns); | |
| if (gres_size >= (gres_ns->gres_cnt_alloc + drain_unalloc)) { | |
| gres_ns->gres_cnt_avail = gres_size - | |
| gres_ns->gres_cnt_alloc - | |
| drain_unalloc; | |
| } else { | |
| error("%s: Changed size count of GRES %s from %"PRIu64 | |
| " to %"PRIu64", resource over allocated", | |
| __func__, gres_name, | |
| gres_ns->gres_cnt_avail, gres_size); | |
| gres_ns->gres_cnt_avail = 0; | |
| } | |
| gres_ns->gres_cnt_config = gres_size; | |
| gres_ns->gres_cnt_found = gres_size; | |
| gres_ns->node_feature = true; | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| } | |
| /* | |
| * Check validity of a GRES change. Specifically if a GRES type has "Files" | |
| * configured then the only valid new counts are the current count or zero | |
| * | |
| * RET true of the requested change is valid | |
| */ | |
| static int _node_reconfig_test(char *node_name, char *new_gres, | |
| gres_state_t *gres_state_node, | |
| slurm_gres_context_t *gres_ctx) | |
| { | |
| gres_node_state_t *orig_gres_ns, *new_gres_ns; | |
| int rc = SLURM_SUCCESS; | |
| xassert(gres_state_node); | |
| if (!(gres_ctx->config_flags & GRES_CONF_HAS_FILE)) | |
| return SLURM_SUCCESS; | |
| orig_gres_ns = gres_state_node->gres_data; | |
| new_gres_ns = _build_gres_node_state(); | |
| _get_gres_cnt(new_gres_ns, new_gres, | |
| gres_ctx->gres_name, | |
| gres_ctx->gres_name_colon, | |
| gres_ctx->gres_name_colon_len); | |
| if ((new_gres_ns->gres_cnt_config != 0) && | |
| (new_gres_ns->gres_cnt_config != | |
| orig_gres_ns->gres_cnt_config)) { | |
| error("Attempt to change gres/%s Count on node %s from %" | |
| PRIu64" to %"PRIu64" invalid with File configuration", | |
| gres_ctx->gres_name, node_name, | |
| orig_gres_ns->gres_cnt_config, | |
| new_gres_ns->gres_cnt_config); | |
| rc = ESLURM_INVALID_GRES; | |
| } | |
| _gres_node_state_delete(new_gres_ns); | |
| return rc; | |
| } | |
| static int _node_reconfig(char *node_name, char *new_gres, char **gres_str, | |
| gres_state_t *gres_state_node, bool config_overrides, | |
| slurm_gres_context_t *gres_ctx, | |
| bool *updated_gpu_cnt) | |
| { | |
| int i; | |
| gres_node_state_t *gres_ns; | |
| uint64_t gres_bits, orig_cnt; | |
| xassert(gres_state_node); | |
| xassert(updated_gpu_cnt); | |
| *updated_gpu_cnt = false; | |
| if (gres_state_node->gres_data == NULL) | |
| gres_state_node->gres_data = _build_gres_node_state(); | |
| gres_ns = gres_state_node->gres_data; | |
| orig_cnt = gres_ns->gres_cnt_config; | |
| _get_gres_cnt(gres_ns, new_gres, | |
| gres_ctx->gres_name, | |
| gres_ctx->gres_name_colon, | |
| gres_ctx->gres_name_colon_len); | |
| if (gres_ns->gres_cnt_config == orig_cnt) | |
| return SLURM_SUCCESS; /* No change in count */ | |
| /* Update count */ | |
| gres_ctx->total_cnt -= orig_cnt; | |
| gres_ctx->total_cnt += gres_ns->gres_cnt_config; | |
| gres_ns->gres_cnt_avail = gres_ns->gres_cnt_config; | |
| /* | |
| * The chunk layout names device indexes, so a changed count leaves it | |
| * pointing past the resized bitmaps. Only a registration can rebuild | |
| * it, so drop it until then rather than select from it. | |
| */ | |
| _clear_chunk_config(gres_ns); | |
| if (gres_ctx->config_flags & GRES_CONF_HAS_FILE) { | |
| if (gres_id_shared(gres_ctx->config_flags)) | |
| gres_bits = gres_ns->topo_cnt; | |
| else | |
| gres_bits = gres_ns->gres_cnt_avail; | |
| _gres_bit_alloc_resize(gres_ns, gres_bits); | |
| } else if (gres_ns->gres_bit_alloc && | |
| !gres_id_shared(gres_ctx->config_flags)) { | |
| /* | |
| * If GRES count changed in configuration between reboots, | |
| * update bitmap sizes as needed. | |
| */ | |
| gres_bits = gres_ns->gres_cnt_avail; | |
| if (gres_bits != bit_size(gres_ns->gres_bit_alloc)) { | |
| info("gres/%s count changed on node %s to %"PRIu64, | |
| gres_ctx->gres_name, node_name, gres_bits); | |
| if (gres_id_sharing(gres_ctx->plugin_id)) | |
| *updated_gpu_cnt = true; | |
| _gres_bit_alloc_resize(gres_ns, gres_bits); | |
| for (i = 0; i < gres_ns->topo_cnt; i++) { | |
| if (gres_ns->topo_gres_bitmap && | |
| gres_ns->topo_gres_bitmap[i] && | |
| (gres_bits != | |
| bit_size(gres_ns->topo_gres_bitmap[i]))){ | |
| bit_realloc(gres_ns->topo_gres_bitmap[i], | |
| gres_bits); | |
| } | |
| } | |
| } | |
| } | |
| /* | |
| * The config counts were reset above, so re-clamp the drain counts to | |
| * what each type still has available across reconfig. | |
| */ | |
| _gres_drain_cnt_clamp(gres_ns); | |
| return SLURM_SUCCESS; | |
| } | |
| /* Convert core bitmap into socket string, xfree return value */ | |
| static char *_core_bitmap2str(bitstr_t *core_map, int cores_per_sock, | |
| int sock_per_node) | |
| { | |
| char *sock_info = NULL, tmp[256]; | |
| bitstr_t *sock_map; | |
| int c, s, core_offset, max_core; | |
| bool any_set = false; | |
| xassert(core_map); | |
| max_core = bit_size(core_map) - 1; | |
| sock_map = bit_alloc(sock_per_node); | |
| for (s = 0; s < sock_per_node; s++) { | |
| core_offset = s * cores_per_sock; | |
| for (c = 0; c < cores_per_sock; c++) { | |
| if (core_offset > max_core) { | |
| error("%s: bad core offset (%d >= %d)", | |
| __func__, core_offset, max_core); | |
| break; | |
| } | |
| if (bit_test(core_map, core_offset++)) { | |
| bit_set(sock_map, s); | |
| any_set = true; | |
| break; | |
| } | |
| } | |
| } | |
| if (any_set) { | |
| bit_fmt(tmp, sizeof(tmp), sock_map); | |
| xstrfmtcat(sock_info, "(S:%s)", tmp); | |
| } else { | |
| /* We have a core bitmap with no bits set */ | |
| sock_info = xstrdup(""); | |
| } | |
| FREE_NULL_BITMAP(sock_map); | |
| return sock_info; | |
| } | |
| /* Given a count, modify it as needed and return suffix (e.g. "M" for mega ) */ | |
| static char *_get_suffix(uint64_t *count) | |
| { | |
| if (*count == 0) | |
| return ""; | |
| if ((*count % ((uint64_t)1024 * 1024 * 1024 * 1024 * 1024)) == 0) { | |
| *count /= ((uint64_t)1024 * 1024 * 1024 * 1024 * 1024); | |
| return "P"; | |
| } else if ((*count % ((uint64_t)1024 * 1024 * 1024 * 1024)) == 0) { | |
| *count /= ((uint64_t)1024 * 1024 * 1024 * 1024); | |
| return "T"; | |
| } else if ((*count % ((uint64_t)1024 * 1024 * 1024)) == 0) { | |
| *count /= ((uint64_t)1024 * 1024 * 1024); | |
| return "G"; | |
| } else if ((*count % (1024 * 1024)) == 0) { | |
| *count /= (1024 * 1024); | |
| return "M"; | |
| } else if ((*count % 1024) == 0) { | |
| *count /= 1024; | |
| return "K"; | |
| } else { | |
| return ""; | |
| } | |
| } | |
| /* Build node's GRES string based upon data in that node's GRES list */ | |
| static void _build_node_gres_str(list_t **gres_list, char **gres_str, | |
| int cores_per_sock, int sock_per_node) | |
| { | |
| gres_state_t *gres_state_node; | |
| gres_node_state_t *gres_ns; | |
| bitstr_t *done_topo, *core_map; | |
| uint64_t gres_sum; | |
| char *sep = "", *suffix, *sock_info = NULL, *sock_str, *no_consume_str; | |
| int c, i, j; | |
| xassert(gres_str); | |
| xfree(*gres_str); | |
| for (c = 0; c < gres_context_cnt; c++) { | |
| /* Find gres_state entry on the list */ | |
| gres_state_node = list_find_first(*gres_list, gres_find_id, | |
| &gres_context[c].plugin_id); | |
| if (gres_state_node == NULL) | |
| continue; /* Node has none of this GRES */ | |
| gres_ns = (gres_node_state_t *) gres_state_node->gres_data; | |
| no_consume_str = gres_ns->no_consume ? ":no_consume" : ""; | |
| if (gres_ns->topo_cnt && | |
| gres_ns->gres_cnt_avail) { | |
| done_topo = bit_alloc(gres_ns->topo_cnt); | |
| for (i = 0; i < gres_ns->topo_cnt; i++) { | |
| if (bit_test(done_topo, i)) | |
| continue; | |
| bit_set(done_topo, i); | |
| gres_sum = gres_ns-> | |
| topo_gres_cnt_avail[i]; | |
| if (gres_ns->topo_core_bitmap[i]) { | |
| core_map = bit_copy( | |
| gres_ns-> | |
| topo_core_bitmap[i]); | |
| } else | |
| core_map = NULL; | |
| for (j = 0; j < gres_ns->topo_cnt; j++){ | |
| if (gres_ns->topo_type_id[i] != | |
| gres_ns->topo_type_id[j]) | |
| continue; | |
| if (bit_test(done_topo, j)) | |
| continue; | |
| bit_set(done_topo, j); | |
| gres_sum += gres_ns-> | |
| topo_gres_cnt_avail[j]; | |
| if (core_map && | |
| gres_ns-> | |
| topo_core_bitmap[j]) { | |
| bit_or(core_map, | |
| gres_ns-> | |
| topo_core_bitmap[j]); | |
| } else if (gres_ns-> | |
| topo_core_bitmap[j]) { | |
| core_map = bit_copy( | |
| gres_ns-> | |
| topo_core_bitmap[j]); | |
| } | |
| } | |
| if (core_map) { | |
| sock_info = _core_bitmap2str( | |
| core_map, | |
| cores_per_sock, | |
| sock_per_node); | |
| FREE_NULL_BITMAP(core_map); | |
| sock_str = sock_info; | |
| } else | |
| sock_str = ""; | |
| suffix = _get_suffix(&gres_sum); | |
| if (gres_ns->topo_type_name[i]) { | |
| xstrfmtcat(*gres_str, | |
| "%s%s:%s%s:%"PRIu64"%s%s", sep, | |
| gres_context[c].gres_name, | |
| gres_ns-> | |
| topo_type_name[i], | |
| no_consume_str, gres_sum, | |
| suffix, sock_str); | |
| } else { | |
| xstrfmtcat(*gres_str, | |
| "%s%s%s:%"PRIu64"%s%s", sep, | |
| gres_context[c].gres_name, | |
| no_consume_str, gres_sum, | |
| suffix, sock_str); | |
| } | |
| xfree(sock_info); | |
| sep = ","; | |
| } | |
| FREE_NULL_BITMAP(done_topo); | |
| } else if (gres_ns->type_cnt && | |
| gres_ns->gres_cnt_avail) { | |
| for (i = 0; i < gres_ns->type_cnt; i++) { | |
| gres_sum = gres_ns->type_cnt_avail[i]; | |
| suffix = _get_suffix(&gres_sum); | |
| xstrfmtcat(*gres_str, "%s%s:%s%s:%"PRIu64"%s", | |
| sep, gres_context[c].gres_name, | |
| gres_ns->type_name[i], | |
| no_consume_str, gres_sum, suffix); | |
| sep = ","; | |
| } | |
| } else if (gres_ns->gres_cnt_avail) { | |
| gres_sum = gres_ns->gres_cnt_avail; | |
| suffix = _get_suffix(&gres_sum); | |
| xstrfmtcat(*gres_str, "%s%s%s:%"PRIu64"%s", | |
| sep, gres_context[c].gres_name, | |
| no_consume_str, gres_sum, suffix); | |
| sep = ","; | |
| } | |
| } | |
| } | |
| /* Stop the walk on a group the restored state cannot support. */ | |
| static int _foreach_validate_drain_group(void *x, void *arg) | |
| { | |
| drain_group_t *group = x; | |
| gres_node_state_t *gres_ns = arg; | |
| if (group->index_bits) { | |
| if (bit_size(group->index_bits) != _gres_drain_bits(gres_ns)) | |
| return -1; | |
| /* A group can only name devices that are actually drained. */ | |
| bit_and(group->index_bits, gres_ns->gres_bit_drain); | |
| } | |
| return 0; | |
| } | |
| /* Nothing of the group survived the TRES it named. */ | |
| static int _find_empty_drain_group(void *x, void *arg) | |
| { | |
| return drained_tres_group_empty(x); | |
| } | |
| /* | |
| * Check the drain groups just restored against the TRES they name. A | |
| * group's bitmap is sized like gres_bit_drain and is indexed by bit everywhere | |
| * it is read. A mismatch means a truncated or hand-edited state buffer; bail | |
| * rather than let a reader index past the bitmap. | |
| */ | |
| static int _validate_drain_groups(gres_node_state_t *gres_ns) | |
| { | |
| if (!gres_ns->drain_groups) | |
| return SLURM_SUCCESS; | |
| if (list_for_each(gres_ns->drain_groups, _foreach_validate_drain_group, | |
| gres_ns) < 0) | |
| return SLURM_ERROR; | |
| list_delete_all(gres_ns->drain_groups, _find_empty_drain_group, NULL); | |
| if (!list_count(gres_ns->drain_groups)) | |
| FREE_NULL_LIST(gres_ns->drain_groups); | |
| return SLURM_SUCCESS; | |
| } | |
| static int _foreach_node_state_pack(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_node = x; | |
| pack_state_t *pack_state = arg; | |
| gres_node_state_t *gres_ns = gres_state_node->gres_data; | |
| uint16_t gres_bitmap_size; | |
| if (pack_state->protocol_version >= SLURM_26_11_PROTOCOL_VERSION) { | |
| pack32(pack_state->magic, pack_state->buffer); | |
| pack32(gres_state_node->plugin_id, pack_state->buffer); | |
| pack32(gres_state_node->config_flags, pack_state->buffer); | |
| pack64(gres_ns->gres_cnt_avail, pack_state->buffer); | |
| /* | |
| * Just note if gres_bit_alloc exists. | |
| * Rebuild it based upon the state of recovered jobs | |
| */ | |
| if (gres_ns->gres_bit_alloc) | |
| gres_bitmap_size = bit_size(gres_ns->gres_bit_alloc); | |
| else | |
| gres_bitmap_size = 0; | |
| pack16(gres_bitmap_size, pack_state->buffer); | |
| pack16(gres_ns->topo_cnt, pack_state->buffer); | |
| for (int i = 0; i < gres_ns->topo_cnt; i++) { | |
| pack_bit_str_hex(gres_ns->topo_core_bitmap[i], | |
| pack_state->buffer); | |
| pack_bit_str_hex(gres_ns->topo_gres_bitmap[i], | |
| pack_state->buffer); | |
| pack_bit_str_hex(gres_ns->topo_res_core_bitmap[i], | |
| pack_state->buffer); | |
| } | |
| pack64_array(gres_ns->topo_gres_cnt_alloc, gres_ns->topo_cnt, | |
| pack_state->buffer); | |
| pack64_array(gres_ns->topo_gres_cnt_avail, gres_ns->topo_cnt, | |
| pack_state->buffer); | |
| pack32_array(gres_ns->topo_type_id, gres_ns->topo_cnt, | |
| pack_state->buffer); | |
| packstr_array(gres_ns->topo_type_name, gres_ns->topo_cnt, | |
| pack_state->buffer); | |
| /* | |
| * Chunk state is only built on registration, so without this | |
| * a restored node allocates with the chunk rules switched off | |
| * until its slurmd checks in. | |
| */ | |
| pack32(gres_ns->chunk_domain_cnt, pack_state->buffer); | |
| for (int d = 0; d < gres_ns->chunk_domain_cnt; d++) { | |
| gres_chunk_domain_t *dom = &gres_ns->chunk_domains[d]; | |
| pack32(dom->order_len, pack_state->buffer); | |
| for (int i = 0; i < dom->order_len; i++) | |
| pack32(dom->order[i], pack_state->buffer); | |
| packstr(dom->order_str, pack_state->buffer); | |
| pack32_array(dom->sizes, dom->sizes_cnt, | |
| pack_state->buffer); | |
| packstr(dom->sizes_str, pack_state->buffer); | |
| pack32(dom->type_id, pack_state->buffer); | |
| } | |
| pack32(gres_ns->dev_uuid_cnt, pack_state->buffer); | |
| if (gres_ns->dev_uuid_cnt) { | |
| packstr_array(gres_ns->dev_uuid, gres_ns->dev_uuid_cnt, | |
| pack_state->buffer); | |
| } | |
| /* The reasons the devices were drained with live in the groups. */ | |
| drained_tres_groups_pack(gres_ns->drain_groups, | |
| pack_state->protocol_version, | |
| pack_state->buffer); | |
| pack_bit_str_hex(gres_ns->gres_bit_drain, pack_state->buffer); | |
| pack64(gres_ns->gres_cnt_drain, pack_state->buffer); | |
| drained_tres_counts_pack(gres_ns->type_drain, | |
| pack_state->protocol_version, | |
| pack_state->buffer); | |
| drained_tres_count_pack(&gres_ns->untyped_drain, | |
| pack_state->protocol_version, | |
| pack_state->buffer); | |
| } else if (pack_state->protocol_version >= SLURM_MIN_PROTOCOL_VERSION) { | |
| pack32(pack_state->magic, pack_state->buffer); | |
| pack32(gres_state_node->plugin_id, pack_state->buffer); | |
| pack32(gres_state_node->config_flags, pack_state->buffer); | |
| pack64(gres_ns->gres_cnt_avail, pack_state->buffer); | |
| /* | |
| * Just note if gres_bit_alloc exists. | |
| * Rebuild it based upon the state of recovered jobs | |
| */ | |
| if (gres_ns->gres_bit_alloc) | |
| gres_bitmap_size = bit_size(gres_ns->gres_bit_alloc); | |
| else | |
| gres_bitmap_size = 0; | |
| pack16(gres_bitmap_size, pack_state->buffer); | |
| pack16(gres_ns->topo_cnt, pack_state->buffer); | |
| for (int i = 0; i < gres_ns->topo_cnt; i++) { | |
| pack_bit_str_hex(gres_ns->topo_core_bitmap[i], | |
| pack_state->buffer); | |
| pack_bit_str_hex(gres_ns->topo_gres_bitmap[i], | |
| pack_state->buffer); | |
| pack_bit_str_hex(gres_ns->topo_res_core_bitmap[i], | |
| pack_state->buffer); | |
| } | |
| pack64_array(gres_ns->topo_gres_cnt_alloc, gres_ns->topo_cnt, | |
| pack_state->buffer); | |
| pack64_array(gres_ns->topo_gres_cnt_avail, gres_ns->topo_cnt, | |
| pack_state->buffer); | |
| pack32_array(gres_ns->topo_type_id, gres_ns->topo_cnt, | |
| pack_state->buffer); | |
| packstr_array(gres_ns->topo_type_name, gres_ns->topo_cnt, | |
| pack_state->buffer); | |
| } else { | |
| error("%s: protocol_version %hu not supported", | |
| __func__, pack_state->protocol_version); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int _foreach_job_state_pack(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_job = x; | |
| pack_state_t *pack_state = arg; | |
| gres_job_state_t *gres_js = gres_state_job->gres_data; | |
| int i; | |
| if (pack_state->protocol_version >= SLURM_MIN_PROTOCOL_VERSION) { | |
| pack32(pack_state->magic, pack_state->buffer); | |
| pack32(gres_state_job->plugin_id, pack_state->buffer); | |
| pack16(gres_js->cpus_per_gres, pack_state->buffer); | |
| pack16(gres_js->flags, pack_state->buffer); | |
| pack64(gres_js->gres_per_job, pack_state->buffer); | |
| pack64(gres_js->gres_per_node, pack_state->buffer); | |
| pack64(gres_js->gres_per_socket, pack_state->buffer); | |
| pack64(gres_js->gres_per_task, pack_state->buffer); | |
| pack64(gres_js->mem_per_gres, pack_state->buffer); | |
| pack16(gres_js->ntasks_per_gres, pack_state->buffer); | |
| pack64(gres_js->total_gres, pack_state->buffer); | |
| packstr(gres_js->type_name, pack_state->buffer); | |
| pack32(gres_js->node_cnt, pack_state->buffer); | |
| if (gres_js->gres_cnt_node_alloc) { | |
| pack8((uint8_t) 1, pack_state->buffer); | |
| pack64_array(gres_js->gres_cnt_node_alloc, | |
| gres_js->node_cnt, pack_state->buffer); | |
| } else { | |
| pack8((uint8_t) 0, pack_state->buffer); | |
| } | |
| if (gres_js->gres_bit_alloc) { | |
| pack8((uint8_t) 1, pack_state->buffer); | |
| for (i = 0; i < gres_js->node_cnt; i++) { | |
| pack_bit_str_hex(gres_js-> | |
| gres_bit_alloc[i], | |
| pack_state->buffer); | |
| } | |
| } else { | |
| pack8((uint8_t) 0, pack_state->buffer); | |
| } | |
| for (i = 0; i < gres_js->node_cnt; i++) { | |
| if (!gres_js->gres_per_bit_alloc || | |
| !gres_js->gres_per_bit_alloc[i] || | |
| !gres_js->gres_bit_alloc || | |
| !gres_js->gres_bit_alloc[i]) { | |
| pack8((uint8_t)0, pack_state->buffer); | |
| continue; | |
| } | |
| pack8((uint8_t)1, pack_state->buffer); | |
| pack64_array( | |
| gres_js->gres_per_bit_alloc[i], | |
| bit_size(gres_js->gres_bit_alloc[i]), | |
| pack_state->buffer); | |
| } | |
| if (pack_state->details && gres_js->gres_bit_step_alloc) { | |
| pack8((uint8_t) 1, pack_state->buffer); | |
| for (i = 0; i < gres_js->node_cnt; i++) { | |
| pack_bit_str_hex(gres_js-> | |
| gres_bit_step_alloc[i], | |
| pack_state->buffer); | |
| } | |
| } else { | |
| pack8((uint8_t) 0, pack_state->buffer); | |
| } | |
| if (pack_state->details && gres_js->gres_cnt_step_alloc) { | |
| pack8((uint8_t) 1, pack_state->buffer); | |
| for (i = 0; i < gres_js->node_cnt; i++) { | |
| pack64(gres_js-> | |
| gres_cnt_step_alloc[i], | |
| pack_state->buffer); | |
| } | |
| } else { | |
| pack8((uint8_t) 0, pack_state->buffer); | |
| } | |
| for (i = 0; i < gres_js->node_cnt; i++) { | |
| if (!pack_state->details || | |
| !gres_js->gres_per_bit_step_alloc || | |
| !gres_js->gres_per_bit_step_alloc[i] || | |
| !gres_js->gres_bit_step_alloc || | |
| !gres_js->gres_bit_step_alloc[i]) { | |
| pack8((uint8_t)0, pack_state->buffer); | |
| continue; | |
| } | |
| pack8((uint8_t)1, pack_state->buffer); | |
| pack64_array( | |
| gres_js->gres_per_bit_step_alloc[i], | |
| bit_size(gres_js->gres_bit_step_alloc[i]), | |
| pack_state->buffer); | |
| } | |
| } else { | |
| error("%s: protocol_version %hu not supported", | |
| __func__, pack_state->protocol_version); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int _foreach_step_state_pack(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_step = x; | |
| pack_state_t *pack_state = arg; | |
| gres_step_state_t *gres_ss = gres_state_step->gres_data; | |
| int i; | |
| if (pack_state->protocol_version >= SLURM_MIN_PROTOCOL_VERSION) { | |
| pack32(pack_state->magic, pack_state->buffer); | |
| pack32(gres_state_step->plugin_id, pack_state->buffer); | |
| pack16(gres_ss->cpus_per_gres, pack_state->buffer); | |
| pack16(gres_ss->flags, pack_state->buffer); | |
| pack64(gres_ss->gres_per_step, pack_state->buffer); | |
| pack64(gres_ss->gres_per_node, pack_state->buffer); | |
| pack64(gres_ss->gres_per_socket, pack_state->buffer); | |
| pack64(gres_ss->gres_per_task, pack_state->buffer); | |
| pack64(gres_ss->mem_per_gres, pack_state->buffer); | |
| pack64(gres_ss->total_gres, pack_state->buffer); | |
| packstr(gres_ss->type_name, pack_state->buffer); | |
| pack32(gres_ss->node_cnt, pack_state->buffer); | |
| pack_bit_str_hex(gres_ss->node_in_use, pack_state->buffer); | |
| if (gres_ss->gres_cnt_node_alloc) { | |
| pack8((uint8_t) 1, pack_state->buffer); | |
| pack64_array(gres_ss->gres_cnt_node_alloc, | |
| gres_ss->node_cnt, pack_state->buffer); | |
| } else { | |
| pack8((uint8_t) 0, pack_state->buffer); | |
| } | |
| if (gres_ss->gres_bit_alloc) { | |
| pack8((uint8_t) 1, pack_state->buffer); | |
| for (i = 0; i < gres_ss->node_cnt; i++) | |
| pack_bit_str_hex(gres_ss->gres_bit_alloc[i], | |
| pack_state->buffer); | |
| } else { | |
| pack8((uint8_t) 0, pack_state->buffer); | |
| } | |
| for (i = 0; i < gres_ss->node_cnt; i++) { | |
| if (!gres_ss->gres_per_bit_alloc || | |
| !gres_ss->gres_per_bit_alloc[i] || | |
| !gres_ss->gres_bit_alloc || | |
| !gres_ss->gres_bit_alloc[i]) { | |
| pack8((uint8_t)0, pack_state->buffer); | |
| continue; | |
| } | |
| pack8((uint8_t)1, pack_state->buffer); | |
| pack64_array(gres_ss->gres_per_bit_alloc[i], | |
| bit_size(gres_ss->gres_bit_alloc[i]), | |
| pack_state->buffer); | |
| } | |
| } else { | |
| error("%s: protocol_version %hu not supported", | |
| __func__, pack_state->protocol_version); | |
| return -1; | |
| } | |
| return 0; | |
| } | |
| static int _pack_state(list_t *gres_list, pack_state_t *pack_state, | |
| int (*pack_function) (void *x, void *key)) | |
| { | |
| int rc = SLURM_SUCCESS; | |
| uint32_t top_offset, tail_offset; | |
| uint16_t rec_cnt = 0; | |
| top_offset = get_buf_offset(pack_state->buffer); | |
| pack16(rec_cnt, pack_state->buffer); /* placeholder if data */ | |
| if (!gres_list) | |
| return rc; | |
| rec_cnt = list_for_each(gres_list, pack_function, pack_state); | |
| if (rec_cnt > 0) { | |
| tail_offset = get_buf_offset(pack_state->buffer); | |
| set_buf_offset(pack_state->buffer, top_offset); | |
| pack16(rec_cnt, pack_state->buffer); | |
| set_buf_offset(pack_state->buffer, tail_offset); | |
| } | |
| return rc; | |
| } | |
| /* | |
| * Note that a node's configuration has been modified (e.g. "scontol update ..") | |
| * IN node_name - name of the node for which the gres information applies | |
| * IN new_gres - Updated GRES information supplied from slurm.conf or scontrol | |
| * IN/OUT gres_str - Node's current GRES string, updated as needed | |
| * IN/OUT gres_list - List of Gres records for this node to track usage | |
| * IN config_overrides - true: Don't validate hardware, use slurm.conf | |
| * configuration | |
| * false: Validate hardware config, but use slurm.conf | |
| * config | |
| * IN cores_per_sock - Number of cores per socket on this node | |
| * IN sock_per_node - Total count of sockets on this node (on any board) | |
| */ | |
| extern int gres_node_reconfig(char *node_name, | |
| char *new_gres, | |
| char **gres_str, | |
| list_t **gres_list, | |
| bool config_overrides, | |
| int cores_per_sock, | |
| int sock_per_node) | |
| { | |
| int i, rc = SLURM_SUCCESS; | |
| gres_state_t *gres_state_node = NULL, **gres_state_node_array; | |
| gres_state_t *gpu_gres_state_node = NULL; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| gres_state_node_array = xcalloc(gres_context_cnt, | |
| sizeof(gres_state_t *)); | |
| if ((gres_context_cnt > 0) && (*gres_list == NULL)) | |
| *gres_list = list_create(_gres_node_list_delete); | |
| /* First validate all of the requested GRES changes */ | |
| for (i = 0; (rc == SLURM_SUCCESS) && (i < gres_context_cnt); i++) { | |
| /* Find gres_state entry on the list */ | |
| gres_state_node = list_find_first(*gres_list, gres_find_id, | |
| &gres_context[i].plugin_id); | |
| if (gres_state_node == NULL) | |
| continue; | |
| gres_state_node_array[i] = gres_state_node; | |
| rc = _node_reconfig_test(node_name, new_gres, gres_state_node, | |
| &gres_context[i]); | |
| } | |
| /* Now update the GRES counts */ | |
| for (i = 0; (rc == SLURM_SUCCESS) && (i < gres_context_cnt); i++) { | |
| bool updated_gpu_cnt = false; | |
| if (gres_state_node_array[i] == NULL) | |
| continue; | |
| rc = _node_reconfig(node_name, new_gres, gres_str, | |
| gres_state_node_array[i], config_overrides, | |
| &gres_context[i], &updated_gpu_cnt); | |
| if (updated_gpu_cnt) | |
| gpu_gres_state_node = gres_state_node_array[i]; | |
| } | |
| /* Now synchronize gres/gpu and gres/'shared' state */ | |
| if (gpu_gres_state_node) { | |
| /* Update gres/'shared' counts and bitmaps to match gres/gpu */ | |
| _sync_node_shared_to_sharing(gpu_gres_state_node); | |
| } | |
| /* Build new per-node gres_str */ | |
| _build_node_gres_str(gres_list, gres_str, cores_per_sock,sock_per_node); | |
| slurm_mutex_unlock(&gres_context_lock); | |
| xfree(gres_state_node_array); | |
| return rc; | |
| } | |
| extern void gres_node_remove(node_record_t *node_ptr) | |
| { | |
| if (!node_ptr->gres_list) | |
| return; | |
| slurm_mutex_lock(&gres_context_lock); | |
| for (int i = 0; i < gres_context_cnt; i++) { | |
| gres_state_t *gres_state_node; | |
| if (!(gres_state_node = | |
| list_find_first(node_ptr->gres_list, gres_find_id, | |
| &gres_context[i].plugin_id))) | |
| continue; | |
| if (gres_state_node->gres_data) { | |
| gres_node_state_t *gres_ns = gres_state_node->gres_data; | |
| gres_context[i].total_cnt -= gres_ns->gres_cnt_config; | |
| } | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| } | |
| /* | |
| * Pack a node's current gres status, called from slurmctld for save/restore | |
| * IN gres_list - generated by gres_node_config_validate() | |
| * IN/OUT buffer - location to write state to | |
| */ | |
| extern int gres_node_state_pack(list_t *gres_list, buf_t *buffer, | |
| uint16_t protocol_version) | |
| { | |
| pack_state_t pack_state = { | |
| .buffer = buffer, | |
| .magic = GRES_MAGIC, | |
| .protocol_version = protocol_version, | |
| }; | |
| return _pack_state(gres_list, &pack_state, _foreach_node_state_pack); | |
| } | |
| /* | |
| * type_cnt_avail is restored from the node config at registration. Seed it | |
| * from a count pool so an intervening clamp does not drop a just-unpacked | |
| * drain count to zero. | |
| */ | |
| static int _seed_pool_type_avail(void *x, void *arg) | |
| { | |
| drain_count_t *pool = x; | |
| gres_node_state_t *gres_ns = arg; | |
| int type; | |
| if (!pool->count || !pool->name) | |
| return 0; | |
| gres_add_type(pool->name, gres_ns, 0); | |
| type = _find_gres_type_name(gres_ns, pool->name); | |
| if ((type >= 0) && (gres_ns->type_cnt_avail[type] < pool->count)) | |
| gres_ns->type_cnt_avail[type] = pool->count; | |
| return 0; | |
| } | |
| /* | |
| * Unpack a node's current gres status, called from slurmctld for save/restore | |
| * OUT gres_list - restored state stored by gres_node_state_pack() | |
| * IN/OUT buffer - location to read state from | |
| * IN node_name - name of the node for which the gres information applies | |
| */ | |
| extern int gres_node_state_unpack(list_t **gres_list, buf_t *buffer, | |
| char *node_name, | |
| uint16_t protocol_version) | |
| { | |
| int rc = SLURM_SUCCESS; | |
| uint32_t magic = 0, plugin_id = 0, config_flags = 0; | |
| uint16_t gres_bitmap_size = 0, rec_cnt = 0; | |
| gres_state_t *gres_state_node; | |
| gres_node_state_t *gres_ns = NULL; | |
| bool locked = false; | |
| safe_unpack16(&rec_cnt, buffer); | |
| if (rec_cnt == 0) | |
| return SLURM_SUCCESS; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| locked = true; | |
| if ((gres_context_cnt > 0) && (*gres_list == NULL)) | |
| *gres_list = list_create(_gres_node_list_delete); | |
| while ((rc == SLURM_SUCCESS) && (rec_cnt)) { | |
| uint32_t tmp_uint32; | |
| uint32_t full_config_flags = 0; | |
| slurm_gres_context_t *gres_ctx; | |
| if ((buffer == NULL) || (remaining_buf(buffer) == 0)) | |
| break; | |
| rec_cnt--; | |
| gres_ns = _build_gres_node_state(); | |
| if (protocol_version >= SLURM_26_11_PROTOCOL_VERSION) { | |
| safe_unpack32(&magic, buffer); | |
| if (magic != GRES_MAGIC) | |
| goto unpack_error; | |
| safe_unpack32(&plugin_id, buffer); | |
| safe_unpack32(&config_flags, buffer); | |
| safe_unpack64(&gres_ns->gres_cnt_avail, buffer); | |
| safe_unpack16(&gres_bitmap_size, buffer); | |
| safe_unpack16(&gres_ns->topo_cnt, buffer); | |
| if (gres_ns->topo_cnt) { | |
| gres_ns->topo_core_bitmap = | |
| xcalloc(gres_ns->topo_cnt, | |
| sizeof(bitstr_t *)); | |
| gres_ns->topo_gres_bitmap = | |
| xcalloc(gres_ns->topo_cnt, | |
| sizeof(bitstr_t *)); | |
| gres_ns->topo_res_core_bitmap = | |
| xcalloc(gres_ns->topo_cnt, | |
| sizeof(bitstr_t *)); | |
| for (int i = 0; i < gres_ns->topo_cnt; i++) { | |
| unpack_bit_str_hex( | |
| &gres_ns->topo_core_bitmap[i], | |
| buffer); | |
| unpack_bit_str_hex( | |
| &gres_ns->topo_gres_bitmap[i], | |
| buffer); | |
| unpack_bit_str_hex( | |
| &gres_ns->topo_res_core_bitmap | |
| [i], | |
| buffer); | |
| } | |
| } | |
| safe_unpack64_array(&gres_ns->topo_gres_cnt_alloc, | |
| &tmp_uint32, buffer); | |
| safe_unpack64_array(&gres_ns->topo_gres_cnt_avail, | |
| &tmp_uint32, buffer); | |
| safe_unpack32_array(&gres_ns->topo_type_id, &tmp_uint32, | |
| buffer); | |
| safe_unpackstr_array(&gres_ns->topo_type_name, | |
| &tmp_uint32, buffer); | |
| safe_unpack32(&tmp_uint32, buffer); | |
| if (tmp_uint32 > GRES_MAX_CHUNK_SIZE) | |
| goto unpack_error; | |
| if (tmp_uint32) | |
| gres_ns->chunk_domains = xcalloc( | |
| tmp_uint32, | |
| sizeof(*gres_ns->chunk_domains)); | |
| for (int d = 0; d < tmp_uint32; d++) { | |
| gres_chunk_domain_t *dom = | |
| &gres_ns->chunk_domains[d]; | |
| uint32_t cnt; | |
| /* Count it now so an error frees it */ | |
| gres_ns->chunk_domain_cnt++; | |
| safe_unpack32(&cnt, buffer); | |
| if (cnt > GRES_MAX_CHUNK_SIZE) | |
| goto unpack_error; | |
| dom->order_len = cnt; | |
| if (dom->order_len) | |
| dom->order = xcalloc(dom->order_len, | |
| sizeof(int)); | |
| for (int i = 0; i < dom->order_len; i++) { | |
| safe_unpack32(&cnt, buffer); | |
| dom->order[i] = cnt; | |
| } | |
| safe_unpackstr(&dom->order_str, buffer); | |
| safe_unpack32_array(&dom->sizes, &cnt, buffer); | |
| if (cnt > GRES_MAX_CHUNK_SIZE) | |
| goto unpack_error; | |
| dom->sizes_cnt = cnt; | |
| safe_unpackstr(&dom->sizes_str, buffer); | |
| safe_unpack32(&dom->type_id, buffer); | |
| } | |
| safe_unpack32(&tmp_uint32, buffer); | |
| if (tmp_uint32 > MAX_GRES_BITMAP) | |
| goto unpack_error; | |
| if (tmp_uint32) { | |
| uint32_t cnt; | |
| safe_unpackstr_array(&gres_ns->dev_uuid, &cnt, | |
| buffer); | |
| /* Set first, so unpack_error frees the strings */ | |
| gres_ns->dev_uuid_cnt = cnt; | |
| if (cnt != tmp_uint32) | |
| goto unpack_error; | |
| for (uint32_t u = 0; u < cnt; u++) { | |
| if (gres_ns->dev_uuid[u] && | |
| !gres_ns->dev_uuid[u][0]) | |
| xfree(gres_ns->dev_uuid[u]); | |
| } | |
| } | |
| if (drained_tres_groups_unpack(&gres_ns->drain_groups, | |
| protocol_version, | |
| buffer) != SLURM_SUCCESS) | |
| goto unpack_error; | |
| unpack_bit_str_hex(&gres_ns->gres_bit_drain, buffer); | |
| safe_unpack64(&gres_ns->gres_cnt_drain, buffer); | |
| if (drained_tres_counts_unpack(&gres_ns->type_drain, | |
| protocol_version, | |
| buffer) != SLURM_SUCCESS) | |
| goto unpack_error; | |
| if (drained_tres_count_unpack(&gres_ns->untyped_drain, | |
| protocol_version, | |
| buffer) != SLURM_SUCCESS) | |
| goto unpack_error; | |
| /* The groups are checked against gres_bit_drain */ | |
| if (_validate_drain_groups(gres_ns) != SLURM_SUCCESS) | |
| goto unpack_error; | |
| } else if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) { | |
| safe_unpack32(&magic, buffer); | |
| if (magic != GRES_MAGIC) | |
| goto unpack_error; | |
| safe_unpack32(&plugin_id, buffer); | |
| safe_unpack32(&config_flags, buffer); | |
| safe_unpack64(&gres_ns->gres_cnt_avail, buffer); | |
| safe_unpack16(&gres_bitmap_size, buffer); | |
| safe_unpack16(&gres_ns->topo_cnt, buffer); | |
| if (gres_ns->topo_cnt) { | |
| gres_ns->topo_core_bitmap = | |
| xcalloc(gres_ns->topo_cnt, | |
| sizeof(bitstr_t *)); | |
| gres_ns->topo_gres_bitmap = | |
| xcalloc(gres_ns->topo_cnt, | |
| sizeof(bitstr_t *)); | |
| gres_ns->topo_res_core_bitmap = | |
| xcalloc(gres_ns->topo_cnt, | |
| sizeof(bitstr_t *)); | |
| for (int i = 0; i < gres_ns->topo_cnt; i++) { | |
| unpack_bit_str_hex( | |
| &gres_ns->topo_core_bitmap[i], | |
| buffer); | |
| unpack_bit_str_hex( | |
| &gres_ns->topo_gres_bitmap[i], | |
| buffer); | |
| unpack_bit_str_hex( | |
| &gres_ns-> | |
| topo_res_core_bitmap[i], | |
| buffer); | |
| } | |
| } | |
| safe_unpack64_array(&gres_ns->topo_gres_cnt_alloc, | |
| &tmp_uint32, buffer); | |
| safe_unpack64_array(&gres_ns->topo_gres_cnt_avail, | |
| &tmp_uint32, buffer); | |
| safe_unpack32_array(&gres_ns->topo_type_id, &tmp_uint32, | |
| buffer); | |
| safe_unpackstr_array(&gres_ns->topo_type_name, | |
| &tmp_uint32, buffer); | |
| } else { | |
| error("%s: protocol_version %hu not supported", | |
| __func__, protocol_version); | |
| goto unpack_error; | |
| } | |
| /* | |
| * type_cnt_avail is filled again at registration. Seed it | |
| * here so a clamp cannot drop a drain count just restored. | |
| * This does not read the buffer. | |
| */ | |
| if (gres_ns->type_drain) | |
| (void) list_for_each(gres_ns->type_drain, | |
| _seed_pool_type_avail, | |
| gres_ns); | |
| if (!(gres_ctx = _find_context_by_id(plugin_id))) { | |
| error("%s: no plugin configured to unpack data type %u from node %s", | |
| __func__, plugin_id, node_name); | |
| /* | |
| * A likely sign that GresPlugins has changed. | |
| * Not a fatal error, skip over the data. | |
| */ | |
| _gres_node_state_delete(gres_ns); | |
| continue; | |
| } | |
| if (gres_bitmap_size) { | |
| gres_ns->gres_bit_alloc = | |
| bit_alloc(gres_bitmap_size); | |
| } | |
| /* We don't want to lose flags from gres_ctx */ | |
| full_config_flags = gres_ctx->config_flags; | |
| /* | |
| * Flag this as flags read from state so we only use them until | |
| * the node checks in. | |
| */ | |
| gres_ctx->config_flags = config_flags | GRES_CONF_FROM_STATE; | |
| gres_state_node = gres_create_state( | |
| gres_ctx, GRES_STATE_SRC_CONTEXT_PTR, | |
| GRES_STATE_TYPE_NODE, gres_ns); | |
| list_append(*gres_list, gres_state_node); | |
| gres_ctx->config_flags |= full_config_flags; | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return rc; | |
| unpack_error: | |
| error("%s: unpack error from node %s", __func__, node_name); | |
| _gres_node_state_delete(gres_ns); | |
| if (locked) | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return SLURM_ERROR; | |
| } | |
| static int _dup_drain_id(void *x, void *arg) | |
| { | |
| list_append((list_t *) arg, xstrdup(x)); | |
| return 0; | |
| } | |
| static int _dup_drain_group(void *x, void *arg) | |
| { | |
| drain_group_t *group = x; | |
| drain_group_t *copy = xmalloc(sizeof(*copy)); | |
| copy->reason = xstrdup(group->reason); | |
| if (group->index_bits) | |
| copy->index_bits = bit_copy(group->index_bits); | |
| if (group->ids) { | |
| copy->ids = list_create(xfree_ptr); | |
| (void) list_for_each(group->ids, _dup_drain_id, copy->ids); | |
| } | |
| list_append((list_t *) arg, copy); | |
| return 0; | |
| } | |
| /* Deep copy. NULL when groups is NULL. */ | |
| static list_t *_drain_groups_dup(list_t *groups) | |
| { | |
| list_t *out; | |
| if (!groups) | |
| return NULL; | |
| out = list_create(drained_tres_group_delete); | |
| (void) list_for_each(groups, _dup_drain_group, out); | |
| return out; | |
| } | |
| static int _dup_drain_count(void *x, void *arg) | |
| { | |
| drain_count_t *src = x; | |
| drain_count_t *copy = xmalloc(sizeof(*copy)); | |
| copy->name = xstrdup(src->name); | |
| copy->count = src->count; | |
| copy->reason = xstrdup(src->reason); | |
| list_append((list_t *) arg, copy); | |
| return 0; | |
| } | |
| /* Deep copy. NULL when counts is NULL. */ | |
| static list_t *_drain_counts_dup(list_t *counts) | |
| { | |
| list_t *out; | |
| if (!counts) | |
| return NULL; | |
| out = list_create(drained_tres_count_delete); | |
| (void) list_for_each(counts, _dup_drain_count, out); | |
| return out; | |
| } | |
| static void *_node_state_dup(gres_node_state_t *gres_ns) | |
| { | |
| int i, j; | |
| gres_node_state_t *new_gres_ns; | |
| if (gres_ns == NULL) | |
| return NULL; | |
| new_gres_ns = xmalloc(sizeof(gres_node_state_t)); | |
| new_gres_ns->gres_cnt_found = gres_ns->gres_cnt_found; | |
| new_gres_ns->gres_cnt_config = gres_ns->gres_cnt_config; | |
| new_gres_ns->gres_cnt_avail = gres_ns->gres_cnt_avail; | |
| new_gres_ns->gres_cnt_alloc = gres_ns->gres_cnt_alloc; | |
| new_gres_ns->gres_cnt_drain = gres_ns->gres_cnt_drain; | |
| new_gres_ns->no_consume = gres_ns->no_consume; | |
| if (gres_ns->gres_bit_alloc) | |
| new_gres_ns->gres_bit_alloc = bit_copy(gres_ns->gres_bit_alloc); | |
| if (gres_ns->gres_bit_drain) | |
| new_gres_ns->gres_bit_drain = bit_copy(gres_ns->gres_bit_drain); | |
| /* | |
| * Device groups, the typed count list, and the untyped pool. A | |
| * dry-run resume reads the counts from the duplicate. | |
| */ | |
| new_gres_ns->drain_groups = _drain_groups_dup(gres_ns->drain_groups); | |
| new_gres_ns->untyped_drain.count = gres_ns->untyped_drain.count; | |
| new_gres_ns->untyped_drain.reason = | |
| xstrdup(gres_ns->untyped_drain.reason); | |
| new_gres_ns->type_drain = _drain_counts_dup(gres_ns->type_drain); | |
| if (gres_ns->dev_uuid_cnt) { | |
| new_gres_ns->dev_uuid_cnt = gres_ns->dev_uuid_cnt; | |
| new_gres_ns->dev_uuid = | |
| xcalloc(gres_ns->dev_uuid_cnt, sizeof(char *)); | |
| for (i = 0; i < gres_ns->dev_uuid_cnt; i++) | |
| new_gres_ns->dev_uuid[i] = | |
| xstrdup(gres_ns->dev_uuid[i]); | |
| } | |
| if (gres_ns->links_cnt && gres_ns->link_len) { | |
| new_gres_ns->links_cnt = xcalloc(gres_ns->link_len, | |
| sizeof(int *)); | |
| j = sizeof(int) * gres_ns->link_len; | |
| for (i = 0; i < gres_ns->link_len; i++) { | |
| new_gres_ns->links_cnt[i] = xmalloc(j); | |
| memcpy(new_gres_ns->links_cnt[i], | |
| gres_ns->links_cnt[i], j); | |
| } | |
| new_gres_ns->link_len = gres_ns->link_len; | |
| } | |
| if (gres_ns->chunk_domain_cnt) { | |
| new_gres_ns->chunk_domains = | |
| xcalloc(gres_ns->chunk_domain_cnt, | |
| sizeof(*new_gres_ns->chunk_domains)); | |
| new_gres_ns->chunk_domain_cnt = gres_ns->chunk_domain_cnt; | |
| for (i = 0; i < gres_ns->chunk_domain_cnt; i++) { | |
| gres_chunk_domain_t *dom = &gres_ns->chunk_domains[i]; | |
| gres_chunk_domain_t *new_dom = | |
| &new_gres_ns->chunk_domains[i]; | |
| *new_dom = *dom; | |
| new_dom->sizes = | |
| xcalloc(dom->sizes_cnt, sizeof(*dom->sizes)); | |
| memcpy(new_dom->sizes, dom->sizes, | |
| dom->sizes_cnt * sizeof(*dom->sizes)); | |
| new_dom->order = | |
| xcalloc(dom->order_len, sizeof(*dom->order)); | |
| memcpy(new_dom->order, dom->order, | |
| dom->order_len * sizeof(*dom->order)); | |
| new_dom->order_str = xstrdup(dom->order_str); | |
| new_dom->sizes_str = xstrdup(dom->sizes_str); | |
| } | |
| } | |
| if (gres_ns->topo_cnt) { | |
| new_gres_ns->topo_cnt = gres_ns->topo_cnt; | |
| new_gres_ns->topo_core_bitmap = xcalloc(gres_ns->topo_cnt, | |
| sizeof(bitstr_t *)); | |
| new_gres_ns->topo_gres_bitmap = xcalloc(gres_ns->topo_cnt, | |
| sizeof(bitstr_t *)); | |
| new_gres_ns->topo_res_core_bitmap = xcalloc(gres_ns->topo_cnt, | |
| sizeof(bitstr_t *)); | |
| new_gres_ns->topo_gres_cnt_alloc = xcalloc(gres_ns->topo_cnt, | |
| sizeof(uint64_t)); | |
| new_gres_ns->topo_gres_cnt_avail = xcalloc(gres_ns->topo_cnt, | |
| sizeof(uint64_t)); | |
| new_gres_ns->topo_type_id = xcalloc(gres_ns->topo_cnt, | |
| sizeof(uint32_t)); | |
| new_gres_ns->topo_type_name = xcalloc(gres_ns->topo_cnt, | |
| sizeof(char *)); | |
| for (i = 0; i < gres_ns->topo_cnt; i++) { | |
| if (gres_ns->topo_core_bitmap[i]) { | |
| new_gres_ns->topo_core_bitmap[i] = | |
| bit_copy(gres_ns->topo_core_bitmap[i]); | |
| } | |
| if (gres_ns->topo_res_core_bitmap[i]) { | |
| new_gres_ns->topo_res_core_bitmap[i] = | |
| bit_copy(gres_ns-> | |
| topo_res_core_bitmap[i]); | |
| } | |
| if (gres_ns->topo_gres_bitmap[i]) { | |
| new_gres_ns->topo_gres_bitmap[i] = | |
| bit_copy(gres_ns->topo_gres_bitmap[i]); | |
| } | |
| new_gres_ns->topo_gres_cnt_alloc[i] = | |
| gres_ns->topo_gres_cnt_alloc[i]; | |
| new_gres_ns->topo_gres_cnt_avail[i] = | |
| gres_ns->topo_gres_cnt_avail[i]; | |
| new_gres_ns->topo_type_id[i] = gres_ns->topo_type_id[i]; | |
| new_gres_ns->topo_type_name[i] = | |
| xstrdup(gres_ns->topo_type_name[i]); | |
| } | |
| } | |
| if (gres_ns->type_cnt) { | |
| new_gres_ns->type_cnt = gres_ns->type_cnt; | |
| new_gres_ns->type_cnt_alloc = xcalloc(gres_ns->type_cnt, | |
| sizeof(uint64_t)); | |
| new_gres_ns->type_cnt_avail = xcalloc(gres_ns->type_cnt, | |
| sizeof(uint64_t)); | |
| new_gres_ns->type_id = xcalloc(gres_ns->type_cnt, | |
| sizeof(uint32_t)); | |
| new_gres_ns->type_name = xcalloc(gres_ns->type_cnt, | |
| sizeof(char *)); | |
| for (i = 0; i < gres_ns->type_cnt; i++) { | |
| new_gres_ns->type_cnt_alloc[i] = | |
| gres_ns->type_cnt_alloc[i]; | |
| new_gres_ns->type_cnt_avail[i] = | |
| gres_ns->type_cnt_avail[i]; | |
| new_gres_ns->type_id[i] = gres_ns->type_id[i]; | |
| new_gres_ns->type_name[i] = | |
| xstrdup(gres_ns->type_name[i]); | |
| } | |
| } | |
| return new_gres_ns; | |
| } | |
| static int _foreach_node_state_dup(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_node = x, *new_gres; | |
| list_t *new_list = arg; | |
| void *gres_ns; | |
| if (!_find_context_by_id(gres_state_node->plugin_id)) { | |
| error("Could not find plugin id %u to dup node record", | |
| gres_state_node->plugin_id); | |
| return 0; | |
| } | |
| gres_ns = _node_state_dup(gres_state_node->gres_data); | |
| if (gres_ns) { | |
| new_gres = gres_create_state( | |
| gres_state_node, GRES_STATE_SRC_STATE_PTR, | |
| GRES_STATE_TYPE_NODE, gres_ns); | |
| /* | |
| * Because "gres/'shared'" follows "gres/gpu" (see gres_init) | |
| * the sharing gres will be in new list already. | |
| */ | |
| if (gres_id_shared(new_gres->config_flags)) { | |
| /* | |
| * gres_id_sharing currently only includes gpus so we | |
| * can just search for that. | |
| */ | |
| _set_alt_gres(new_gres, | |
| list_find_first(new_list, gres_find_id, | |
| &gpu_plugin_id)); | |
| } | |
| list_append(new_list, new_gres); | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Shared body of gres_node_state_list_dup()/gres_node_state_dup_one(). | |
| * IN gres_list - node gres state information | |
| * IN name - duplicate only the entry with this GRES name, or NULL for all | |
| * RET the duplicate list, or NULL if gres_list is NULL, no plugins are | |
| * loaded, or (when name is given) no entry matches it | |
| */ | |
| static list_t *_gres_node_state_dup_common(list_t *gres_list, char *name) | |
| { | |
| list_t *new_list = NULL; | |
| if (gres_list == NULL) | |
| return new_list; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| if (gres_context_cnt > 0) { | |
| if (name) { | |
| gres_state_t *gres_state_node = | |
| list_find_first(gres_list, | |
| _find_node_state_by_name, name); | |
| if (gres_state_node) { | |
| new_list = list_create(_gres_node_list_delete); | |
| (void) _foreach_node_state_dup(gres_state_node, | |
| new_list); | |
| } | |
| } else { | |
| new_list = list_create(_gres_node_list_delete); | |
| (void) list_for_each(gres_list, _foreach_node_state_dup, | |
| new_list); | |
| } | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return new_list; | |
| } | |
| /* | |
| * Duplicate a node gres status (used for will-run logic) | |
| * IN gres_list - node gres state information | |
| * RET a copy of gres_list or NULL on failure | |
| */ | |
| extern list_t *gres_node_state_list_dup(list_t *gres_list) | |
| { | |
| return _gres_node_state_dup_common(gres_list, NULL); | |
| } | |
| extern list_t *gres_node_state_dup_one(list_t *gres_list, char *name) | |
| { | |
| xassert(name && name[0]); | |
| return _gres_node_state_dup_common(gres_list, name); | |
| } | |
| static int _node_state_dealloc(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_node = x; | |
| int i; | |
| gres_node_state_t *gres_ns; | |
| gres_ns = (gres_node_state_t *) gres_state_node->gres_data; | |
| gres_ns->gres_cnt_alloc = 0; | |
| if (gres_ns->gres_bit_alloc) | |
| bit_clear_all(gres_ns->gres_bit_alloc); | |
| if (gres_ns->topo_cnt && !gres_ns->topo_gres_cnt_alloc) { | |
| error("gres_node_state_dealloc_all: gres/%s topo_cnt!=0 " | |
| "and topo_gres_cnt_alloc is NULL", | |
| gres_state_node->gres_name); | |
| } else if (gres_ns->topo_cnt) { | |
| for (i = 0; i < gres_ns->topo_cnt; i++) { | |
| gres_ns->topo_gres_cnt_alloc[i] = 0; | |
| } | |
| } else { | |
| /* | |
| * This array can be set at startup if a job has been allocated | |
| * specific GRES and the node has not registered with the | |
| * details needed to track individual GRES (rather than only | |
| * a GRES count). | |
| */ | |
| xfree(gres_ns->topo_gres_cnt_alloc); | |
| } | |
| for (i = 0; i < gres_ns->type_cnt; i++) { | |
| gres_ns->type_cnt_alloc[i] = 0; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Deallocate all resources on this node previous allocated to any jobs. | |
| * This function isused to synchronize state after slurmctld restarts or | |
| * is reconfigured. | |
| * IN gres_list - node gres state information | |
| */ | |
| extern void gres_node_state_dealloc_all(list_t *gres_list) | |
| { | |
| if (gres_list == NULL) | |
| return; | |
| xassert(gres_context_cnt >= 0); | |
| (void) list_for_each(gres_list, _node_state_dealloc, NULL); | |
| } | |
| static bool _is_shared_gres_full_alloc(gres_node_state_t *gres_ns, int topo_inx) | |
| { | |
| gres_node_state_t *alt_gres_ns; | |
| xassert(gres_ns); | |
| xassert(topo_inx < gres_ns->topo_cnt); | |
| if (!gres_ns->alt_gres || !gres_ns->topo_gres_bitmap || | |
| !gres_ns->topo_gres_bitmap[topo_inx]) | |
| return false; | |
| alt_gres_ns = gres_ns->alt_gres->gres_data; | |
| if (!alt_gres_ns || !alt_gres_ns->gres_bit_alloc) | |
| return false; | |
| return bit_overlap_any(gres_ns->topo_gres_bitmap[topo_inx], | |
| alt_gres_ns->gres_bit_alloc); | |
| } | |
| static void _xstrfmtcat_shared_gres_used(char **str, const char *sep, | |
| gres_node_state_t *gres_ns, | |
| int topo_inx) | |
| { | |
| uint64_t alloc = gres_ns->topo_gres_cnt_alloc[topo_inx]; | |
| uint64_t total = gres_ns->topo_gres_cnt_avail[topo_inx]; | |
| if (_shared_topo_bit_drained(gres_ns, topo_inx)) { | |
| if (alloc) | |
| xstrfmtcat(*str, "%sd%" PRIu64 "/%" PRIu64, sep, alloc, | |
| total); | |
| else | |
| xstrfmtcat(*str, "%sd/%" PRIu64, sep, total); | |
| } else if (!alloc && _is_shared_gres_full_alloc(gres_ns, topo_inx)) { | |
| xstrfmtcat(*str, "%s-/%" PRIu64, sep, total); | |
| } else { | |
| xstrfmtcat(*str, "%s%" PRIu64 "/%" PRIu64, sep, alloc, total); | |
| } | |
| } | |
| static char *_node_gres_used(gres_node_state_t *gres_ns, char *gres_name) | |
| { | |
| char *sep = ""; | |
| int i, j; | |
| xassert(gres_ns); | |
| if (!gres_ns->gres_cnt_avail) { | |
| return NULL; | |
| } else if ((gres_ns->topo_cnt != 0) && (gres_ns->no_consume == false)) { | |
| bitstr_t *topo_printed = bit_alloc(gres_ns->topo_cnt); | |
| xfree(gres_ns->gres_used); /* Free any cached value */ | |
| for (i = 0; i < gres_ns->topo_cnt; i++) { | |
| /* | |
| * For non-shared gres, we record which indices have | |
| * gres allocated. For shared gres, we record the count | |
| * of allocated gres at each index (may be >1, as | |
| * opposed to non-shared gres which is never >1) | |
| * | |
| * topo_gres_bitmap is used for non-shared gres, while | |
| * topo_gres_cnt_alloc_str is used for shared gres | |
| * (shard, mps). | |
| */ | |
| bitstr_t *topo_gres_bitmap = NULL; | |
| char *topo_gres_cnt_alloc_str = NULL; | |
| uint64_t gres_alloc_cnt = 0; | |
| char *gres_alloc_idx, tmp_str[64]; | |
| bool is_shared; | |
| if (bit_test(topo_printed, i)) | |
| continue; | |
| bit_set(topo_printed, i); | |
| is_shared = gres_is_shared_name(gres_name); | |
| if (is_shared) { | |
| _xstrfmtcat_shared_gres_used( | |
| &topo_gres_cnt_alloc_str, "", gres_ns, | |
| i); | |
| gres_alloc_cnt += | |
| gres_ns->topo_gres_cnt_alloc[i]; | |
| } else if (gres_ns->topo_gres_bitmap[i]) { | |
| topo_gres_bitmap = | |
| bit_copy(gres_ns-> | |
| topo_gres_bitmap[i]); | |
| } | |
| for (j = i + 1; j < gres_ns->topo_cnt; j++) { | |
| if (bit_test(topo_printed, j)) | |
| continue; | |
| if (gres_ns->topo_type_id[i] != | |
| gres_ns->topo_type_id[j]) | |
| continue; | |
| bit_set(topo_printed, j); | |
| if (is_shared) { | |
| _xstrfmtcat_shared_gres_used( | |
| &topo_gres_cnt_alloc_str, ",", | |
| gres_ns, j); | |
| gres_alloc_cnt += | |
| gres_ns->topo_gres_cnt_alloc[j]; | |
| } else if (gres_ns->topo_gres_bitmap[j]) { | |
| if (!topo_gres_bitmap) { | |
| topo_gres_bitmap = | |
| bit_copy(gres_ns-> | |
| topo_gres_bitmap[j]); | |
| } else if (bit_size(topo_gres_bitmap) == | |
| bit_size(gres_ns-> | |
| topo_gres_bitmap[j])){ | |
| bit_or(topo_gres_bitmap, | |
| gres_ns-> | |
| topo_gres_bitmap[j]); | |
| } | |
| } | |
| } | |
| if (!is_shared && gres_ns->gres_bit_alloc && | |
| topo_gres_bitmap && | |
| (bit_size(topo_gres_bitmap) == | |
| bit_size(gres_ns->gres_bit_alloc))) { | |
| bit_and(topo_gres_bitmap, | |
| gres_ns->gres_bit_alloc); | |
| gres_alloc_cnt = bit_set_count(topo_gres_bitmap); | |
| } | |
| if (is_shared) { | |
| gres_alloc_idx = topo_gres_cnt_alloc_str; | |
| } else if (gres_alloc_cnt > 0) { | |
| bit_fmt(tmp_str, sizeof(tmp_str), | |
| topo_gres_bitmap); | |
| gres_alloc_idx = tmp_str; | |
| } else { | |
| gres_alloc_idx = "N/A"; | |
| } | |
| xstrfmtcat(gres_ns->gres_used, | |
| "%s%s%s%s:%" PRIu64 "(%s%s)", sep, gres_name, | |
| gres_ns->topo_type_name[i] ? ":" : "", | |
| gres_ns->topo_type_name[i] ? | |
| gres_ns->topo_type_name[i] : | |
| "", | |
| gres_alloc_cnt, | |
| is_shared ? "" : "IDX:", gres_alloc_idx); | |
| sep = ","; | |
| FREE_NULL_BITMAP(topo_gres_bitmap); | |
| xfree(topo_gres_cnt_alloc_str); | |
| } | |
| FREE_NULL_BITMAP(topo_printed); | |
| } else if (gres_ns->gres_used) { | |
| ; /* Used cached value */ | |
| } else if (gres_ns->type_cnt == 0) { | |
| if (gres_ns->no_consume) { | |
| xstrfmtcat(gres_ns->gres_used, "%s:0", gres_name); | |
| } else { | |
| xstrfmtcat(gres_ns->gres_used, "%s:%"PRIu64, | |
| gres_name, gres_ns->gres_cnt_alloc); | |
| } | |
| } else { | |
| for (i = 0; i < gres_ns->type_cnt; i++) { | |
| if (gres_ns->no_consume) { | |
| xstrfmtcat(gres_ns->gres_used, | |
| "%s%s:%s:0", sep, gres_name, | |
| gres_ns->type_name[i]); | |
| } else { | |
| xstrfmtcat(gres_ns->gres_used, | |
| "%s%s:%s:%"PRIu64, sep, gres_name, | |
| gres_ns->type_name[i], | |
| gres_ns->type_cnt_alloc[i]); | |
| } | |
| sep = ","; | |
| } | |
| } | |
| return gres_ns->gres_used; | |
| } | |
| static int _foreach_node_state_log(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_node = x; | |
| gres_node_state_t *gres_ns = gres_state_node->gres_data; | |
| char *gres_name = gres_state_node->gres_name; | |
| char *node_name = arg; | |
| int i, j; | |
| char *buf = NULL, *sep, tmp_str[128]; | |
| xassert(gres_ns); | |
| info("gres/%s: state for %s", gres_name, node_name); | |
| if (gres_ns->gres_cnt_found == NO_VAL64) { | |
| snprintf(tmp_str, sizeof(tmp_str), "TBD"); | |
| } else { | |
| snprintf(tmp_str, sizeof(tmp_str), "%"PRIu64, | |
| gres_ns->gres_cnt_found); | |
| } | |
| if (gres_ns->no_consume) { | |
| info(" gres_cnt found:%s configured:%"PRIu64" " | |
| "avail:%"PRIu64" no_consume", | |
| tmp_str, gres_ns->gres_cnt_config, | |
| gres_ns->gres_cnt_avail); | |
| } else { | |
| info(" gres_cnt found:%s configured:%"PRIu64" " | |
| "avail:%"PRIu64" alloc:%"PRIu64"", | |
| tmp_str, gres_ns->gres_cnt_config, | |
| gres_ns->gres_cnt_avail, | |
| gres_ns->gres_cnt_alloc); | |
| } | |
| if (gres_ns->gres_bit_alloc) { | |
| bit_fmt(tmp_str, sizeof(tmp_str),gres_ns->gres_bit_alloc); | |
| info(" gres_bit_alloc:%s of %d", | |
| tmp_str, (int) bit_size(gres_ns->gres_bit_alloc)); | |
| } else { | |
| info(" gres_bit_alloc:NULL"); | |
| } | |
| info(" gres_used:%s", gres_ns->gres_used); | |
| if (gres_ns->links_cnt && gres_ns->link_len) { | |
| for (i = 0; i < gres_ns->link_len; i++) { | |
| sep = ""; | |
| for (j = 0; j < gres_ns->link_len; j++) { | |
| xstrfmtcat(buf, "%s%d", sep, | |
| gres_ns->links_cnt[i][j]); | |
| sep = ", "; | |
| } | |
| info(" links[%d]:%s", i, buf); | |
| xfree(buf); | |
| } | |
| } | |
| for (int d = 0; d < gres_ns->chunk_domain_cnt; d++) { | |
| gres_chunk_domain_t *dom = &gres_ns->chunk_domains[d]; | |
| char *pos = NULL; | |
| sep = ""; | |
| for (i = 0; i < dom->sizes_cnt; i++) { | |
| xstrfmtcatat(buf, &pos, "%s%u", sep, dom->sizes[i]); | |
| sep = ","; | |
| } | |
| xstrcatat(buf, &pos, " order:"); | |
| sep = ""; | |
| for (i = 0; i < dom->order_len; i++) { | |
| xstrfmtcatat(buf, &pos, "%s%d", sep, dom->order[i]); | |
| sep = ","; | |
| } | |
| info(" chunks[%d]:type_id:%u sizes:%s", d, dom->type_id, buf); | |
| xfree(buf); | |
| } | |
| for (i = 0; i < gres_ns->topo_cnt; i++) { | |
| info(" topo[%d]:%s(%u)", i, gres_ns->topo_type_name[i], | |
| gres_ns->topo_type_id[i]); | |
| if (gres_ns->topo_core_bitmap[i]) { | |
| bit_fmt(tmp_str, sizeof(tmp_str), | |
| gres_ns->topo_core_bitmap[i]); | |
| info(" topo_core_bitmap[%d]:%s of %d", i, tmp_str, | |
| (int)bit_size(gres_ns->topo_core_bitmap[i])); | |
| } else | |
| info(" topo_core_bitmap[%d]:NULL", i); | |
| if (gres_ns->topo_res_core_bitmap[i]) { | |
| bit_fmt(tmp_str, sizeof(tmp_str), | |
| gres_ns->topo_res_core_bitmap[i]); | |
| info(" topo_res_core_bitmap[%d]:%s of %d", i, tmp_str, | |
| (int)bit_size(gres_ns->topo_res_core_bitmap[i])); | |
| } | |
| if (gres_ns->topo_gres_bitmap[i]) { | |
| bit_fmt(tmp_str, sizeof(tmp_str), | |
| gres_ns->topo_gres_bitmap[i]); | |
| info(" topo_gres_bitmap[%d]:%s of %d", i, tmp_str, | |
| (int)bit_size(gres_ns->topo_gres_bitmap[i])); | |
| } else | |
| info(" topo_gres_bitmap[%d]:NULL", i); | |
| info(" topo_gres_cnt_alloc[%d]:%"PRIu64"", i, | |
| gres_ns->topo_gres_cnt_alloc[i]); | |
| info(" topo_gres_cnt_avail[%d]:%"PRIu64"", i, | |
| gres_ns->topo_gres_cnt_avail[i]); | |
| } | |
| for (i = 0; i < gres_ns->type_cnt; i++) { | |
| info(" type[%d]:%s(%u)", i, gres_ns->type_name[i], | |
| gres_ns->type_id[i]); | |
| info(" type_cnt_alloc[%d]:%"PRIu64, i, | |
| gres_ns->type_cnt_alloc[i]); | |
| info(" type_cnt_avail[%d]:%"PRIu64, i, | |
| gres_ns->type_cnt_avail[i]); | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Log a node's current gres state | |
| * IN gres_list - generated by gres_node_config_validate() | |
| * IN node_name - name of the node for which the gres information applies | |
| */ | |
| extern void gres_node_state_log(list_t *gres_list, char *node_name) | |
| { | |
| if (!(slurm_conf.debug_flags & DEBUG_FLAG_GRES) || !gres_list) | |
| return; | |
| xassert(gres_context_cnt >= 0); | |
| (void) list_for_each(gres_list, _foreach_node_state_log, node_name); | |
| } | |
| /* Find node_state_t gres record with any allocated gres (key is unused) */ | |
| static int _find_node_state_with_alloc_gres(void *x, void *key) | |
| { | |
| gres_state_t *gres_state_node = (gres_state_t *) x; | |
| if (((gres_node_state_t *) gres_state_node->gres_data)->gres_cnt_alloc) | |
| return 1; | |
| else | |
| return 0; | |
| } | |
| extern bool gres_node_state_list_has_alloc_gres(list_t *gres_list) | |
| { | |
| if (!gres_list) | |
| return false; | |
| return list_find_first(gres_list, | |
| _find_node_state_with_alloc_gres, NULL); | |
| } | |
| /* | |
| * Count the devices set in both drain and mask, leaving out those set in | |
| * alloc when alloc is given. The bitmaps can differ in width: a topology | |
| * bitmap follows what slurmd last reported, drain and alloc the configured | |
| * count. Only the drained span is walked and no bitmap is copied, since this | |
| * runs per topology record while scheduling. | |
| */ | |
| static uint64_t _drained_in_mask(bitstr_t *drain, bitstr_t *alloc, | |
| bitstr_t *mask) | |
| { | |
| uint64_t cnt = 0; | |
| int first, last; | |
| if (!drain || !mask || ((first = bit_ffs(drain)) < 0)) | |
| return 0; | |
| last = MIN(bit_fls(drain), bit_size(mask) - 1); | |
| for (int i = first; i <= last; i++) { | |
| if (!bit_test(drain, i) || !bit_test(mask, i)) | |
| continue; | |
| if (alloc && (i < bit_size(alloc)) && bit_test(alloc, i)) | |
| continue; | |
| cnt++; | |
| } | |
| return cnt; | |
| } | |
| static bool _shared_topo_bit_drained(gres_node_state_t *gres_ns, int topo_inx) | |
| { | |
| gres_node_state_t *alt_gres_ns; | |
| bitstr_t *gres_bit_drain; | |
| xassert(gres_ns); | |
| xassert(topo_inx < gres_ns->topo_cnt); | |
| if (!gres_ns->alt_gres || !gres_ns->alt_gres->gres_data) | |
| return false; | |
| if (!gres_ns->topo_gres_bitmap || !gres_ns->topo_gres_bitmap[topo_inx]) | |
| return false; | |
| alt_gres_ns = gres_ns->alt_gres->gres_data; | |
| gres_bit_drain = alt_gres_ns->gres_bit_drain; | |
| if (!gres_bit_drain) | |
| return false; | |
| return _drained_in_mask(gres_bit_drain, NULL, | |
| gres_ns->topo_gres_bitmap[topo_inx]); | |
| } | |
| extern uint64_t gres_node_cnt_drain_unalloc(gres_node_state_t *gres_ns) | |
| { | |
| uint64_t overlap; | |
| if (!gres_ns || !gres_ns->gres_cnt_drain) | |
| return 0; | |
| /* Only a device that is both drained and allocated is removed. */ | |
| overlap = 0; | |
| if (gres_ns->gres_bit_drain && gres_ns->gres_bit_alloc) | |
| overlap = bit_overlap(gres_ns->gres_bit_drain, | |
| gres_ns->gres_bit_alloc); | |
| if (overlap >= gres_ns->gres_cnt_drain) | |
| return 0; | |
| return gres_ns->gres_cnt_drain - overlap; | |
| } | |
| extern uint64_t gres_node_topo_cnt_avail(gres_node_state_t *gres_ns, | |
| int topo_inx, bool use_total_gres) | |
| { | |
| uint64_t cnt; | |
| xassert(gres_ns); | |
| xassert(topo_inx < gres_ns->topo_cnt); | |
| cnt = gres_ns->topo_gres_cnt_avail[topo_inx]; | |
| if (use_total_gres || !gres_ns->topo_gres_bitmap || | |
| !gres_ns->topo_gres_bitmap[topo_inx]) | |
| return cnt; | |
| /* Shared gres must check if sharing device is drained */ | |
| if (gres_ns->alt_gres && | |
| gres_id_sharing(gres_ns->alt_gres->plugin_id)) { | |
| if (_shared_topo_bit_drained(gres_ns, topo_inx)) | |
| return 0; | |
| return cnt; | |
| } | |
| /* The bitmap is kept after its last drain is resumed */ | |
| if (!gres_ns->gres_bit_drain || !gres_ns->gres_cnt_drain) | |
| return cnt; | |
| /* | |
| * Allocated drained devices are already in topo_gres_cnt_alloc. | |
| * Leave those bits out of the overlap so they are not subtracted twice. | |
| */ | |
| cnt -= MIN(cnt, _drained_in_mask(gres_ns->gres_bit_drain, | |
| gres_ns->gres_bit_alloc, | |
| gres_ns->topo_gres_bitmap[topo_inx])); | |
| return cnt; | |
| } | |
| static int _find_node_state_with_drain_gres(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_node = (gres_state_t *) x; | |
| gres_node_state_t *gres_ns = gres_state_node->gres_data; | |
| if (gres_ns && gres_ns->gres_cnt_drain) | |
| return 1; | |
| return 0; | |
| } | |
| extern bool gres_node_state_list_has_drain_gres(list_t *gres_list) | |
| { | |
| if (!gres_list) | |
| return false; | |
| return list_find_first(gres_list, _find_node_state_with_drain_gres, | |
| NULL); | |
| } | |
| static int _foreach_resume_drain(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_node = x; | |
| gres_node_state_t *gres_ns = gres_state_node->gres_data; | |
| bool *did_change = arg; | |
| if (!gres_ns) | |
| return 0; | |
| if (!gres_ns->gres_cnt_drain && !_gres_drain_uuid_cnt(gres_ns) && | |
| !gres_ns->drain_groups && !gres_ns->type_drain && | |
| !gres_ns->untyped_drain.count) | |
| return 0; | |
| _gres_drain_clear(gres_ns); | |
| *did_change = true; | |
| return 0; | |
| } | |
| extern bool gres_node_state_list_resume_drain(list_t *gres_list) | |
| { | |
| bool did_change = false; | |
| if (!gres_list) | |
| return false; | |
| (void) list_for_each(gres_list, _foreach_resume_drain, &did_change); | |
| return did_change; | |
| } | |
| extern uint64_t gres_node_type_drain_cnt(gres_node_state_t *gres_ns, | |
| uint32_t type_id, bool exclude_alloc) | |
| { | |
| /* | |
| * An allocated drained device is already counted in the allocation, | |
| * so leave it out when asked to. | |
| */ | |
| bitstr_t *alloc = exclude_alloc ? gres_ns->gres_bit_alloc : NULL; | |
| uint64_t count = 0; | |
| if (!gres_ns->gres_bit_drain) { | |
| for (int type = 0; type < gres_ns->type_cnt; type++) { | |
| if (gres_ns->type_id[type] != type_id) | |
| continue; | |
| count = _gres_cnt_pool_count(gres_ns, type); | |
| break; | |
| } | |
| return count; | |
| } | |
| if (!gres_ns->gres_bit_drain || !gres_ns->topo_cnt) | |
| return 0; | |
| for (int type = 0; type < gres_ns->topo_cnt; type++) { | |
| bitstr_t *topo_bits; | |
| if (gres_ns->topo_type_id[type] != type_id) | |
| continue; | |
| if (!(topo_bits = gres_ns->topo_gres_bitmap[type])) | |
| continue; | |
| count += _drained_in_mask(gres_ns->gres_bit_drain, alloc, | |
| topo_bits); | |
| } | |
| return count; | |
| } | |
| static int _foreach_drain_over_limit(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_node = x; | |
| foreach_drain_over_limit_t *args = arg; | |
| gres_node_state_t *gres_ns = gres_state_node->gres_data; | |
| uint64_t max_drained, type_drain; | |
| char *tres_name, *typed_name; | |
| if (!gres_ns || !gres_ns->gres_cnt_drain) | |
| return 0; | |
| tres_name = xstrdup_printf("gres/%s", gres_state_node->gres_name); | |
| max_drained = | |
| drained_tres_max_limit(args->max_drained_tres, tres_name, true); | |
| xfree(tres_name); | |
| if (drained_tres_over_limit(args->reason, gres_state_node->gres_name, | |
| gres_ns->gres_cnt_drain, max_drained)) | |
| args->over_limit = true; | |
| for (int i = 0; i < gres_ns->type_cnt; i++) { | |
| type_drain = | |
| gres_node_type_drain_cnt(gres_ns, gres_ns->type_id[i], | |
| false); | |
| if (!type_drain) | |
| continue; | |
| typed_name = xstrdup_printf("%s:%s", gres_state_node->gres_name, | |
| gres_ns->type_name[i]); | |
| tres_name = xstrdup_printf("gres/%s", typed_name); | |
| max_drained = drained_tres_max_limit(args->max_drained_tres, | |
| tres_name, true); | |
| xfree(tres_name); | |
| if (drained_tres_over_limit(args->reason, typed_name, | |
| type_drain, max_drained)) | |
| args->over_limit = true; | |
| xfree(typed_name); | |
| } | |
| return 0; | |
| } | |
| extern bool gres_node_state_list_drain_over_limit(list_t *gres_list, | |
| char *max_drained_tres, | |
| char **reason) | |
| { | |
| foreach_drain_over_limit_t args = { | |
| .max_drained_tres = max_drained_tres, | |
| .reason = reason, | |
| }; | |
| if (!gres_list) | |
| return false; | |
| (void) list_for_each(gres_list, _foreach_drain_over_limit, &args); | |
| return args.over_limit; | |
| } | |
| static int _find_node_state_by_name(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_node = (gres_state_t *) x; | |
| char *name = arg; | |
| if (!xstrcasecmp(gres_state_node->gres_name, name)) | |
| return 1; | |
| return 0; | |
| } | |
| static bool _drain_overlaps_alloc(gres_node_state_t *gres_ns, int index) | |
| { | |
| if (!gres_ns->gres_bit_alloc) | |
| return false; | |
| if ((index >= 0) && (index < bit_size(gres_ns->gres_bit_alloc))) | |
| return bit_test(gres_ns->gres_bit_alloc, index); | |
| return false; | |
| } | |
| static int _gres_type_order_cmp(const void *x, const void *y) | |
| { | |
| const gres_type_order_t *a = x; | |
| const gres_type_order_t *b = y; | |
| return xstrcasecmp(a->name, b->name); | |
| } | |
| static gres_type_order_t *_gres_type_order(gres_node_state_t *gres_ns) | |
| { | |
| gres_type_order_t *order; | |
| if (!gres_ns->type_cnt) | |
| return NULL; | |
| order = xcalloc(gres_ns->type_cnt, sizeof(*order)); | |
| for (int i = 0; i < gres_ns->type_cnt; i++) { | |
| order[i].index = i; | |
| order[i].name = gres_ns->type_name[i]; | |
| } | |
| qsort(order, gres_ns->type_cnt, sizeof(*order), _gres_type_order_cmp); | |
| return order; | |
| } | |
| static uint64_t _gres_cnt_drain_add_type(gres_node_state_t *gres_ns, int type, | |
| uint64_t count) | |
| { | |
| uint64_t drained = _gres_cnt_pool_count(gres_ns, type); | |
| uint64_t cap = (type >= 0) ? gres_ns->type_cnt_avail[type] : | |
| gres_ns->gres_cnt_config; | |
| uint64_t remaining = cap - MIN(cap, drained); | |
| count = MIN(count, remaining); | |
| if (!count) | |
| return 0; | |
| /* The caller records the reason once this pool exists. */ | |
| _gres_cnt_pool_set(gres_ns, type, drained + count, NULL, false); | |
| gres_ns->gres_cnt_drain += count; | |
| return count; | |
| } | |
| static uint64_t _gres_cnt_drain_resume_type(gres_node_state_t *gres_ns, | |
| int type, uint64_t count) | |
| { | |
| uint64_t drained = _gres_cnt_pool_count(gres_ns, type); | |
| uint64_t resumed = MIN(count, drained); | |
| /* A partial resume keeps the pool's reason. Zero deletes it. */ | |
| _gres_cnt_pool_set(gres_ns, type, drained - resumed, NULL, false); | |
| gres_ns->gres_cnt_drain -= resumed; | |
| return resumed; | |
| } | |
| static int _count_only_drain_apply(gres_node_state_t *gres_ns, char *type, | |
| uint64_t count, bool want_all, char *reason, | |
| bool drain) | |
| { | |
| gres_type_order_t *order = NULL; | |
| uint64_t remaining = want_all ? INFINITE64 : count; | |
| int type_index = -1; | |
| _gres_drain_cnt_clamp(gres_ns); | |
| if (type) { | |
| type_index = _find_gres_type_name(gres_ns, type); | |
| if (type_index < 0) | |
| return ESLURM_INVALID_GRES_TYPE; | |
| } | |
| if (drain && gres_ns->gres_cnt_alloc) | |
| warning("Draining count-only GRES while jobs are using it. Existing allocations are unaffected; no new allocations will use it."); | |
| if (type) { | |
| if (drain) | |
| (void) _gres_cnt_drain_add_type(gres_ns, type_index, | |
| remaining); | |
| else | |
| (void) _gres_cnt_drain_resume_type(gres_ns, type_index, | |
| remaining); | |
| } else if (!gres_ns->type_cnt) { | |
| if (drain) | |
| (void) _gres_cnt_drain_add_type(gres_ns, -1, remaining); | |
| else | |
| (void) _gres_cnt_drain_resume_type(gres_ns, -1, | |
| remaining); | |
| } else { | |
| order = _gres_type_order(gres_ns); | |
| for (int i = 0; (i < gres_ns->type_cnt) && remaining; i++) { | |
| uint64_t changed; | |
| if (drain) | |
| changed = | |
| _gres_cnt_drain_add_type(gres_ns, | |
| order[i].index, | |
| remaining); | |
| else | |
| changed = _gres_cnt_drain_resume_type( | |
| gres_ns, order[i].index, remaining); | |
| /* | |
| * An untyped request spreads over several types, so | |
| * record the reason against each type it actually | |
| * reached rather than leaving them on a stale one. | |
| */ | |
| if (drain && changed) | |
| _gres_type_drain_reason_set(gres_ns, | |
| order[i].index, | |
| reason); | |
| if (!want_all) | |
| remaining -= MIN(remaining, changed); | |
| } | |
| xfree(order); | |
| } | |
| if (drain && (type || !gres_ns->type_cnt)) | |
| _gres_type_drain_reason_set(gres_ns, type_index, reason); | |
| _gres_drain_cnt_clamp(gres_ns); | |
| return SLURM_SUCCESS; | |
| } | |
| /* | |
| * Build a bitmap of the device indexes that belong to GRES type "type". If | |
| * "type" is NULL or empty, all configured devices are selected. Returns NULL | |
| * and sets *rc to ESLURM_INVALID_GRES_TYPE if the type is not present on the | |
| * node. | |
| */ | |
| static bitstr_t *_gres_type_device_mask(gres_node_state_t *gres_ns, char *type, | |
| int dev_bits, int *rc) | |
| { | |
| bitstr_t *mask = bit_alloc(dev_bits); | |
| uint32_t type_id; | |
| bool found = false; | |
| if (!type || !type[0]) { | |
| bit_set_all(mask); | |
| return mask; | |
| } | |
| type_id = gres_build_id(type); | |
| for (int i = 0; i < gres_ns->topo_cnt; i++) { | |
| int topo_bits; | |
| if (gres_ns->topo_type_id[i] != type_id) | |
| continue; | |
| found = true; | |
| if (!gres_ns->topo_gres_bitmap[i]) | |
| continue; | |
| topo_bits = bit_size(gres_ns->topo_gres_bitmap[i]); | |
| for (int b = 0; (b < topo_bits) && (b < dev_bits); b++) | |
| if (bit_test(gres_ns->topo_gres_bitmap[i], b)) | |
| bit_set(mask, b); | |
| } | |
| if (!found) { | |
| FREE_NULL_BITMAP(mask); | |
| *rc = ESLURM_INVALID_GRES_TYPE; | |
| return NULL; | |
| } | |
| return mask; | |
| } | |
| /* | |
| * Apply one count-only GRES drain request in the caller's action. | |
| * | |
| * UPDATE_SET is expressed as a full resume followed by a drain of the requested | |
| * amount. That keeps the arithmetic identical to the add and remove paths and, | |
| * for an untyped request against a multi-type GRES, gives a well defined answer | |
| * to "make the total exactly N", which otherwise has no single distribution | |
| * across types. | |
| */ | |
| static int _update_count_only_gres_drain(gres_node_state_t *gres_ns, char *type, | |
| uint64_t count, bool want_all, | |
| char *reason, update_mode_t action) | |
| { | |
| int rc; | |
| switch (action) { | |
| case UPDATE_ADD: | |
| return _count_only_drain_apply(gres_ns, type, count, want_all, | |
| reason, true); | |
| case UPDATE_REMOVE: | |
| return _count_only_drain_apply(gres_ns, type, count, want_all, | |
| reason, false); | |
| case UPDATE_SET: | |
| rc = _count_only_drain_apply(gres_ns, type, 0, true, NULL, | |
| false); | |
| if (rc) | |
| return rc; | |
| if (!want_all && !count) | |
| return SLURM_SUCCESS; | |
| return _count_only_drain_apply(gres_ns, type, count, want_all, | |
| reason, true); | |
| } | |
| return ESLURM_INVALID_TRES; | |
| } | |
| /* | |
| * Apply a resolved target bitmap to a device-tracked GRES. | |
| * The selector is already resolved to indexes. A slot that holds a UUID | |
| * records the drain or resume against that UUID as well as the index. | |
| * An empty slot has no UUID, so only the index changes. | |
| */ | |
| static void _apply_gres_drain_bits(gres_node_state_t *gres_ns, char *gres_name, | |
| bitstr_t *target, char *reason, bool drain) | |
| { | |
| if (!target || !bit_set_count(target)) | |
| return; | |
| if (drain) { | |
| for (int i = 0; i < bit_size(target); i++) { | |
| if (bit_test(target, i) && | |
| _drain_overlaps_alloc(gres_ns, i)) { | |
| warning("Draining currently allocated GRES %s on node. Existing allocations are unaffected; no new allocations will use it.", | |
| gres_name); | |
| break; | |
| } | |
| } | |
| } | |
| for (int i = 0; i < bit_size(target); i++) { | |
| char *uuid; | |
| if (!bit_test(target, i)) | |
| continue; | |
| uuid = (i < gres_ns->dev_uuid_cnt) ? | |
| gres_ns->dev_uuid[i] : | |
| NULL; | |
| if (drain) { | |
| if (uuid) | |
| drained_tres_group_id_set( | |
| &gres_ns->drain_groups, uuid, reason); | |
| _gres_drain_set_bit(gres_ns, i, reason); | |
| } else { | |
| if (uuid) | |
| drained_tres_group_id_clear( | |
| &gres_ns->drain_groups, uuid); | |
| _gres_drain_clear_bit(gres_ns, i); | |
| } | |
| } | |
| } | |
| /* | |
| * Width of the device index space a drain request may select from. | |
| * | |
| * A UUID map includes every saved slot, empty ones included, so Index | |
| * and ALL can name an absent device. A GRES with no map uses the larger | |
| * of the configured count and the devices currently available. | |
| * | |
| * Never narrower than the existing drain bitmap: narrowing it drops the | |
| * reasons of the bits it cuts, which is registration's job and not a | |
| * selector's. | |
| */ | |
| static uint64_t _gres_drain_sel_bits(gres_node_state_t *gres_ns) | |
| { | |
| uint64_t bits; | |
| if (gres_ns->dev_uuid_cnt) { | |
| bits = gres_ns->dev_uuid_cnt; | |
| } else { | |
| bits = MAX(gres_ns->gres_cnt_config, gres_ns->gres_cnt_avail); | |
| if (gres_ns->topo_cnt) | |
| bits = MAX(bits, gres_ns->topo_cnt); | |
| } | |
| if (gres_ns->gres_bit_drain) | |
| bits = MAX(bits, bit_size(gres_ns->gres_bit_drain)); | |
| return bits; | |
| } | |
| /* | |
| * Resolve req's selector into a bitmap of device indexes, plus the mask of | |
| * devices belonging to req's type that the selection must fall within. | |
| * | |
| * Mutates no drain state, so a caller can validate a request before applying | |
| * any part of it. Both bitmaps are returned to the caller to free, sized to | |
| * _gres_drain_sel_bits(); a caller that goes on to apply the request has to | |
| * widen gres_bit_drain to match first. | |
| */ | |
| static int _gres_resolve_target(gres_node_state_t *gres_ns, | |
| gres_drain_req_t *req, bitstr_t **target_out, | |
| bitstr_t **mask_out) | |
| { | |
| bitstr_t *target = NULL, *type_mask = NULL; | |
| uint64_t bits = _gres_drain_sel_bits(gres_ns); | |
| int rc = SLURM_SUCCESS; | |
| /* No device index space to select from: nothing can be named. */ | |
| if (!bits) | |
| return ESLURM_INVALID_GRES; | |
| type_mask = _gres_type_device_mask(gres_ns, req->type, bits, &rc); | |
| if (!type_mask) | |
| return rc; | |
| target = bit_alloc(bits); | |
| switch (req->sel) { | |
| case GRES_DRAIN_SEL_ALL: | |
| bit_or(target, type_mask); | |
| break; | |
| case GRES_DRAIN_SEL_INDEX: | |
| if (bit_unfmt(target, req->sel_str)) | |
| rc = ESLURM_INVALID_GRES; | |
| else if (!bit_super_set(target, type_mask)) | |
| rc = ESLURM_INVALID_GRES_TYPE; | |
| break; | |
| case GRES_DRAIN_SEL_UUID: | |
| { | |
| char *dup = xstrdup(req->sel_str), *save = NULL, *tok; | |
| for (tok = strtok_r(dup, ",", &save); tok; | |
| tok = strtok_r(NULL, ",", &save)) { | |
| int idx = _gres_uuid_to_index(gres_ns, tok, &rc); | |
| if (idx < 0) | |
| break; | |
| if (idx >= bit_size(target)) { | |
| rc = ESLURM_INVALID_GRES; | |
| break; | |
| } | |
| bit_set(target, idx); | |
| } | |
| xfree(dup); | |
| if (!rc && !bit_super_set(target, type_mask)) | |
| rc = ESLURM_INVALID_GRES_TYPE; | |
| break; | |
| } | |
| case GRES_DRAIN_SEL_COUNT: | |
| /* A bare count cannot name devices. */ | |
| rc = ESLURM_INVALID_GRES; | |
| break; | |
| default: | |
| rc = ESLURM_INVALID_GRES; | |
| break; | |
| } | |
| /* | |
| * A request may name devices by UUID and by index at once, since | |
| * "scontrol show drainedtres" reports both for a device and a reported | |
| * line is meant to be usable as-is. The UUID picks the device; the index | |
| * has to be that device's index. Disagreement means the UUID is not at | |
| * that index, so the request is refused rather than quietly acting on | |
| * whichever selector happened to win. | |
| */ | |
| if (!rc && req->verify_index) { | |
| bitstr_t *check = bit_alloc(bit_size(target)); | |
| if (bit_unfmt(check, req->verify_index)) | |
| rc = ESLURM_INVALID_GRES; | |
| else if (!bit_equal(check, target)) | |
| rc = ESLURM_GRES_SELECTOR_MISMATCH; | |
| FREE_NULL_BITMAP(check); | |
| } | |
| if (rc) { | |
| FREE_NULL_BITMAP(target); | |
| FREE_NULL_BITMAP(type_mask); | |
| return rc; | |
| } | |
| *target_out = target; | |
| *mask_out = type_mask; | |
| return SLURM_SUCCESS; | |
| } | |
| /* True when target names a device that is not on the node. */ | |
| static bool _target_names_absent(gres_node_state_t *gres_ns, bitstr_t *target) | |
| { | |
| if (!target) | |
| return false; | |
| for (int i = 0; i < bit_size(target); i++) { | |
| if (bit_test(target, i) && _gres_slot_absent(gres_ns, i)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| /* Clear the bits of target that name a device not on the node. */ | |
| static void _drop_absent_bits(gres_node_state_t *gres_ns, bitstr_t *target) | |
| { | |
| if (!target) | |
| return; | |
| for (int i = 0; i < bit_size(target); i++) { | |
| if (bit_test(target, i) && _gres_slot_absent(gres_ns, i)) | |
| bit_clear(target, i); | |
| } | |
| } | |
| /* | |
| * Name each absent device in bits that is still drained, so a resume that | |
| * has to leave it can tell the caller. Items are appended to *note. | |
| */ | |
| static void _note_absent_bits(gres_state_t *gres_state, bitstr_t *bits, | |
| const char *node_name, char **note) | |
| { | |
| gres_node_state_t *gres_ns; | |
| int n; | |
| if (!note || !bits || !gres_state || !gres_state->gres_name) | |
| return; | |
| gres_ns = gres_state->gres_data; | |
| if (!gres_ns || !gres_ns->gres_bit_drain) | |
| return; | |
| n = bit_size(bits); | |
| for (int i = 0; i < n; i++) { | |
| if (!bit_test(bits, i) || !_gres_slot_absent(gres_ns, i)) | |
| continue; | |
| if ((i >= bit_size(gres_ns->gres_bit_drain)) || | |
| !bit_test(gres_ns->gres_bit_drain, i)) | |
| continue; | |
| xstrfmtcat(*note, "%sgres/%s index %d", *note ? ", " : "", | |
| gres_state->gres_name, i); | |
| if (node_name && node_name[0]) | |
| xstrfmtcat(*note, " on %s", node_name); | |
| } | |
| } | |
| static int _foreach_note_absent_drain(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state = x; | |
| foreach_note_absent_t *args = arg; | |
| gres_node_state_t *gres_ns = gres_state->gres_data; | |
| if (gres_ns) | |
| _note_absent_bits(gres_state, gres_ns->gres_bit_drain, | |
| args->node_name, args->note); | |
| return 0; | |
| } | |
| extern void gres_node_note_absent_drain(list_t *gres_list, | |
| const char *node_name, char **note) | |
| { | |
| foreach_note_absent_t args = { | |
| .node_name = node_name, | |
| .note = note, | |
| }; | |
| if (!gres_list || !note) | |
| return; | |
| (void) list_for_each_ro(gres_list, _foreach_note_absent_drain, &args); | |
| } | |
| /* | |
| * Apply one resolved drain request to a device-tracked GRES. | |
| * | |
| * UPDATE_SET makes the drained set of this GRES (restricted to req's type, when | |
| * one is given) exactly the resolved target: devices in the target that are not | |
| * drained are drained, and drained devices of the type that are not in the | |
| * target are resumed. Devices of another type are left alone, so a replace is | |
| * scoped to what the request named rather than to the whole node. | |
| */ | |
| static int _update_device_gres_drain(gres_state_t *gres_state_node, | |
| gres_drain_req_t *req) | |
| { | |
| gres_node_state_t *gres_ns = gres_state_node->gres_data; | |
| bitstr_t *target = NULL, *type_mask = NULL; | |
| bitstr_t *to_drain = NULL, *to_resume = NULL; | |
| int rc; | |
| rc = _gres_resolve_target(gres_ns, req, &target, &type_mask); | |
| if (rc) | |
| return rc; | |
| /* | |
| * An empty slot stays drained until a device fills it. Naming that | |
| * index is an error. Index=ALL leaves it, resumes the rest, and | |
| * names what it left for the caller. | |
| */ | |
| if ((req->action == UPDATE_REMOVE) && | |
| _target_names_absent(gres_ns, target)) { | |
| if (req->sel != GRES_DRAIN_SEL_ALL) { | |
| FREE_NULL_BITMAP(target); | |
| FREE_NULL_BITMAP(type_mask); | |
| return ESLURM_GRES_ABSENT_RESUME; | |
| } | |
| _note_absent_bits(gres_state_node, target, req->node_name, | |
| req->absent_note); | |
| _drop_absent_bits(gres_ns, target); | |
| } | |
| /* | |
| * Only now that the request is known good: the bits below are set and | |
| * cleared through helpers that require a drain bitmap wide enough to | |
| * address every device the resolved target names. | |
| */ | |
| _gres_bit_drain_resize(gres_ns, bit_size(target)); | |
| switch (req->action) { | |
| case UPDATE_ADD: | |
| _apply_gres_drain_bits(gres_ns, gres_state_node->gres_name, | |
| target, req->reason, true); | |
| break; | |
| case UPDATE_REMOVE: | |
| _apply_gres_drain_bits(gres_ns, gres_state_node->gres_name, | |
| target, NULL, false); | |
| break; | |
| case UPDATE_SET: | |
| to_drain = bit_copy(target); | |
| if (gres_ns->gres_bit_drain) | |
| bit_and_not(to_drain, gres_ns->gres_bit_drain); | |
| to_resume = gres_ns->gres_bit_drain ? | |
| bit_copy(gres_ns->gres_bit_drain) : | |
| bit_alloc(bit_size(target)); | |
| bit_and(to_resume, type_mask); | |
| bit_and_not(to_resume, target); | |
| _note_absent_bits(gres_state_node, to_resume, req->node_name, | |
| req->absent_note); | |
| _drop_absent_bits(gres_ns, to_resume); | |
| _apply_gres_drain_bits(gres_ns, gres_state_node->gres_name, | |
| to_resume, NULL, false); | |
| _apply_gres_drain_bits(gres_ns, gres_state_node->gres_name, | |
| to_drain, req->reason, true); | |
| break; | |
| } | |
| FREE_NULL_BITMAP(to_drain); | |
| FREE_NULL_BITMAP(to_resume); | |
| FREE_NULL_BITMAP(target); | |
| FREE_NULL_BITMAP(type_mask); | |
| return SLURM_SUCCESS; | |
| } | |
| static int _foreach_drain_signature(void *x, void *arg) | |
| { | |
| drained_tres_t *rec = x; | |
| char **sig = arg; | |
| xstrfmtcat(*sig, "%s|%s|%s|%" PRIu64 "|%s\n", rec->tres_name, | |
| rec->index ? rec->index : "", rec->uuid ? rec->uuid : "", | |
| rec->count, rec->reason ? rec->reason : ""); | |
| return 0; | |
| } | |
| /* | |
| * Render everything a drain request can change about one GRES: which devices | |
| * are drained, by index, how many of each type, and why. Taken before and | |
| * after applying a request so that one asking for the state the GRES is | |
| * already in is not reported as a change - reconciling a node is not free, | |
| * and an unchanged node must not get an accounting event. | |
| * | |
| * Built from the same records everything else reports drains from, so a | |
| * change anyone can observe is a change here. | |
| */ | |
| static char *_gres_drain_signature(gres_state_t *gres_state_node) | |
| { | |
| list_t *recs = list_create(drained_tres_delete); | |
| char *sig = NULL; | |
| _add_drained_gres_recs(gres_state_node, recs); | |
| (void) list_for_each(recs, _foreach_drain_signature, &sig); | |
| FREE_NULL_LIST(recs); | |
| return sig; | |
| } | |
| /* | |
| * Apply one resolved drain request to a single GRES (the name has already been | |
| * matched to gres_state_node). Selector legality depends on how the GRES is | |
| * tracked: device-tracked GRES take GRES_DRAIN_SEL_INDEX, _UUID or _ALL, while | |
| * count-only GRES take GRES_DRAIN_SEL_COUNT or _ALL. | |
| */ | |
| static int _update_one_gres_drain(gres_state_t *gres_state_node, | |
| gres_drain_req_t *req, bool *did_change) | |
| { | |
| gres_node_state_t *gres_ns = gres_state_node->gres_data; | |
| bool want_all = (req->sel == GRES_DRAIN_SEL_ALL); | |
| bool by_count = (req->sel == GRES_DRAIN_SEL_COUNT); | |
| char *before, *after; | |
| int rc; | |
| if (!gres_ns) | |
| return ESLURM_INVALID_GRES; | |
| before = _gres_drain_signature(gres_state_node); | |
| if (gres_ns->gres_bit_alloc || gres_ns->topo_cnt) { | |
| rc = _update_device_gres_drain(gres_state_node, req); | |
| } else if (!by_count && !want_all) { | |
| rc = ESLURM_INVALID_GRES; | |
| } else { | |
| rc = _update_count_only_gres_drain(gres_ns, req->type, | |
| by_count ? req->count : 0, | |
| want_all, req->reason, | |
| req->action); | |
| } | |
| if (rc) { | |
| xfree(before); | |
| return rc; | |
| } | |
| xfree(gres_ns->gres_used); | |
| after = _gres_drain_signature(gres_state_node); | |
| if (xstrcmp(before, after)) | |
| *did_change = true; | |
| xfree(before); | |
| xfree(after); | |
| return SLURM_SUCCESS; | |
| } | |
| extern int gres_node_update_drain(list_t *gres_list, gres_drain_req_t *req, | |
| bool *did_change) | |
| { | |
| gres_state_t *gres_state_node; | |
| int rc; | |
| if (!gres_list || !req || !req->name) | |
| return ESLURM_INVALID_GRES; | |
| if ((req->action != UPDATE_REMOVE) && (!req->reason || !req->reason[0])) | |
| return ESLURM_INVALID_NODE_STATE_TRANSITION; | |
| /* | |
| * UPDATE_ADD drains the named TRES, UPDATE_REMOVE resumes them, and | |
| * UPDATE_SET makes the drained set exactly those, scoped to req->type | |
| * when one is given, so a replace never disturbs a type the request did | |
| * not mention. Shared GRES (e.g. shard, mps) cannot be drained | |
| * directly; drain the sharing device (e.g. gpu) instead. | |
| */ | |
| slurm_mutex_lock(&gres_context_lock); | |
| gres_state_node = | |
| list_find_first(gres_list, _find_node_state_by_name, req->name); | |
| if (!gres_state_node) { | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return ESLURM_INVALID_GRES; | |
| } | |
| if (gres_id_shared(gres_state_node->config_flags)) { | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return ESLURM_GRES_SHARED_DRAIN; | |
| } | |
| rc = _update_one_gres_drain(gres_state_node, req, did_change); | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return rc; | |
| } | |
| /* Reason a device was drained, never NULL. */ | |
| static char *_gres_drain_bit_reason(gres_node_state_t *gres_ns, int bit) | |
| { | |
| char *reason = drained_tres_group_reason(gres_ns->drain_groups, bit); | |
| return reason ? reason : DRAINED_TRES_NO_REASON; | |
| } | |
| /* | |
| * The configured name of this type_id. | |
| * | |
| * RET type name, or NULL when the GRES is untyped or the type is no longer | |
| * configured. Such a device cannot be resolved to a name, so it is left | |
| * untyped and keyed by the GRES name alone. | |
| */ | |
| static char *_gres_type_name(gres_node_state_t *gres_ns, uint32_t type_id) | |
| { | |
| int type_index = _find_gres_type(gres_ns, type_id); | |
| return (type_index >= 0) ? gres_ns->type_name[type_index] : NULL; | |
| } | |
| /* "gres/<name>", or "gres/<name>:<type>" when typed. Caller must xfree. */ | |
| static char *_drained_tres_name(char *gres_name, char *type_name) | |
| { | |
| return xstrdup_printf("gres/%s%s%s", gres_name, type_name ? ":" : "", | |
| type_name ? type_name : ""); | |
| } | |
| /* | |
| * Append the drained-TRES records for this GRES: one per drained device for a | |
| * device-tracked GRES, otherwise one per drained count. | |
| * | |
| * Every caller that needs to see what a GRES has drained goes through here, so | |
| * the definition of a drained unit lives in one place. Unlike the display | |
| * string, which groups devices sharing a reason and can therefore carry only an | |
| * index, these are per device, so each record names exactly one drained device. | |
| */ | |
| static void _add_drained_gres_recs(gres_state_t *gres_state_node, list_t *recs) | |
| { | |
| gres_node_state_t *gres_ns = gres_state_node->gres_data; | |
| char *gres_name = gres_state_node->gres_name; | |
| char *tres_name; | |
| if (gres_id_shared(gres_state_node->config_flags)) | |
| return; | |
| if (!gres_ns || !gres_ns->gres_cnt_drain) | |
| return; | |
| if (gres_ns->gres_bit_drain) { | |
| for (int i = 0; | |
| (i = bit_ffs_from_bit(gres_ns->gres_bit_drain, i)) >= 0; | |
| i++) { | |
| char idx[32]; | |
| snprintf(idx, sizeof(idx), "%d", i); | |
| tres_name = _drained_tres_name( | |
| gres_name, | |
| _gres_type_name( | |
| gres_ns, | |
| _gres_device_topo_type_id(gres_ns, i))); | |
| drained_tres_rec_add(recs, tres_name, idx, | |
| (i < gres_ns->dev_uuid_cnt) ? | |
| gres_ns->dev_uuid[i] : | |
| NULL, | |
| NO_VAL64, | |
| _gres_drain_bit_reason(gres_ns, | |
| i)); | |
| xfree(tres_name); | |
| } | |
| return; | |
| } | |
| /* | |
| * A typed count-only GRES is drained per type, so report each type | |
| * against its own name. Reporting the aggregate under the untyped name | |
| * instead loses which type was drained, and names a TRES that | |
| * "scontrol update" would apply to all of them. | |
| */ | |
| if (gres_ns->type_cnt) { | |
| for (int i = 0; i < gres_ns->type_cnt; i++) { | |
| uint64_t count = _gres_cnt_pool_count(gres_ns, i); | |
| if (!count) | |
| continue; | |
| tres_name = _drained_tres_name(gres_name, | |
| gres_ns->type_name[i]); | |
| drained_tres_rec_add(recs, tres_name, NULL, NULL, count, | |
| _gres_type_drain_reason(gres_ns, | |
| i)); | |
| xfree(tres_name); | |
| } | |
| return; | |
| } | |
| tres_name = _drained_tres_name(gres_name, NULL); | |
| drained_tres_rec_add(recs, tres_name, NULL, NULL, | |
| gres_ns->gres_cnt_drain, | |
| gres_ns->untyped_drain.reason); | |
| xfree(tres_name); | |
| } | |
| static int _foreach_add_drained_gres_recs(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_node = x; | |
| list_t *recs = arg; | |
| _add_drained_gres_recs(gres_state_node, recs); | |
| return 0; | |
| } | |
| extern list_t *gres_get_node_drained_tres_recs(list_t *gres_list) | |
| { | |
| list_t *recs; | |
| if (!gres_list) | |
| return NULL; | |
| recs = list_create(drained_tres_delete); | |
| (void) list_for_each(gres_list, _foreach_add_drained_gres_recs, recs); | |
| if (!list_count(recs)) | |
| FREE_NULL_LIST(recs); | |
| return recs; | |
| } | |
| static int _foreach_get_node_used(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_node = x; | |
| char **gres_usedp = arg; | |
| char *gres_used = *gres_usedp; | |
| char *tmp = NULL; | |
| if (!(tmp = _node_gres_used(gres_state_node->gres_data, | |
| gres_state_node->gres_name))) | |
| return 0; | |
| if (gres_used) | |
| xstrcat(gres_used, ","); | |
| xstrcat(gres_used, tmp); | |
| *gres_usedp = gres_used; | |
| return 0; | |
| } | |
| /* Find the name of a Type on a node, NULL when untyped or not found */ | |
| static char *_topo_type_name(gres_node_state_t *gres_ns, uint32_t type_id) | |
| { | |
| for (int i = 0; type_id && (i < gres_ns->topo_cnt); i++) { | |
| if (gres_ns->topo_type_id[i] == type_id) | |
| return gres_ns->topo_type_name[i]; | |
| } | |
| return NULL; | |
| } | |
| static int _foreach_get_node_chunks(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_node = x; | |
| gres_node_state_t *gres_ns = gres_state_node->gres_data; | |
| node_chunks_t *node_chunks = arg; | |
| for (int d = 0; d < gres_ns->chunk_domain_cnt; d++) { | |
| gres_chunk_domain_t *dom = &gres_ns->chunk_domains[d]; | |
| char *type_name = _topo_type_name(gres_ns, dom->type_id); | |
| bitstr_t *devs = NULL; | |
| char *sep = ""; | |
| int max_dev = 0; | |
| if (node_chunks->chunks) | |
| xstrcatat(node_chunks->chunks, &node_chunks->pos, ","); | |
| xstrfmtcatat(node_chunks->chunks, &node_chunks->pos, | |
| "%s%s%s:sizes=", gres_state_node->gres_name, | |
| type_name ? ":" : "", type_name ? type_name : ""); | |
| for (int i = 0; i < dom->sizes_cnt; i++) { | |
| xstrfmtcatat(node_chunks->chunks, &node_chunks->pos, | |
| "%s%u", sep, dom->sizes[i]); | |
| sep = "/"; | |
| } | |
| /* Number the devices within the Type, as ChunkOrder does */ | |
| for (int i = 0; i < dom->order_len; i++) | |
| max_dev = MAX(max_dev, dom->order[i]); | |
| devs = bit_alloc(max_dev + 1); | |
| for (int i = 0; i < dom->order_len; i++) | |
| bit_set(devs, dom->order[i]); | |
| sep = ""; | |
| xstrcatat(node_chunks->chunks, &node_chunks->pos, ";order="); | |
| for (int i = 0; i < dom->order_len; i++) { | |
| xstrfmtcatat(node_chunks->chunks, &node_chunks->pos, | |
| "%s%d", sep, | |
| bit_set_count_range(devs, 0, | |
| dom->order[i])); | |
| sep = "/"; | |
| } | |
| FREE_NULL_BITMAP(devs); | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Build a string describing a node's GRES chunk configuration | |
| * IN gres_list - generated by gres_node_config_validate() | |
| * RET string, must be xfreed. NULL if no GRES uses chunks. | |
| */ | |
| extern char *gres_get_node_chunks(list_t *gres_list) | |
| { | |
| node_chunks_t node_chunks = { 0 }; | |
| if (gres_list) | |
| (void) list_for_each(gres_list, _foreach_get_node_chunks, | |
| &node_chunks); | |
| return node_chunks.chunks; | |
| } | |
| /* | |
| * Build a string indicating a node's used GRES | |
| * IN gres_list - generated by gres_node_config_validate() | |
| * RET - string, must be xfreed by caller | |
| */ | |
| extern char *gres_get_node_used(list_t *gres_list) | |
| { | |
| char *gres_used = NULL; | |
| if (gres_list) | |
| (void) list_for_each(gres_list, | |
| _foreach_get_node_used, | |
| &gres_used); | |
| return gres_used; | |
| } | |
| /* | |
| * Give the total system count of a given GRES | |
| * Returns NO_VAL64 if name not found | |
| */ | |
| extern uint64_t gres_get_system_cnt(char *name, bool case_insensitive) | |
| { | |
| uint64_t count = NO_VAL64; | |
| int i; | |
| if (!name) | |
| return NO_VAL64; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| if (case_insensitive ? | |
| !xstrcasecmp(gres_context[i].gres_name, name) : | |
| !xstrcmp(gres_context[i].gres_name, name)) { | |
| count = gres_context[i].total_cnt; | |
| break; | |
| } | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return count; | |
| } | |
| /* | |
| * Get the count of a node's GRES | |
| * IN gres_list - List of Gres records for this node to track usage | |
| * IN name - name of gres | |
| */ | |
| extern uint64_t gres_node_config_cnt(list_t *gres_list, char *name) | |
| { | |
| int i; | |
| gres_state_t *gres_state_node; | |
| gres_node_state_t *gres_ns; | |
| uint64_t count = 0; | |
| if (!gres_list || !name || !list_count(gres_list)) | |
| return count; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| if (!xstrcasecmp(gres_context[i].gres_name, name)) { | |
| /* Find or create gres_state entry on the list */ | |
| gres_state_node = list_find_first( | |
| gres_list, gres_find_id, | |
| &gres_context[i].plugin_id); | |
| if (!gres_state_node || !gres_state_node->gres_data) | |
| break; | |
| gres_ns = gres_state_node->gres_data; | |
| count = gres_ns->gres_cnt_config; | |
| break; | |
| } else if (!xstrncasecmp(name, gres_context[i].gres_name_colon, | |
| gres_context[i].gres_name_colon_len)) { | |
| int type; | |
| uint32_t type_id; | |
| char *type_str = NULL; | |
| if (!(type_str = strchr(name, ':'))) { | |
| error("Invalid gres name '%s'", name); | |
| break; | |
| } | |
| type_str++; | |
| gres_state_node = list_find_first( | |
| gres_list, gres_find_id, | |
| &gres_context[i].plugin_id); | |
| if (!gres_state_node || !gres_state_node->gres_data) | |
| break; | |
| gres_ns = gres_state_node->gres_data; | |
| type_id = gres_build_id(type_str); | |
| for (type = 0; type < gres_ns->type_cnt; type++) { | |
| if (gres_ns->type_id[type] == type_id) { | |
| count = gres_ns->type_cnt_avail[type]; | |
| break; | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return count; | |
| } | |
| extern void gres_job_state_delete(gres_job_state_t *gres_js) | |
| { | |
| int i; | |
| if (gres_js == NULL) | |
| return; | |
| gres_job_clear_alloc(gres_js); | |
| if (gres_js->gres_bit_select) { | |
| for (i = 0; i < gres_js->total_node_cnt; i++) | |
| FREE_NULL_BITMAP(gres_js->gres_bit_select[i]); | |
| xfree(gres_js->gres_bit_select); | |
| } | |
| if (gres_js->gres_per_bit_select) { | |
| for (i = 0; i < gres_js->total_node_cnt; i++){ | |
| xfree(gres_js->gres_per_bit_select[i]); | |
| } | |
| xfree(gres_js->gres_per_bit_select); | |
| } | |
| if (gres_js->res_gpu_cores) { | |
| for (i = 0; i < gres_js->res_array_size; i++) { | |
| FREE_NULL_BITMAP(gres_js->res_gpu_cores[i]); | |
| } | |
| xfree(gres_js->res_gpu_cores); | |
| } | |
| xfree(gres_js->gres_cnt_node_alloc); | |
| xfree(gres_js->gres_cnt_node_select); | |
| xfree(gres_js->type_name); | |
| xfree(gres_js); | |
| } | |
| extern void gres_job_clear_alloc(gres_job_state_t *gres_js) | |
| { | |
| for (int i = 0; i < gres_js->node_cnt; i++) { | |
| if (gres_js->gres_bit_alloc) | |
| FREE_NULL_BITMAP(gres_js->gres_bit_alloc[i]); | |
| if (gres_js->gres_bit_step_alloc) | |
| FREE_NULL_BITMAP(gres_js->gres_bit_step_alloc[i]); | |
| if (gres_js->gres_per_bit_alloc) | |
| xfree(gres_js->gres_per_bit_alloc[i]); | |
| if (gres_js->gres_per_bit_step_alloc) | |
| xfree(gres_js->gres_per_bit_step_alloc[i]); | |
| } | |
| xfree(gres_js->gres_bit_alloc); | |
| xfree(gres_js->gres_bit_step_alloc); | |
| xfree(gres_js->gres_per_bit_alloc); | |
| xfree(gres_js->gres_per_bit_step_alloc); | |
| xfree(gres_js->gres_cnt_step_alloc); | |
| xfree(gres_js->gres_cnt_node_alloc); | |
| gres_js->node_cnt = 0; | |
| } | |
| /* | |
| * Remap a pointer array from old node indices to new ones. | |
| * Orphaned elements (old index maps to -1 or out of range) are freed | |
| * via elem_free; pass NULL to simply discard orphaned values. | |
| */ | |
| static void _remap_ptr_array(void ***arr_ptr, int *old_to_new, uint32_t limit, | |
| uint32_t new_cnt, void (*elem_free)(void *)) | |
| { | |
| void **old_arr = *arr_ptr; | |
| void **new_arr = xcalloc(new_cnt, sizeof(void *)); | |
| for (uint32_t i = 0; i < limit; i++) { | |
| if (!old_arr[i]) | |
| continue; | |
| if ((old_to_new[i] >= 0) && (old_to_new[i] < (int) new_cnt)) | |
| new_arr[old_to_new[i]] = old_arr[i]; | |
| else if (elem_free) | |
| elem_free(old_arr[i]); | |
| old_arr[i] = NULL; | |
| } | |
| xfree(*arr_ptr); | |
| *arr_ptr = new_arr; | |
| } | |
| /* | |
| * Remap a uint64_t value array from old node indices to new ones. | |
| * Orphaned entries (old index maps to -1 or out of range) are dropped. | |
| */ | |
| static void _remap_uint64_t_array(uint64_t **arr_ptr, int *old_to_new, | |
| uint32_t limit, uint32_t new_cnt) | |
| { | |
| uint64_t *old_arr = *arr_ptr; | |
| uint64_t *new_arr = xcalloc(new_cnt, sizeof(uint64_t)); | |
| for (uint32_t i = 0; i < limit; i++) { | |
| if ((old_to_new[i] >= 0) && (old_to_new[i] < (int) new_cnt)) | |
| new_arr[old_to_new[i]] = old_arr[i]; | |
| } | |
| xfree(*arr_ptr); | |
| *arr_ptr = new_arr; | |
| } | |
| static int _foreach_resv_gres_remap(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_job = x; | |
| gres_job_state_t *gres_js; | |
| uint32_t new_cnt = node_record_count; | |
| uint32_t limit; | |
| if (!gres_state_job) | |
| return 0; | |
| gres_js = gres_state_job->gres_data; | |
| if (!gres_js || !gres_js->node_cnt) | |
| return 0; | |
| log_flag(GRES, "%s: remapping %s:%s node_cnt %u -> %u", | |
| __func__, | |
| gres_state_job->gres_name ? gres_state_job->gres_name : "?", | |
| (gres_js->type_name && gres_js->type_name[0]) ? | |
| gres_js->type_name : "(typeless)", | |
| gres_js->node_cnt, new_cnt); | |
| limit = MIN(gres_js->node_cnt, old_node_record_count); | |
| if (gres_js->gres_bit_alloc) | |
| _remap_ptr_array((void ***) &gres_js->gres_bit_alloc, | |
| node_old_to_new_map, limit, new_cnt, | |
| bit_free_ptr); | |
| if (gres_js->gres_cnt_node_alloc) | |
| _remap_uint64_t_array(&gres_js->gres_cnt_node_alloc, | |
| node_old_to_new_map, limit, new_cnt); | |
| if (gres_js->gres_per_bit_alloc) | |
| _remap_ptr_array((void ***) &gres_js->gres_per_bit_alloc, | |
| node_old_to_new_map, limit, new_cnt, | |
| xfree_ptr); | |
| gres_js->node_cnt = new_cnt; | |
| return 0; | |
| } | |
| extern void gres_resv_list_remap_global_indices(list_t *gres_list) | |
| { | |
| xassert(gres_list); | |
| xassert(node_record_count); | |
| xassert(node_old_to_new_map); | |
| xassert(old_node_record_count); | |
| if (!gres_list || !node_record_count || !node_old_to_new_map || | |
| !old_node_record_count) { | |
| error("%s: called with invalid state (gres_list=%p node_record_count=%d map=%p old_cnt=%u)", | |
| __func__, gres_list, node_record_count, | |
| node_old_to_new_map, old_node_record_count); | |
| return; | |
| } | |
| (void) list_for_each(gres_list, _foreach_resv_gres_remap, NULL); | |
| } | |
| extern void gres_job_list_delete(void *list_element) | |
| { | |
| gres_state_t *gres_state_job; | |
| gres_state_job = (gres_state_t *) list_element; | |
| gres_job_state_delete(gres_state_job->gres_data); | |
| gres_state_job->gres_data = NULL; | |
| _gres_state_delete_members(gres_state_job); | |
| } | |
| /* | |
| * Ensure consistency of gres_per_* options | |
| * Modify task and node count as needed for consistentcy with GRES options | |
| * RET -1 on failure, 0 on success | |
| */ | |
| static int _test_gres_cnt(gres_state_t *gres_state_job, | |
| gres_job_state_validate_t *gres_js_val) | |
| { | |
| gres_job_state_t *gres_js = gres_state_job->gres_data; | |
| int req_nodes, req_tasks, req_tasks_per_node, req_tasks_per_socket; | |
| int req_sockets, req_cpus_per_task; | |
| uint16_t cpus_per_gres; | |
| /* Ensure gres_per_job >= gres_per_node >= gres_per_socket */ | |
| if (gres_js->gres_per_job && | |
| ((gres_js->gres_per_node && | |
| (gres_js->gres_per_node > gres_js->gres_per_job)) || | |
| (gres_js->gres_per_task && | |
| (gres_js->gres_per_task > gres_js->gres_per_job)) || | |
| (gres_js->gres_per_socket && | |
| (gres_js->gres_per_socket > | |
| gres_js->gres_per_job)))) { | |
| error("Failed to ensure --%ss >= --gres=%s/--%ss-per-node >= --%ss-per-socket", | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name); | |
| return -1; | |
| } | |
| /* Ensure gres_per_job >= gres_per_task */ | |
| if (gres_js->gres_per_node && | |
| ((gres_js->gres_per_task && | |
| (gres_js->gres_per_task > gres_js->gres_per_node)) || | |
| (gres_js->gres_per_socket && | |
| (gres_js->gres_per_socket > | |
| gres_js->gres_per_node)))) { | |
| error("Failed to ensure --%ss >= --%ss-per-task", | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name); | |
| return -1; | |
| } | |
| /* gres_per_socket requires sockets-per-node count specification */ | |
| if (gres_js->gres_per_socket) { | |
| if (*gres_js_val->sockets_per_node == NO_VAL16) { | |
| error("--%ss-per-socket option requires --sockets-per-node specification", | |
| gres_state_job->gres_name); | |
| return -1; | |
| } | |
| } | |
| /* make sure --cpu-per-gres is not combined with --cpus-per-task */ | |
| if (!running_in_slurmctld() && gres_js->cpus_per_gres && | |
| (*gres_js_val->cpus_per_task != NO_VAL16)) { | |
| error("--cpus-per-%s is mutually exclusive with --cpus-per-task", | |
| gres_state_job->gres_name); | |
| return -1; | |
| } | |
| /* | |
| * Ensure gres_per_job is multiple of gres_per_node | |
| * Ensure node count is consistent with GRES parameters | |
| */ | |
| if (gres_js->gres_per_job && gres_js->gres_per_node) { | |
| if (gres_js->gres_per_job % gres_js->gres_per_node){ | |
| /* gres_per_job not multiple of gres_per_node */ | |
| error("Failed to validate job spec, --%ss is not multiple of --gres=%s/--%ss-per-node", | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name); | |
| return -1; | |
| } | |
| req_nodes = gres_js->gres_per_job / | |
| gres_js->gres_per_node; | |
| if (((*gres_js_val->min_nodes != NO_VAL) && | |
| (req_nodes < *gres_js_val->min_nodes)) || | |
| (req_nodes > *gres_js_val->max_nodes)) { | |
| error("Failed to validate job spec. Based on --%s and --gres=%s/--%ss-per-node required nodes (%u) doesn't fall between min_nodes (%u) and max_nodes (%u) boundaries.", | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name, | |
| req_nodes, | |
| *gres_js_val->min_nodes, | |
| *gres_js_val->max_nodes); | |
| return -1; | |
| } | |
| *gres_js_val->min_nodes = *gres_js_val->max_nodes = req_nodes; | |
| } | |
| /* | |
| * Ensure gres_per_node is multiple of gres_per_socket | |
| * Ensure task count is consistent with GRES parameters | |
| */ | |
| if (gres_js->gres_per_node && gres_js->gres_per_socket) { | |
| if (gres_js->gres_per_node % | |
| gres_js->gres_per_socket) { | |
| /* gres_per_node not multiple of gres_per_socket */ | |
| error("Failed to validate job spec, --gres=%s/--%ss-per-node not multiple of --%ss-per-socket.", | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name); | |
| return -1; | |
| } | |
| req_sockets = gres_js->gres_per_node / | |
| gres_js->gres_per_socket; | |
| if (*gres_js_val->sockets_per_node == NO_VAL16) | |
| *gres_js_val->sockets_per_node = req_sockets; | |
| else if (*gres_js_val->sockets_per_node != req_sockets) { | |
| error("Failed to validate job spec. Based on --gres=%s/--%ss-per-node and --%ss-per-socket required number of sockets differ from --sockets-per-node.", | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name); | |
| return -1; | |
| } | |
| } | |
| /* | |
| * Ensure ntasks_per_tres is multiple of num_tasks | |
| */ | |
| if (gres_js->ntasks_per_gres && | |
| (gres_js->ntasks_per_gres != NO_VAL16) && | |
| (*gres_js_val->num_tasks != NO_VAL)) { | |
| int tmp = *gres_js_val->num_tasks / gres_js->ntasks_per_gres; | |
| if ((tmp * gres_js->ntasks_per_gres) != | |
| *gres_js_val->num_tasks) { | |
| error("Failed to validate job spec, -n/--ntasks has to be a multiple of --ntasks-per-%s.", | |
| gres_state_job->gres_name); | |
| return -1; | |
| } | |
| } | |
| /* | |
| * Ensure gres_per_job is multiple of gres_per_task | |
| * Ensure task count is consistent with GRES parameters | |
| */ | |
| if (gres_js->gres_per_task) { | |
| if(gres_js->gres_per_job) { | |
| if (gres_js->gres_per_job % | |
| gres_js->gres_per_task) { | |
| /* gres_per_job not multiple of gres_per_task */ | |
| error("Failed to validate job spec, --%ss not multiple of --%ss-per-task", | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name); | |
| return -1; | |
| } | |
| req_tasks = gres_js->gres_per_job / | |
| gres_js->gres_per_task; | |
| if (*gres_js_val->num_tasks == NO_VAL) | |
| *gres_js_val->num_tasks = req_tasks; | |
| else if (*gres_js_val->num_tasks != req_tasks) { | |
| if (running_in_slurmctld()) { | |
| /* requesting new task count */ | |
| gres_js->total_gres = | |
| gres_js->gres_per_job = | |
| *gres_js_val->num_tasks * | |
| gres_js->gres_per_task; | |
| } else { | |
| /* | |
| * Anywhere outside of the slurmctld we | |
| * are asking for something incorrect. | |
| */ | |
| error("Failed to validate job spec. Based on --%ss and --%ss-per-task number of requested tasks differ from -n/--ntasks.", | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name); | |
| return -1; | |
| } | |
| } | |
| } else if (*gres_js_val->num_tasks != NO_VAL) { | |
| gres_js->gres_per_job = *gres_js_val->num_tasks * | |
| gres_js->gres_per_task; | |
| } else if (!xstrcmp(gres_state_job->gres_name, "gpu")) { | |
| error("Failed to validate job spec. --%ss-per-task or --tres-per-task used without either --%ss or -n/--ntasks is not allowed.", | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name); | |
| return -1; | |
| } else { | |
| error("Failed to validate job spec. --tres-per-task used without -n/--ntasks is not allowed."); | |
| return -1; | |
| } | |
| } | |
| /* | |
| * Ensure gres_per_node is multiple of gres_per_task | |
| * Ensure tasks_per_node is consistent with GRES parameters | |
| */ | |
| if (gres_js->gres_per_node && gres_js->gres_per_task) { | |
| if (gres_js->gres_per_node % | |
| gres_js->gres_per_task) { | |
| /* gres_per_node not multiple of gres_per_task */ | |
| error("Failed to validate job spec, --gres=%s/--%ss-per-node not multiple of --%ss-per-task.", | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name); | |
| return -1; | |
| } | |
| req_tasks_per_node = gres_js->gres_per_node / | |
| gres_js->gres_per_task; | |
| if ((*gres_js_val->ntasks_per_node == NO_VAL16) || | |
| (*gres_js_val->ntasks_per_node == 0)) | |
| *gres_js_val->ntasks_per_node = req_tasks_per_node; | |
| else if (*gres_js_val->ntasks_per_node != req_tasks_per_node) { | |
| error("Failed to validate job spec. Based on --gres=%s/--%ss-per-node and --%ss-per-task requested number of tasks per node differ from --ntasks-per-node.", | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name); | |
| return -1; | |
| } | |
| } | |
| /* | |
| * Ensure gres_per_socket is multiple of gres_per_task | |
| * Ensure ntasks_per_socket is consistent with GRES parameters | |
| */ | |
| if (gres_js->gres_per_socket && gres_js->gres_per_task) { | |
| if (gres_js->gres_per_socket % | |
| gres_js->gres_per_task) { | |
| /* gres_per_socket not multiple of gres_per_task */ | |
| error("Failed to validate job spec, --%ss-per-socket not multiple of --%ss-per-task.", | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name); | |
| return -1; | |
| } | |
| req_tasks_per_socket = gres_js->gres_per_socket / | |
| gres_js->gres_per_task; | |
| if ((*gres_js_val->ntasks_per_socket == NO_VAL16) || | |
| (*gres_js_val->ntasks_per_socket == 0)) | |
| *gres_js_val->ntasks_per_socket = req_tasks_per_socket; | |
| else if (*gres_js_val->ntasks_per_socket != | |
| req_tasks_per_socket) { | |
| error("Failed to validate job spec. Based on --%ss-per-socket and --%ss-per-task requested number of tasks per sockets differ from --ntasks-per-socket.", | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name); | |
| return -1; | |
| } | |
| } | |
| /* Ensure that cpus_per_gres * gres_per_task == cpus_per_task */ | |
| if (gres_js->cpus_per_gres) | |
| cpus_per_gres = gres_js->cpus_per_gres; | |
| else | |
| cpus_per_gres = gres_js->def_cpus_per_gres; | |
| if (cpus_per_gres && gres_js->gres_per_task) { | |
| req_cpus_per_task = cpus_per_gres * gres_js->gres_per_task; | |
| if ((*gres_js_val->cpus_per_task == NO_VAL16) || | |
| (*gres_js_val->cpus_per_task == 0)) | |
| *gres_js_val->cpus_per_task = req_cpus_per_task; | |
| else if (*gres_js_val->cpus_per_task != req_cpus_per_task) { | |
| error("Failed to validate job spec. Based on --cpus-per-%s and --%ss-per-task requested number of cpus differ from -c/--cpus-per-task.", | |
| gres_state_job->gres_name, | |
| gres_state_job->gres_name); | |
| return -1; | |
| } | |
| } | |
| /* Ensure tres_per_job >= node count */ | |
| if (gres_js->gres_per_job) { | |
| if ((*gres_js_val->min_nodes != NO_VAL) && | |
| (gres_js->gres_per_job < *gres_js_val->min_nodes)) { | |
| error("Failed to validate job spec, --%ss < -N", | |
| gres_state_job->gres_name); | |
| return -1; | |
| } | |
| if ((*gres_js_val->max_nodes != NO_VAL) && | |
| (gres_js->gres_per_job < *gres_js_val->max_nodes)) { | |
| *gres_js_val->max_nodes = gres_js->gres_per_job; | |
| } | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Reentrant TRES specification parse logic | |
| * in_val IN - initial input string | |
| * type OUT - must be xfreed by caller | |
| * cnt OUT - count of values | |
| * save_ptr IN/OUT - NULL on initial call, otherwise value from previous call | |
| * RET rc - error code | |
| */ | |
| static int _get_next_gres(char *in_val, char **type_ptr, int *context_inx_ptr, | |
| uint64_t *cnt, char **save_ptr) | |
| { | |
| char *name = NULL, *type = NULL, *tres_type = "gres"; | |
| int i, rc = SLURM_SUCCESS; | |
| uint64_t value = 0; | |
| xassert(cnt); | |
| xassert(save_ptr); | |
| rc = slurm_get_next_tres(&tres_type, in_val, &name, &type, | |
| &value, save_ptr); | |
| if (name) { | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| if (!xstrcmp(name, gres_context[i].gres_name) || | |
| !xstrncmp(name, gres_context[i].gres_name_colon, | |
| gres_context[i].gres_name_colon_len)) | |
| break; /* GRES name match found */ | |
| } | |
| if (i >= gres_context_cnt) { | |
| debug("%s: Failed to locate GRES %s", __func__, name); | |
| rc = ESLURM_INVALID_GRES; | |
| } else | |
| *context_inx_ptr = i; | |
| xfree(name); | |
| } | |
| if (rc != SLURM_SUCCESS) { | |
| *save_ptr = NULL; | |
| if ((rc == ESLURM_INVALID_TRES) && running_in_slurmctld()) { | |
| info("%s: Invalid GRES job specification %s", __func__, | |
| in_val); | |
| } | |
| xfree(type); | |
| *type_ptr = NULL; | |
| } else { | |
| *cnt = value; | |
| *type_ptr = type; | |
| } | |
| xfree(name); | |
| return rc; | |
| } | |
| /* | |
| * TRES specification parse logic | |
| * in_val IN - initial input string | |
| * cnt OUT - count of values | |
| * gres_list IN/OUT - where to search for (or add) new job TRES record | |
| * save_ptr IN/OUT - NULL on initial call, otherwise value from previous call | |
| * rc OUT - unchanged or an error code | |
| * RET gres - job record to set value in, found or created by this function | |
| */ | |
| static gres_state_t *_get_next_job_gres(char *in_val, uint64_t *cnt, | |
| list_t *gres_list, char **save_ptr, | |
| int *rc) | |
| { | |
| static char *prev_save_ptr = NULL; | |
| int context_inx = NO_VAL, my_rc = SLURM_SUCCESS; | |
| gres_job_state_t *gres_js = NULL; | |
| gres_state_t *gres_state_job = NULL; | |
| gres_key_t job_search_key; | |
| char *type = NULL, *name = NULL; | |
| xassert(save_ptr); | |
| if (!in_val && (*save_ptr == NULL)) { | |
| return NULL; | |
| } | |
| if (*save_ptr == NULL) { | |
| prev_save_ptr = in_val; | |
| } else if (*save_ptr != prev_save_ptr) { | |
| error("%s: parsing error", __func__); | |
| my_rc = SLURM_ERROR; | |
| goto fini; | |
| } | |
| if (prev_save_ptr[0] == '\0') { /* Empty input token */ | |
| *save_ptr = NULL; | |
| return NULL; | |
| } | |
| if ((my_rc = _get_next_gres(in_val, &type, &context_inx, | |
| cnt, &prev_save_ptr)) || | |
| (context_inx == NO_VAL)) { | |
| prev_save_ptr = NULL; | |
| goto fini; | |
| } | |
| /* Find the job GRES record */ | |
| job_search_key.config_flags = gres_context[context_inx].config_flags; | |
| job_search_key.plugin_id = gres_context[context_inx].plugin_id; | |
| job_search_key.type_id = gres_build_id(type); | |
| gres_state_job = list_find_first(gres_list, gres_find_job_by_key, | |
| &job_search_key); | |
| if (gres_state_job) { | |
| gres_js = gres_state_job->gres_data; | |
| } else { | |
| gres_js = xmalloc(sizeof(gres_job_state_t)); | |
| gres_js->type_id = job_search_key.type_id; | |
| gres_js->type_name = type; | |
| type = NULL; /* String moved above */ | |
| gres_state_job = gres_create_state( | |
| &gres_context[context_inx], GRES_STATE_SRC_CONTEXT_PTR, | |
| GRES_STATE_TYPE_JOB, gres_js); | |
| list_append(gres_list, gres_state_job); | |
| } | |
| fini: xfree(name); | |
| xfree(type); | |
| if (my_rc != SLURM_SUCCESS) { | |
| prev_save_ptr = NULL; | |
| if ((my_rc == ESLURM_INVALID_GRES) && running_in_slurmctld()) { | |
| info("%s: Invalid GRES job specification %s", __func__, | |
| in_val); | |
| } | |
| *rc = my_rc; | |
| } | |
| *save_ptr = prev_save_ptr; | |
| return gres_state_job; | |
| } | |
| /* Return true if job specification only includes cpus_per_gres or mem_per_gres | |
| * Return false if any other field set | |
| */ | |
| static bool _generic_state(void *gres_data, bool is_job) | |
| { | |
| if (is_job) { | |
| gres_job_state_t *gres_js = gres_data; | |
| if (gres_js->gres_per_job || | |
| gres_js->gres_per_node || | |
| gres_js->gres_per_socket || | |
| gres_js->gres_per_task) | |
| return false; | |
| } else { | |
| gres_step_state_t *gres_ss = gres_data; | |
| if (gres_ss->gres_per_step || | |
| gres_ss->gres_per_node || | |
| gres_ss->gres_per_socket || | |
| gres_ss->gres_per_task) | |
| return false; | |
| } | |
| return true; | |
| } | |
| /* | |
| * Setup over_array to mark if we have gres of the same type. | |
| */ | |
| static void _set_over_array(gres_state_t *gres_state, | |
| job_validate_t *job_validate) | |
| { | |
| char *type_name = job_validate->is_job ? | |
| ((gres_job_state_t *) gres_state->gres_data)->type_name: | |
| ((gres_step_state_t *) gres_state->gres_data)->type_name; | |
| int i; | |
| overlap_check_t *overlap_check = NULL; | |
| xassert(job_validate->over_array); | |
| for (i = 0; i < job_validate->over_count; i++) { | |
| if (job_validate->over_array[i].plugin_id == | |
| gres_state->plugin_id) | |
| break; | |
| } | |
| /* | |
| * Set overlap_check after the loop since when over_count is 0 the loop | |
| * won't happen. | |
| */ | |
| overlap_check = &job_validate->over_array[i]; | |
| xassert(overlap_check); | |
| if (i >= job_validate->over_count) { | |
| job_validate->over_count++; | |
| overlap_check->plugin_id = gres_state->plugin_id; | |
| if (type_name) { | |
| overlap_check->with_type = true; | |
| } else { | |
| overlap_check->without_type = true; | |
| overlap_check->without_type_state = | |
| gres_state->gres_data; | |
| } | |
| } else if (type_name) { | |
| overlap_check->with_type = true; | |
| if (overlap_check->without_type) | |
| job_validate->overlap_merge = true; | |
| } else { | |
| overlap_check->without_type = true; | |
| overlap_check->without_type_state = gres_state->gres_data; | |
| if (overlap_check->with_type) | |
| job_validate->overlap_merge = true; | |
| } | |
| return; | |
| } | |
| static int _foreach_merge_generic_data(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state = x; | |
| merge_generic_t *merge_generic = arg; | |
| if (merge_generic->plugin_id != gres_state->plugin_id) | |
| return 0; | |
| if (merge_generic->generic_gres_data == gres_state->gres_data) | |
| return 1; | |
| if (merge_generic->is_job) { | |
| gres_job_state_t *gres_js_in = merge_generic->generic_gres_data; | |
| gres_job_state_t *gres_js = gres_state->gres_data; | |
| if (!gres_js->cpus_per_gres) | |
| gres_js->cpus_per_gres = gres_js_in->cpus_per_gres; | |
| if (!gres_js->mem_per_gres) | |
| gres_js->mem_per_gres = gres_js_in->mem_per_gres; | |
| } else { | |
| gres_step_state_t *gres_ss_in = | |
| merge_generic->generic_gres_data; | |
| gres_step_state_t *gres_ss = gres_state->gres_data; | |
| if (!gres_ss->cpus_per_gres) | |
| gres_ss->cpus_per_gres = gres_ss_in->cpus_per_gres; | |
| if (!gres_ss->mem_per_gres) | |
| gres_ss->mem_per_gres = gres_ss_in->mem_per_gres; | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Put generic data (*_per_gres) on other gres of the same kind. | |
| */ | |
| static int _merge_generic_data( | |
| list_t *gres_list, job_validate_t *job_validate) | |
| { | |
| int rc = SLURM_SUCCESS; | |
| merge_generic_t merge_generic = { | |
| .is_job = job_validate->is_job, | |
| }; | |
| for (int i = 0; i < job_validate->over_count; i++) { | |
| overlap_check_t *overlap_check = &job_validate->over_array[i]; | |
| if (!overlap_check->with_type || | |
| !overlap_check->without_type_state) | |
| continue; | |
| if (!_generic_state(overlap_check->without_type_state, | |
| job_validate->is_job)) { | |
| rc = ESLURM_INVALID_GRES_TYPE; | |
| break; | |
| } | |
| /* Propagate generic parameters */ | |
| merge_generic.generic_gres_data = | |
| overlap_check->without_type_state; | |
| merge_generic.plugin_id = overlap_check->plugin_id; | |
| (void) list_delete_all(gres_list, | |
| _foreach_merge_generic_data, | |
| &merge_generic); | |
| } | |
| return rc; | |
| } | |
| static int _foreach_set_over_array(void *x, void *arg) | |
| { | |
| _set_over_array(x, arg); | |
| return 0; | |
| } | |
| static int _foreach_job_state_validate(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_job = x; | |
| gres_job_state_t *gres_js = gres_state_job->gres_data; | |
| job_validate_t *job_validate = arg; | |
| if (_test_gres_cnt(gres_state_job, job_validate->gres_js_val) != 0) { | |
| job_validate->rc = ESLURM_INVALID_GRES; | |
| return -1; | |
| } | |
| if (!job_validate->have_gres_sharing && | |
| gres_id_sharing(gres_state_job->plugin_id)) | |
| job_validate->have_gres_sharing = true; | |
| if (gres_id_shared(gres_state_job->config_flags)) { | |
| job_validate->have_gres_shared = true; | |
| } | |
| if (job_validate->have_gres_sharing && job_validate->have_gres_shared) { | |
| job_validate->rc = ESLURM_INVALID_GRES; | |
| return -1; | |
| } | |
| if (job_validate->cpus_per_gres && | |
| (gres_state_job->plugin_id == gres_get_gpu_plugin_id())) | |
| job_validate->tmp_min_cpus += | |
| job_validate->cpus_per_gres * gres_js->total_gres; | |
| (void) _foreach_set_over_array(gres_state_job, job_validate); | |
| return 0; | |
| } | |
| extern int gres_job_state_validate(gres_job_state_validate_t *gres_js_val) | |
| { | |
| int rc = SLURM_SUCCESS, size; | |
| bool requested_gpu = false; | |
| gres_state_t *gres_state_job; | |
| gres_job_state_t *gres_js; | |
| uint64_t cnt = 0; | |
| char *cpus_per_tres; | |
| char *mem_per_tres; | |
| char *tres_freq; | |
| char *tres_per_job; | |
| char *tres_per_node; | |
| char *tres_per_socket; | |
| char *tres_per_task; | |
| job_validate_t job_validate = { | |
| .gres_js_val = gres_js_val, | |
| .is_job = true, | |
| .rc = SLURM_SUCCESS, | |
| }; | |
| xassert(gres_js_val); | |
| xassert(gres_js_val->gres_list); | |
| xassert(!*gres_js_val->gres_list); | |
| cpus_per_tres = gres_js_val->cpus_per_tres; | |
| mem_per_tres = gres_js_val->mem_per_tres; | |
| tres_freq = gres_js_val->tres_freq; | |
| tres_per_job = gres_js_val->tres_per_job; | |
| tres_per_node = gres_js_val->tres_per_node; | |
| tres_per_socket = gres_js_val->tres_per_socket; | |
| tres_per_task = gres_js_val->tres_per_task; | |
| if (tres_per_task && running_in_slurmctld() && !running_cons_tres()) { | |
| char *tmp = xstrdup(tres_per_task); | |
| /* | |
| * Check if cpus_per_task is the only part of tres_per_task. If | |
| * so, continue with validation. If not, then the request is | |
| * invalid: reject the request. | |
| */ | |
| slurm_option_update_tres_per_task(0, "cpu", &tmp); | |
| if (tmp) { | |
| xfree(tmp); | |
| return ESLURM_UNSUPPORTED_GRES; | |
| } | |
| } | |
| if (running_in_slurmctld() && !running_cons_tres() && | |
| (cpus_per_tres || tres_per_job || tres_per_socket || mem_per_tres)) | |
| return ESLURM_UNSUPPORTED_GRES; | |
| if (!cpus_per_tres && !tres_per_job && !tres_per_node && | |
| !tres_per_socket && !tres_per_task && !mem_per_tres && | |
| !gres_js_val->ntasks_per_tres) | |
| return SLURM_SUCCESS; | |
| if ((tres_per_task || (*gres_js_val->ntasks_per_tres != NO_VAL16)) && | |
| (*gres_js_val->num_tasks == NO_VAL) && | |
| (*gres_js_val->min_nodes != NO_VAL) && | |
| (*gres_js_val->min_nodes == *gres_js_val->max_nodes)) { | |
| /* Implicitly set task count */ | |
| if (*gres_js_val->ntasks_per_tres != NO_VAL16) | |
| *gres_js_val->num_tasks = *gres_js_val->min_nodes * | |
| *gres_js_val->ntasks_per_tres; | |
| else if (*gres_js_val->ntasks_per_node != NO_VAL16) | |
| *gres_js_val->num_tasks = *gres_js_val->min_nodes * | |
| *gres_js_val->ntasks_per_node; | |
| else if (*gres_js_val->cpus_per_task == NO_VAL16) | |
| *gres_js_val->num_tasks = *gres_js_val->min_nodes; | |
| } | |
| xassert(gres_context_cnt >= 0); | |
| /* | |
| * Set new values as requested | |
| */ | |
| *gres_js_val->gres_list = list_create(gres_job_list_delete); | |
| slurm_mutex_lock(&gres_context_lock); | |
| if (cpus_per_tres) { | |
| char *in_val = cpus_per_tres, *save_ptr = NULL; | |
| while ((gres_state_job = _get_next_job_gres(in_val, &cnt, | |
| *gres_js_val-> | |
| gres_list, | |
| &save_ptr, &rc))) { | |
| gres_js = gres_state_job->gres_data; | |
| gres_js->cpus_per_gres = cnt; | |
| in_val = NULL; | |
| gres_js->ntasks_per_gres = | |
| *gres_js_val->ntasks_per_tres; | |
| /* | |
| * In theory MAX(cpus_per_gres) shouldn't matter because | |
| * we should only allow one gres name to have | |
| * cpus_per_gres and it should be the same for all types | |
| * (e.g., gpu:k80 vs gpu:tesla) of that same gres (gpu) | |
| */ | |
| job_validate.cpus_per_gres = | |
| MAX(job_validate.cpus_per_gres, cnt); | |
| } | |
| } | |
| if (tres_per_job) { | |
| char *in_val = tres_per_job, *save_ptr = NULL; | |
| while ((gres_state_job = _get_next_job_gres(in_val, &cnt, | |
| *gres_js_val-> | |
| gres_list, | |
| &save_ptr, &rc))) { | |
| if (!requested_gpu && | |
| (!xstrcmp(gres_state_job->gres_name, "gpu"))) | |
| requested_gpu = true; | |
| gres_js = gres_state_job->gres_data; | |
| gres_js->gres_per_job = cnt; | |
| in_val = NULL; | |
| gres_js->total_gres = | |
| MAX(gres_js->total_gres, cnt); | |
| gres_js->ntasks_per_gres = | |
| *gres_js_val->ntasks_per_tres; | |
| } | |
| } | |
| if (tres_per_node) { | |
| char *in_val = tres_per_node, *save_ptr = NULL; | |
| while ((gres_state_job = _get_next_job_gres(in_val, &cnt, | |
| *gres_js_val-> | |
| gres_list, | |
| &save_ptr, &rc))) { | |
| if (!requested_gpu && | |
| (!xstrcmp(gres_state_job->gres_name, "gpu"))) | |
| requested_gpu = true; | |
| gres_js = gres_state_job->gres_data; | |
| gres_js->gres_per_node = cnt; | |
| in_val = NULL; | |
| if (*gres_js_val->min_nodes != NO_VAL) | |
| cnt *= *gres_js_val->min_nodes; | |
| gres_js->total_gres = | |
| MAX(gres_js->total_gres, cnt); | |
| gres_js->ntasks_per_gres = | |
| *gres_js_val->ntasks_per_tres; | |
| } | |
| } | |
| if (tres_per_socket) { | |
| char *in_val = tres_per_socket, *save_ptr = NULL; | |
| while ((gres_state_job = _get_next_job_gres(in_val, &cnt, | |
| *gres_js_val-> | |
| gres_list, | |
| &save_ptr, &rc))) { | |
| if (!requested_gpu && | |
| (!xstrcmp(gres_state_job->gres_name, "gpu"))) | |
| requested_gpu = true; | |
| gres_js = gres_state_job->gres_data; | |
| gres_js->gres_per_socket = cnt; | |
| in_val = NULL; | |
| if ((*gres_js_val->min_nodes != NO_VAL) && | |
| (*gres_js_val->sockets_per_node != NO_VAL16)) { | |
| cnt *= (*gres_js_val->min_nodes * | |
| *gres_js_val->sockets_per_node); | |
| } else if ((*gres_js_val->num_tasks != NO_VAL) && | |
| *gres_js_val->ntasks_per_socket && | |
| (*gres_js_val->ntasks_per_socket != | |
| NO_VAL16)) { | |
| cnt *= ROUNDUP(*gres_js_val->num_tasks, | |
| *gres_js_val->ntasks_per_socket); | |
| } else if (*gres_js_val->sockets_per_node != NO_VAL16) { | |
| /* default 1 node */ | |
| cnt *= *gres_js_val->sockets_per_node; | |
| } | |
| gres_js->total_gres = | |
| MAX(gres_js->total_gres, cnt); | |
| gres_js->ntasks_per_gres = | |
| *gres_js_val->ntasks_per_tres; | |
| } | |
| } | |
| if (tres_per_task) { | |
| char *in_val = tres_per_task, *save_ptr = NULL; | |
| while ((gres_state_job = _get_next_job_gres(in_val, &cnt, | |
| *gres_js_val-> | |
| gres_list, | |
| &save_ptr, &rc))) { | |
| if (!requested_gpu && | |
| (!xstrcmp(gres_state_job->gres_name, "gpu"))) | |
| requested_gpu = true; | |
| gres_js = gres_state_job->gres_data; | |
| gres_js->gres_per_task = cnt; | |
| in_val = NULL; | |
| if (*gres_js_val->num_tasks != NO_VAL) | |
| cnt *= *gres_js_val->num_tasks; | |
| gres_js->total_gres = | |
| MAX(gres_js->total_gres, cnt); | |
| gres_js->ntasks_per_gres = | |
| *gres_js_val->ntasks_per_tres; | |
| } | |
| } | |
| if (mem_per_tres) { | |
| char *in_val = mem_per_tres, *save_ptr = NULL; | |
| while ((gres_state_job = _get_next_job_gres(in_val, &cnt, | |
| *gres_js_val-> | |
| gres_list, | |
| &save_ptr, &rc))) { | |
| gres_js = gres_state_job->gres_data; | |
| gres_js->mem_per_gres = cnt; | |
| in_val = NULL; | |
| gres_js->ntasks_per_gres = | |
| *gres_js_val->ntasks_per_tres; | |
| } | |
| } | |
| /* | |
| * *gres_js_val->num_tasks and *gres_js_val->ntasks_per_tres could be 0 | |
| * on requeue | |
| */ | |
| if (!gres_js_val->ntasks_per_tres || | |
| !*gres_js_val->ntasks_per_tres || | |
| (*gres_js_val->ntasks_per_tres == NO_VAL16)) { | |
| /* do nothing */ | |
| } else if (requested_gpu && list_count(*gres_js_val->gres_list)) { | |
| /* Set num_tasks = gpus * ntasks/gpu */ | |
| uint64_t gpus = _get_job_gres_list_cnt( | |
| *gres_js_val->gres_list, "gpu", NULL); | |
| if (gpus != NO_VAL64) | |
| *gres_js_val->num_tasks = | |
| gpus * *gres_js_val->ntasks_per_tres; | |
| else { | |
| error("%s: Can't set num_tasks = gpus * *ntasks_per_tres because there are no allocated GPUs", | |
| __func__); | |
| rc = ESLURM_INVALID_GRES; | |
| } | |
| } else if (*gres_js_val->num_tasks && | |
| (*gres_js_val->num_tasks != NO_VAL)) { | |
| /* | |
| * If job_gres_list empty, and ntasks_per_tres is specified, | |
| * then derive GPUs according to how many tasks there are. | |
| * GPU GRES = [ntasks / (ntasks_per_tres)] | |
| * For now, only generate type-less GPUs. | |
| */ | |
| uint32_t gpus = *gres_js_val->num_tasks / | |
| *gres_js_val->ntasks_per_tres; | |
| char *save_ptr = NULL, *gres = NULL, *in_val; | |
| xstrfmtcat(gres, "gres/gpu:%u", gpus); | |
| in_val = gres; | |
| while ((gres_state_job = _get_next_job_gres(in_val, &cnt, | |
| *gres_js_val-> | |
| gres_list, | |
| &save_ptr, &rc))) { | |
| gres_js = gres_state_job->gres_data; | |
| gres_js->ntasks_per_gres = | |
| *gres_js_val->ntasks_per_tres; | |
| /* Simulate a tres_per_job specification */ | |
| gres_js->gres_per_job = cnt; | |
| gres_js->total_gres = | |
| MAX(gres_js->total_gres, cnt); | |
| in_val = NULL; | |
| } | |
| if (list_count(*gres_js_val->gres_list) == 0) | |
| error("%s: Failed to add generated GRES %s (via ntasks_per_tres) to gres_list", | |
| __func__, gres); | |
| else | |
| requested_gpu = true; | |
| xfree(gres); | |
| } else { | |
| error("%s: --ntasks-per-tres needs either a GRES GPU specification or a node/ntask specification", | |
| __func__); | |
| rc = ESLURM_INVALID_GRES; | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| if (rc != SLURM_SUCCESS) | |
| return rc; | |
| size = list_count(*gres_js_val->gres_list); | |
| if (size == 0) { | |
| FREE_NULL_LIST(*gres_js_val->gres_list); | |
| return rc; | |
| } | |
| /* | |
| * If someone requested [mem|cpus]_per_tres but didn't request any GPUs | |
| * (even if --exclusive was used), then error. For now we only test for | |
| * GPUs since --[mem|cpus]-per-gpu are the only allowed | |
| * [mem|cpus]_per_gres options. Even though --exclusive means that you | |
| * will be allocated all of the GRES on the node, we still require that | |
| * GPUs are explicitly requested when --[mem|cpus]-per-gpu is used. | |
| */ | |
| if (mem_per_tres && (!requested_gpu)) { | |
| error("Requested mem_per_tres=%s but did not request any GPU.", | |
| mem_per_tres); | |
| return ESLURM_INVALID_GRES; | |
| } | |
| if (cpus_per_tres && (!requested_gpu)) { | |
| error("Requested cpus_per_tres=%s but did not request any GPU.", | |
| cpus_per_tres); | |
| return ESLURM_INVALID_GRES; | |
| } | |
| /* | |
| * Check for record overlap (e.g. "gpu:2,gpu:tesla:1") | |
| * Ensure tres_per_job >= tres_per_node >= tres_per_socket | |
| */ | |
| job_validate.over_array = xcalloc(size, sizeof(overlap_check_t)); | |
| (void) list_for_each(*gres_js_val->gres_list, | |
| _foreach_job_state_validate, | |
| &job_validate); | |
| if (job_validate.tmp_min_cpus > *gres_js_val->min_cpus) | |
| *gres_js_val->min_cpus = job_validate.tmp_min_cpus; | |
| if (((*gres_js_val->cpus_per_task) != NO_VAL16) && | |
| ((*gres_js_val->num_tasks) != NO_VAL)) { | |
| cnt = (*gres_js_val->cpus_per_task) * (*gres_js_val->num_tasks); | |
| if (*gres_js_val->min_cpus < cnt) | |
| *gres_js_val->min_cpus = cnt; | |
| } | |
| if (job_validate.have_gres_shared && | |
| (job_validate.rc == SLURM_SUCCESS) && | |
| tres_freq && | |
| strstr(tres_freq, "gpu")) { | |
| job_validate.rc = ESLURM_INVALID_GRES; | |
| } | |
| if (job_validate.overlap_merge) /* Merge generic data if possible */ | |
| job_validate.rc = _merge_generic_data(*gres_js_val->gres_list, | |
| &job_validate); | |
| xfree(job_validate.over_array); | |
| return job_validate.rc; | |
| } | |
| static int _find_gres_per_jst(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_job = x; | |
| gres_job_state_t *gres_js = gres_state_job->gres_data; | |
| if (gres_js->gres_per_job || | |
| gres_js->gres_per_socket || | |
| gres_js->gres_per_task) | |
| return 1; | |
| return 0; | |
| } | |
| /* | |
| * Determine if a job's specified GRES can be supported. This is designed to | |
| * prevent the running of a job using the GRES options only supported by the | |
| * select/cons_tres plugin when switching (on slurmctld restart) from the | |
| * cons_tres plugin to any other select plugin. | |
| * | |
| * IN gres_list - List of GRES records for this job to track usage | |
| * RET SLURM_SUCCESS or ESLURM_INVALID_GRES | |
| */ | |
| extern int gres_job_revalidate(list_t *gres_list) | |
| { | |
| if (!gres_list || running_cons_tres()) | |
| return SLURM_SUCCESS; | |
| if (list_find_first(gres_list, _find_gres_per_jst, NULL)) | |
| return ESLURM_UNSUPPORTED_GRES; | |
| return SLURM_SUCCESS; | |
| } | |
| /* | |
| * Return TRUE if any of this job's GRES has a populated gres_bit_alloc element. | |
| * This indicates the allocated GRES has a File configuration parameter and is | |
| * tracking individual file assignments. | |
| */ | |
| static int _find_job_has_gres_bits(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_job = x; | |
| gres_job_state_t *gres_js = gres_state_job->gres_data;; | |
| for (int i = 0; i < gres_js->node_cnt; i++) { | |
| if (gres_js->gres_bit_alloc && gres_js->gres_bit_alloc[i]) | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| static int _find_invalid_job_gres_on_node(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_job = x; | |
| gres_job_state_t *gres_js = gres_state_job->gres_data; | |
| validate_job_gres_cnt_t *validate_job_gres_cnt = arg; | |
| gres_state_t *gres_state_node; | |
| gres_node_state_t *gres_ns; | |
| uint32_t plugin_id; | |
| int job_gres_cnt, node_gres_cnt = 0; | |
| if (!gres_js || | |
| !gres_js->gres_bit_alloc || | |
| (gres_js->node_cnt <= validate_job_gres_cnt->node_inx) || | |
| !gres_js->gres_bit_alloc[validate_job_gres_cnt->node_inx]) | |
| return 0; | |
| job_gres_cnt = bit_size( | |
| gres_js->gres_bit_alloc[validate_job_gres_cnt->node_inx]); | |
| if (gres_id_shared(gres_state_job->config_flags)) | |
| plugin_id = gpu_plugin_id; | |
| else | |
| plugin_id = gres_state_job->plugin_id; | |
| if ((gres_state_node = list_find_first(validate_job_gres_cnt-> | |
| node_gres_list, | |
| gres_find_id, | |
| &plugin_id))) { | |
| gres_ns = gres_state_node->gres_data; | |
| node_gres_cnt = (int) gres_ns->gres_cnt_config; | |
| if (gres_js->type_id) { | |
| bool found_type = false; | |
| for (int i = 0; i < gres_ns->type_cnt; i++) { | |
| if (gres_ns->type_id[i] == gres_js->type_id) { | |
| found_type = true; | |
| break; | |
| } | |
| } | |
| if (!found_type) { | |
| error("%s: Killing job %u: gres/%s type %s not found on node %s", | |
| __func__, | |
| validate_job_gres_cnt->job_id, | |
| gres_state_job->gres_name, | |
| gres_js->type_name, | |
| validate_job_gres_cnt->node_name); | |
| return 1; | |
| } | |
| } | |
| } | |
| if (job_gres_cnt != node_gres_cnt) { | |
| error("%s: Killing job %u: gres/%s count mismatch on node " | |
| "%s (%d != %d)", | |
| __func__, validate_job_gres_cnt->job_id, | |
| gres_state_job->gres_name, | |
| validate_job_gres_cnt-> node_name, | |
| job_gres_cnt, node_gres_cnt); | |
| return 1; | |
| } | |
| /* | |
| * Shared GRES bitmaps (e.g. shard and mps) are sized by the backing GPU | |
| * count. That check alone is insufficient when the shared GRES topology | |
| * is removed while GPUs remain: require the shared topology itself | |
| * still exist and cover the job's bits. | |
| */ | |
| if (gres_id_shared(gres_state_job->config_flags)) { | |
| uint64_t old_avail_cnt = 0; | |
| bool kill_job = false; | |
| gres_ns = NULL; | |
| /* Get the shared gres state */ | |
| gres_state_node = | |
| list_find_first(validate_job_gres_cnt->node_gres_list, | |
| gres_find_id, | |
| &gres_state_job->plugin_id); | |
| if (gres_state_node) | |
| gres_ns = gres_state_node->gres_data; | |
| /* | |
| * If the shared gres state no longer exist or the topology does | |
| * not match what is expected then kill the job. | |
| */ | |
| if (!gres_ns || (gres_ns->topo_cnt != job_gres_cnt) || | |
| !gres_ns->topo_gres_cnt_avail) { | |
| kill_job = true; | |
| } else { | |
| /* topo_* vars wait to update until node registration */ | |
| for (int i = 0; i < gres_ns->topo_cnt; i++) | |
| old_avail_cnt += | |
| gres_ns->topo_gres_cnt_avail[i]; | |
| if (gres_ns->gres_cnt_config < old_avail_cnt) | |
| kill_job = true; | |
| } | |
| if (kill_job) { | |
| error("%s: Killing job %u: gres/%s topology missing or reduced on node %s", | |
| __func__, validate_job_gres_cnt->job_id, | |
| gres_state_job->gres_name, | |
| validate_job_gres_cnt->node_name); | |
| return 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Determine if a job's specified GRES are currently valid. This is designed to | |
| * manage jobs allocated GRES which are either no longer supported or a GRES | |
| * configured with the "File" option in gres.conf where the count has changed, | |
| * in which case we don't know how to map the job's old GRES bitmap onto the | |
| * current GRES bitmaps. | |
| * | |
| * IN job_id - ID of job being validated (used for logging) | |
| * IN job_gres_list - List of GRES records for this job to track usage | |
| * RET SLURM_SUCCESS or ESLURM_INVALID_GRES | |
| */ | |
| extern int gres_job_revalidate2(uint32_t job_id, list_t *job_gres_list, | |
| bitstr_t *node_bitmap) | |
| { | |
| node_record_t *node_ptr; | |
| int rc = SLURM_SUCCESS; | |
| validate_job_gres_cnt_t validate_job_gres_cnt = { | |
| .job_id = job_id, | |
| .node_inx = -1, | |
| }; | |
| if (!job_gres_list || !node_bitmap || | |
| !list_find_first(job_gres_list, _find_job_has_gres_bits, NULL)) | |
| return SLURM_SUCCESS; | |
| for (int i = 0; (node_ptr = next_node_bitmap(node_bitmap, &i)); i++) { | |
| /* If no node_ptr->gres_list we are invalid */ | |
| if (!node_ptr->gres_list) | |
| return ESLURM_INVALID_GRES; | |
| validate_job_gres_cnt.node_inx++; | |
| validate_job_gres_cnt.node_gres_list = node_ptr->gres_list; | |
| validate_job_gres_cnt.node_name = node_ptr->name; | |
| if (list_find_first(job_gres_list, | |
| _find_invalid_job_gres_on_node, | |
| &validate_job_gres_cnt)) | |
| return ESLURM_INVALID_GRES; | |
| } | |
| return rc; | |
| } | |
| /* | |
| * Find a sock_gres_t record in a list by matching the plugin_id and type_id | |
| * from a gres_state_t job record | |
| * IN x - a sock_gres_t record to test | |
| * IN key - the gres_state_t record (from a job) we want to match | |
| * RET 1 on match, otherwise 0 | |
| */ | |
| extern int gres_find_sock_by_job_state(void *x, void *key) | |
| { | |
| sock_gres_t *sock_data = (sock_gres_t *) x; | |
| gres_state_t *job_gres_state = (gres_state_t *) key; | |
| gres_job_state_t *sock_gres_js, *gres_js; | |
| gres_js = (gres_job_state_t *) job_gres_state->gres_data; | |
| sock_gres_js = sock_data->gres_state_job->gres_data; | |
| if ((sock_data->gres_state_job->plugin_id == | |
| job_gres_state->plugin_id) && | |
| (sock_gres_js->type_id == gres_js->type_id)) | |
| return 1; | |
| return 0; | |
| } | |
| /* | |
| * Create a (partial) copy of a job's gres state for job binding | |
| * IN gres_list - List of Gres records for this job to track usage | |
| * RET The copy or NULL on failure | |
| * NOTE: Only job details are copied, NOT the job step details | |
| */ | |
| extern list_t *gres_job_state_list_dup(list_t *gres_list) | |
| { | |
| return gres_job_state_extract(gres_list, -1); | |
| } | |
| /* Copy the per-bit counts of each node that has a bitmap and counts */ | |
| static uint64_t **_dup_per_bit(uint32_t node_cnt, bitstr_t **bit_alloc, | |
| uint64_t **per_bit) | |
| { | |
| uint64_t **new_per_bit; | |
| if (!per_bit || !bit_alloc) | |
| return NULL; | |
| new_per_bit = xcalloc(node_cnt, sizeof(uint64_t *)); | |
| for (int i = 0; i < node_cnt; i++) { | |
| int bit_cnt; | |
| if (!bit_alloc[i] || !per_bit[i]) | |
| continue; | |
| bit_cnt = bit_size(bit_alloc[i]); | |
| new_per_bit[i] = xcalloc(bit_cnt, sizeof(uint64_t)); | |
| memcpy(new_per_bit[i], per_bit[i], bit_cnt * sizeof(uint64_t)); | |
| } | |
| return new_per_bit; | |
| } | |
| static gres_job_state_t *_job_state_dup_common(gres_job_state_t *gres_js) | |
| { | |
| gres_job_state_t *new_gres_js = xmalloc(sizeof(gres_job_state_t)); | |
| new_gres_js->cpus_per_gres = gres_js->cpus_per_gres; | |
| new_gres_js->def_cpus_per_gres = gres_js->def_cpus_per_gres; | |
| new_gres_js->def_mem_per_gres = gres_js->def_mem_per_gres; | |
| new_gres_js->flags = gres_js->flags; | |
| new_gres_js->gres_per_job = gres_js->gres_per_job; | |
| new_gres_js->gres_per_node = gres_js->gres_per_node; | |
| new_gres_js->gres_per_socket = gres_js->gres_per_socket; | |
| new_gres_js->gres_per_task = gres_js->gres_per_task; | |
| new_gres_js->mem_per_gres = gres_js->mem_per_gres; | |
| new_gres_js->ntasks_per_gres = gres_js->ntasks_per_gres; | |
| new_gres_js->node_cnt = gres_js->node_cnt; | |
| new_gres_js->res_array_size = gres_js->res_array_size; | |
| new_gres_js->total_gres = gres_js->total_gres; | |
| new_gres_js->total_node_cnt = gres_js->total_node_cnt; | |
| new_gres_js->type_id = gres_js->type_id; | |
| new_gres_js->type_name = xstrdup(gres_js->type_name); | |
| return new_gres_js; | |
| } | |
| /* Copy gres_job_state_t record for ALL nodes */ | |
| extern void *gres_job_state_dup(gres_job_state_t *gres_js) | |
| { | |
| int i; | |
| gres_job_state_t *new_gres_js; | |
| if (gres_js == NULL) | |
| return NULL; | |
| new_gres_js = _job_state_dup_common(gres_js); | |
| if (gres_js->gres_cnt_node_alloc) { | |
| i = sizeof(uint64_t) * gres_js->node_cnt; | |
| new_gres_js->gres_cnt_node_alloc = xmalloc(i); | |
| memcpy(new_gres_js->gres_cnt_node_alloc, | |
| gres_js->gres_cnt_node_alloc, i); | |
| } | |
| if (gres_js->gres_cnt_step_alloc) { | |
| new_gres_js->gres_cnt_step_alloc = xcalloc( | |
| gres_js->node_cnt, | |
| sizeof(*new_gres_js->gres_cnt_step_alloc)); | |
| memcpy(new_gres_js->gres_cnt_step_alloc, | |
| gres_js->gres_cnt_step_alloc, | |
| (sizeof(*new_gres_js->gres_cnt_step_alloc) * | |
| gres_js->node_cnt)); | |
| } | |
| if (gres_js->gres_bit_alloc) { | |
| new_gres_js->gres_bit_alloc = xcalloc(gres_js->node_cnt, | |
| sizeof(bitstr_t *)); | |
| for (i = 0; i < gres_js->node_cnt; i++) { | |
| if (gres_js->gres_bit_alloc[i] == NULL) | |
| continue; | |
| new_gres_js->gres_bit_alloc[i] = | |
| bit_copy(gres_js->gres_bit_alloc[i]); | |
| } | |
| } | |
| new_gres_js->gres_per_bit_alloc = | |
| _dup_per_bit(gres_js->node_cnt, gres_js->gres_bit_alloc, | |
| gres_js->gres_per_bit_alloc); | |
| if (gres_js->gres_bit_step_alloc) { | |
| new_gres_js->gres_bit_step_alloc = xcalloc(gres_js->node_cnt, | |
| sizeof(bitstr_t *)); | |
| for (i = 0; i < gres_js->node_cnt; i++) { | |
| if (!gres_js->gres_bit_step_alloc[i]) | |
| continue; | |
| new_gres_js->gres_bit_step_alloc[i] = | |
| bit_copy(gres_js->gres_bit_step_alloc[i]); | |
| } | |
| } | |
| new_gres_js->gres_per_bit_step_alloc = | |
| _dup_per_bit(gres_js->node_cnt, gres_js->gres_bit_alloc, | |
| gres_js->gres_per_bit_step_alloc); | |
| if (gres_js->gres_cnt_node_select) { | |
| i = sizeof(uint64_t) * gres_js->total_node_cnt; | |
| new_gres_js->gres_cnt_node_select = xmalloc(i); | |
| memcpy(new_gres_js->gres_cnt_node_select, | |
| gres_js->gres_cnt_node_select, i); | |
| } | |
| if (gres_js->gres_bit_select) { | |
| new_gres_js->gres_bit_select = xcalloc(gres_js->total_node_cnt, | |
| sizeof(bitstr_t *)); | |
| for (i = 0; i < gres_js->total_node_cnt; i++) { | |
| if (gres_js->gres_bit_select[i] == NULL) | |
| continue; | |
| new_gres_js->gres_bit_select[i] = | |
| bit_copy(gres_js->gres_bit_select[i]); | |
| } | |
| } | |
| if (gres_js->gres_per_bit_select && gres_js->gres_bit_select) { | |
| new_gres_js->gres_per_bit_select = | |
| xcalloc(gres_js->total_node_cnt, sizeof(uint64_t *)); | |
| for (i = 0; i < gres_js->total_node_cnt; i++) { | |
| int bit_cnt; | |
| if (!gres_js->gres_bit_select[i]) | |
| continue; | |
| bit_cnt = bit_size(gres_js->gres_bit_select[i]); | |
| new_gres_js->gres_per_bit_select[i] = xcalloc( | |
| bit_cnt, sizeof(uint64_t)); | |
| memcpy(new_gres_js->gres_per_bit_select[i], | |
| gres_js->gres_per_bit_select[i], bit_cnt); | |
| } | |
| } | |
| if (gres_js->res_gpu_cores) { | |
| new_gres_js->res_gpu_cores = xcalloc(gres_js->res_array_size, | |
| sizeof(bitstr_t *)); | |
| for (i = 0; i < gres_js->res_array_size; i++) { | |
| if (gres_js->res_gpu_cores[i] == NULL) | |
| continue; | |
| new_gres_js->res_gpu_cores[i] = | |
| bit_copy(gres_js->res_gpu_cores[i]); | |
| } | |
| } | |
| return new_gres_js; | |
| } | |
| /* Copy gres_job_state_t record for one specific node (stepd) */ | |
| static void *_job_state_dup2(gres_job_state_t *gres_js, int job_node_index) | |
| { | |
| gres_job_state_t *new_gres_js; | |
| if (gres_js == NULL) | |
| return NULL; | |
| new_gres_js = _job_state_dup_common(gres_js); | |
| new_gres_js->total_node_cnt = 1; | |
| new_gres_js->node_cnt = 1; | |
| if (gres_js->gres_cnt_node_alloc) { | |
| new_gres_js->gres_cnt_node_alloc = xmalloc(sizeof(uint64_t)); | |
| new_gres_js->gres_cnt_node_alloc[0] = | |
| gres_js->gres_cnt_node_alloc[job_node_index]; | |
| } | |
| if (gres_js->gres_bit_alloc && | |
| gres_js->gres_bit_alloc[job_node_index]) { | |
| new_gres_js->gres_bit_alloc = xmalloc(sizeof(bitstr_t *)); | |
| new_gres_js->gres_bit_alloc[0] = | |
| bit_copy(gres_js->gres_bit_alloc[job_node_index]); | |
| } | |
| if (gres_js->gres_per_bit_alloc && | |
| gres_js->gres_bit_alloc && | |
| gres_js->gres_bit_alloc[job_node_index]) { | |
| new_gres_js->gres_per_bit_alloc = xmalloc(sizeof(uint64_t *)); | |
| new_gres_js->gres_per_bit_alloc[0] = xcalloc( | |
| bit_size(gres_js->gres_bit_alloc[job_node_index]), | |
| sizeof(uint64_t)); | |
| memcpy(new_gres_js->gres_per_bit_alloc[0], | |
| gres_js->gres_per_bit_alloc[job_node_index], | |
| bit_size(gres_js->gres_bit_alloc[job_node_index]) * | |
| sizeof(uint64_t)); | |
| } | |
| /* | |
| * No reason to do | |
| * | |
| * gres_js->gres_cnt_node_select | |
| * gres_js->gres_bit_select | |
| * | |
| * they are based off the entire cluster this is not needed for the | |
| * stepd. | |
| */ | |
| return new_gres_js; | |
| } | |
| static int _foreach_job_state_extract(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_job = x; | |
| job_state_extract_t *job_state_extract = arg; | |
| gres_state_t *new_gres_state; | |
| void *new_gres_data; | |
| if (job_state_extract->job_node_index == -1) | |
| new_gres_data = gres_job_state_dup( | |
| gres_state_job->gres_data); | |
| else | |
| new_gres_data = _job_state_dup2( | |
| gres_state_job->gres_data, | |
| job_state_extract->job_node_index); | |
| if (!new_gres_data) | |
| return -1; | |
| if (!job_state_extract->new_list) | |
| job_state_extract->new_list = list_create(gres_job_list_delete); | |
| new_gres_state = gres_create_state( | |
| gres_state_job, GRES_STATE_SRC_STATE_PTR, | |
| GRES_STATE_TYPE_JOB, new_gres_data); | |
| list_append(job_state_extract->new_list, new_gres_state); | |
| return 0; | |
| } | |
| /* | |
| * Create a (partial) copy of a job's gres state for a particular node index | |
| * IN gres_list - List of Gres records for this job to track usage | |
| * IN job_node_index - zero-origin index to the node | |
| * RET The copy or NULL on failure | |
| */ | |
| extern list_t *gres_job_state_extract(list_t *gres_list, int job_node_index) | |
| { | |
| job_state_extract_t job_state_extract = { | |
| .job_node_index = job_node_index, | |
| }; | |
| if (gres_list) | |
| (void) list_for_each(gres_list, | |
| _foreach_job_state_extract, | |
| &job_state_extract); | |
| return job_state_extract.new_list; | |
| } | |
| /* | |
| * Pack a job's current gres status, called from slurmctld for save/restore | |
| * IN gres_list - generated by gres_job_config_validate() | |
| * IN/OUT buffer - location to write state to | |
| * IN job_id - job's ID | |
| * IN details - if set then pack job step allocation details (only needed to | |
| * save/restore job state, not needed in job credential for | |
| * slurmd task binding) | |
| * | |
| * NOTE: A job's allocation to steps is not recorded here, but recovered with | |
| * the job step state information upon slurmctld restart. | |
| */ | |
| extern int gres_job_state_pack(list_t *gres_list, buf_t *buffer, | |
| uint32_t job_id, bool details, | |
| uint16_t protocol_version) | |
| { | |
| pack_state_t pack_state = { | |
| .buffer = buffer, | |
| .details = details, | |
| .magic = GRES_MAGIC, | |
| .protocol_version = protocol_version, | |
| }; | |
| return _pack_state(gres_list, &pack_state, _foreach_job_state_pack); | |
| } | |
| /* | |
| * Unpack a job's current gres status, called from slurmctld for save/restore | |
| * OUT gres_list - restored state stored by gres_job_state_pack() | |
| * IN/OUT buffer - location to read state from | |
| * IN job_id - job's ID | |
| */ | |
| extern int gres_job_state_unpack(list_t **gres_list, buf_t *buffer, | |
| uint32_t job_id, | |
| uint16_t protocol_version) | |
| { | |
| int i = 0, rc = SLURM_SUCCESS; | |
| uint32_t magic = 0, plugin_id = 0, utmp32 = 0; | |
| uint16_t rec_cnt = 0; | |
| uint8_t has_more = 0; | |
| gres_state_t *gres_state_job; | |
| gres_job_state_t *gres_js = NULL; | |
| bool locked = false; | |
| safe_unpack16(&rec_cnt, buffer); | |
| if (rec_cnt == 0) | |
| return SLURM_SUCCESS; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| locked = true; | |
| if ((gres_context_cnt > 0) && (*gres_list == NULL)) { | |
| *gres_list = list_create(gres_job_list_delete); | |
| } | |
| while ((rc == SLURM_SUCCESS) && (rec_cnt)) { | |
| slurm_gres_context_t *gres_ctx; | |
| if ((buffer == NULL) || (remaining_buf(buffer) == 0)) | |
| break; | |
| rec_cnt--; | |
| if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) { | |
| safe_unpack32(&magic, buffer); | |
| if (magic != GRES_MAGIC) | |
| goto unpack_error; | |
| safe_unpack32(&plugin_id, buffer); | |
| gres_js = xmalloc(sizeof(gres_job_state_t)); | |
| safe_unpack16(&gres_js->cpus_per_gres, buffer); | |
| safe_unpack16(&gres_js->flags, buffer); | |
| safe_unpack64(&gres_js->gres_per_job, buffer); | |
| safe_unpack64(&gres_js->gres_per_node, buffer); | |
| safe_unpack64(&gres_js->gres_per_socket, buffer); | |
| safe_unpack64(&gres_js->gres_per_task, buffer); | |
| safe_unpack64(&gres_js->mem_per_gres, buffer); | |
| safe_unpack16(&gres_js->ntasks_per_gres, buffer); | |
| safe_unpack64(&gres_js->total_gres, buffer); | |
| safe_unpackstr(&gres_js->type_name, buffer); | |
| gres_js->type_id = | |
| gres_build_id(gres_js->type_name); | |
| safe_unpack32(&gres_js->node_cnt, buffer); | |
| if (gres_js->node_cnt > NO_VAL) | |
| goto unpack_error; | |
| safe_unpack8(&has_more, buffer); | |
| if (has_more) { | |
| safe_unpack64_array( | |
| &gres_js->gres_cnt_node_alloc, | |
| &utmp32, buffer); | |
| } | |
| safe_unpack8(&has_more, buffer); | |
| if (has_more) { | |
| safe_xcalloc(gres_js->gres_bit_alloc, | |
| gres_js->node_cnt, | |
| sizeof(bitstr_t *)); | |
| for (i = 0; i < gres_js->node_cnt; i++) { | |
| unpack_bit_str_hex(&gres_js-> | |
| gres_bit_alloc[i], | |
| buffer); | |
| } | |
| } | |
| for (i = 0; i < gres_js->node_cnt; i++) { | |
| safe_unpack8(&has_more, buffer); | |
| if (!has_more) | |
| continue; | |
| if (!gres_js->gres_per_bit_alloc) | |
| safe_xcalloc( | |
| gres_js->gres_per_bit_alloc, | |
| gres_js->node_cnt, | |
| sizeof(uint64_t *)); | |
| safe_unpack64_array( | |
| &gres_js->gres_per_bit_alloc[i], | |
| &utmp32, buffer); | |
| } | |
| safe_unpack8(&has_more, buffer); | |
| if (has_more) { | |
| safe_xcalloc(gres_js->gres_bit_step_alloc, | |
| gres_js->node_cnt, | |
| sizeof(bitstr_t *)); | |
| for (i = 0; i < gres_js->node_cnt; i++) { | |
| unpack_bit_str_hex(&gres_js-> | |
| gres_bit_step_alloc[i], | |
| buffer); | |
| } | |
| } | |
| safe_unpack8(&has_more, buffer); | |
| if (has_more) { | |
| safe_xcalloc(gres_js->gres_cnt_step_alloc, | |
| gres_js->node_cnt, | |
| sizeof(uint64_t)); | |
| for (i = 0; i < gres_js->node_cnt; i++) { | |
| safe_unpack64(&gres_js-> | |
| gres_cnt_step_alloc[i], | |
| buffer); | |
| } | |
| } | |
| for (i = 0; i < gres_js->node_cnt; i++) { | |
| safe_unpack8(&has_more, buffer); | |
| if (!has_more) | |
| continue; | |
| if (!gres_js->gres_per_bit_step_alloc) | |
| safe_xcalloc( | |
| gres_js->gres_per_bit_step_alloc, | |
| gres_js->node_cnt, | |
| sizeof(uint64_t *)); | |
| safe_unpack64_array( | |
| &gres_js->gres_per_bit_step_alloc[i], | |
| &utmp32, buffer); | |
| } | |
| } else { | |
| error("%s: protocol_version %hu not supported", | |
| __func__, protocol_version); | |
| goto unpack_error; | |
| } | |
| if (!(gres_ctx = _find_context_by_id(plugin_id))) { | |
| /* | |
| * A likely sign that GresPlugins has changed. | |
| * Not a fatal error, skip over the data. | |
| */ | |
| error("%s: no plugin configured to unpack data type %u from job %u. This is likely due to a difference in the GresTypes configured in slurm.conf on different cluster nodes.", | |
| __func__, plugin_id, job_id); | |
| gres_job_state_delete(gres_js); | |
| continue; | |
| } | |
| gres_state_job = gres_create_state( | |
| gres_ctx, GRES_STATE_SRC_CONTEXT_PTR, | |
| GRES_STATE_TYPE_JOB, gres_js); | |
| gres_js = NULL; /* nothing left to free on error */ | |
| list_append(*gres_list, gres_state_job); | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return rc; | |
| unpack_error: | |
| error("%s: unpack error from job %u", __func__, job_id); | |
| if (gres_js) | |
| gres_job_state_delete(gres_js); | |
| if (locked) | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return SLURM_ERROR; | |
| } | |
| extern void gres_prep_pack(void *in, uint16_t protocol_version, buf_t *buffer) | |
| { | |
| uint32_t magic = GRES_MAGIC; | |
| gres_prep_t *gres_prep = in; | |
| if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) { | |
| pack32(magic, buffer); | |
| pack32(gres_prep->plugin_id, buffer); | |
| pack32(gres_prep->node_cnt, buffer); | |
| if (gres_prep->gres_cnt_node_alloc) { | |
| pack8((uint8_t) 1, buffer); | |
| pack64_array(gres_prep->gres_cnt_node_alloc, | |
| gres_prep->node_cnt, buffer); | |
| } else { | |
| pack8((uint8_t) 0, buffer); | |
| } | |
| if (gres_prep->gres_bit_alloc) { | |
| pack8((uint8_t) 1, buffer); | |
| for (int i = 0; i < gres_prep->node_cnt; i++) { | |
| pack_bit_str_hex(gres_prep-> | |
| gres_bit_alloc[i], | |
| buffer); | |
| } | |
| } else { | |
| pack8((uint8_t) 0, buffer); | |
| } | |
| } else { | |
| error("%s: protocol_version %hu not supported", | |
| __func__, protocol_version); | |
| } | |
| } | |
| static void _prep_list_del(void *x) | |
| { | |
| gres_prep_t *gres_prep = (gres_prep_t *) x; | |
| int i; | |
| if (!gres_prep) | |
| return; | |
| if (gres_prep->gres_bit_alloc) { | |
| for (i = 0; i < gres_prep->node_cnt; i++) | |
| FREE_NULL_BITMAP(gres_prep->gres_bit_alloc[i]); | |
| xfree(gres_prep->gres_bit_alloc); | |
| } | |
| xfree(gres_prep->gres_cnt_node_alloc); | |
| xfree(gres_prep); | |
| } | |
| static int _gres_prep_unpack(void **object, uint16_t protocol_version, | |
| buf_t *buffer) | |
| { | |
| uint32_t magic = 0, utmp32 = 0; | |
| uint8_t filled = 0; | |
| gres_prep_t *gres_prep = NULL; | |
| gres_prep = xmalloc(sizeof(gres_prep_t)); | |
| if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) { | |
| safe_unpack32(&magic, buffer); | |
| if (magic != GRES_MAGIC) | |
| goto unpack_error; | |
| safe_unpack32(&gres_prep->plugin_id, buffer); | |
| safe_unpack32(&gres_prep->node_cnt, buffer); | |
| if (gres_prep->node_cnt > NO_VAL) | |
| goto unpack_error; | |
| safe_unpack8(&filled, buffer); | |
| if (filled) { | |
| safe_unpack64_array( | |
| &gres_prep->gres_cnt_node_alloc, | |
| &utmp32, buffer); | |
| } | |
| safe_unpack8(&filled, buffer); | |
| if (filled) { | |
| safe_xcalloc(gres_prep->gres_bit_alloc, | |
| gres_prep->node_cnt, | |
| sizeof(bitstr_t *)); | |
| for (int i = 0; i < gres_prep->node_cnt; i++) { | |
| unpack_bit_str_hex(&gres_prep-> | |
| gres_bit_alloc[i], | |
| buffer); | |
| } | |
| } | |
| } else { | |
| error("%s: protocol_version %hu not supported", | |
| __func__, protocol_version); | |
| goto unpack_error; | |
| } | |
| if (!_find_context_by_id(gres_prep->plugin_id)) { | |
| /* | |
| * A likely sign that GresPlugins has changed. | |
| * Not a fatal error, skip over the data. | |
| */ | |
| error("%s: no plugin configured to unpack data type %u", | |
| __func__, gres_prep->plugin_id); | |
| _prep_list_del(gres_prep); | |
| gres_prep = NULL; | |
| /* Don't return SLURM_ERROR */ | |
| } | |
| *object = gres_prep; | |
| return SLURM_SUCCESS; | |
| unpack_error: | |
| error("%s: unpack error", __func__); | |
| _prep_list_del(gres_prep); | |
| return SLURM_ERROR; | |
| } | |
| extern int gres_prep_unpack_list(list_t **out, buf_t *buffer, | |
| uint16_t protocol_version) | |
| { | |
| int rc = SLURM_SUCCESS; | |
| /* We have to have gres_context_lock locked to call the unpack */ | |
| slurm_mutex_lock(&gres_context_lock); | |
| if ((rc = slurm_unpack_list(out, _gres_prep_unpack, _prep_list_del, | |
| buffer, protocol_version)) != SLURM_SUCCESS) | |
| FREE_NULL_LIST(*out); | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return rc; | |
| } | |
| /* True when saved slot i is empty. Indexes past the UUID map are present. */ | |
| static bool _dev_uuid_absent(char **dev_uuid, uint32_t dev_uuid_cnt, int i) | |
| { | |
| if (!dev_uuid || (i < 0) || (i >= (int) dev_uuid_cnt)) | |
| return false; | |
| return !dev_uuid[i] || !dev_uuid[i][0]; | |
| } | |
| static bool _dev_uuid_has_hole(char **dev_uuid, uint32_t dev_uuid_cnt, int n) | |
| { | |
| int lim = n; | |
| if ((int) dev_uuid_cnt < lim) | |
| lim = (int) dev_uuid_cnt; | |
| for (int i = 0; i < lim; i++) { | |
| if (_dev_uuid_absent(dev_uuid, dev_uuid_cnt, i)) | |
| return true; | |
| } | |
| return false; | |
| } | |
| /* | |
| * Move set bits from controller indexes to the indexes slurmd has. | |
| * slurmd's device i is the i-th non-NULL dev_uuid. The bitmap keeps its | |
| * length, because a slurmd index is never past the controller index it | |
| * came from. A granted empty slot is left out. Per-bit counts move with | |
| * their bit. | |
| */ | |
| static void _remap_bitmap_to_slurmd(bitstr_t **bits_ptr, | |
| uint64_t **per_bit_slot, uint64_t *cnt, | |
| char **dev_uuid, uint32_t dev_uuid_cnt, | |
| const char *name, int node_inx) | |
| { | |
| bitstr_t *old, *remapped; | |
| uint64_t *remapped_per = NULL; | |
| int n, slurmd = 0; | |
| bool dropped = false; | |
| if (!bits_ptr || !*bits_ptr || !dev_uuid || !dev_uuid_cnt) | |
| return; | |
| old = *bits_ptr; | |
| n = bit_size(old); | |
| if (!_dev_uuid_has_hole(dev_uuid, dev_uuid_cnt, n)) | |
| return; | |
| remapped = bit_alloc(n); | |
| if (per_bit_slot && *per_bit_slot) | |
| remapped_per = xcalloc(n, sizeof(*remapped_per)); | |
| for (int i = 0; i < n; i++) { | |
| bool absent = _dev_uuid_absent(dev_uuid, dev_uuid_cnt, i); | |
| bool granted = bit_test(old, i); | |
| if (granted && !absent) { | |
| if (slurmd < n) { | |
| bit_set(remapped, slurmd); | |
| if (remapped_per) | |
| remapped_per[slurmd] = | |
| (*per_bit_slot)[i]; | |
| } else { | |
| error("%s: %s node %d slurmd index %d outside bitmap of %d", | |
| __func__, name, node_inx, slurmd, n); | |
| dropped = true; | |
| } | |
| } else if (granted && absent) { | |
| dropped = true; | |
| log_flag(GRES, "%s: leaving absent %s index %d out of the slurmd bitmap on node %d", | |
| __func__, name, i, node_inx); | |
| } | |
| if (!absent) | |
| slurmd++; | |
| } | |
| if (bit_equal(old, remapped)) { | |
| FREE_NULL_BITMAP(remapped); | |
| xfree(remapped_per); | |
| return; | |
| } | |
| if (slurm_conf.debug_flags & DEBUG_FLAG_GRES) { | |
| char *before = bit_fmt_hexmask_trim(old); | |
| char *after = bit_fmt_hexmask_trim(remapped); | |
| log_flag(GRES, "%s: %s node %d %s -> %s", | |
| __func__, name, node_inx, before, after); | |
| xfree(before); | |
| xfree(after); | |
| } | |
| FREE_NULL_BITMAP(*bits_ptr); | |
| *bits_ptr = remapped; | |
| if (remapped_per) { | |
| xfree(*per_bit_slot); | |
| *per_bit_slot = remapped_per; | |
| } | |
| if (dropped && cnt) { | |
| if (remapped_per) { | |
| uint64_t sum = 0; | |
| for (int i = 0; i < n; i++) { | |
| if (bit_test(remapped, i)) | |
| sum += remapped_per[i]; | |
| } | |
| *cnt = sum; | |
| } else { | |
| *cnt = bit_set_count(remapped); | |
| } | |
| } | |
| } | |
| /* | |
| * UUID map for this GRES on the node. Shared GRES bitmaps use the | |
| * sharing GPU's device indexes, and that GPU holds the map. | |
| */ | |
| static char **_node_dev_uuid(node_record_t *node_ptr, uint32_t plugin_id, | |
| uint32_t *dev_uuid_cnt) | |
| { | |
| gres_state_t *gres_state; | |
| gres_node_state_t *gres_ns; | |
| *dev_uuid_cnt = 0; | |
| if (!node_ptr || !node_ptr->gres_list) | |
| return NULL; | |
| gres_state = | |
| list_find_first(node_ptr->gres_list, gres_find_id, &plugin_id); | |
| if (!gres_state || !gres_state->gres_data) | |
| return NULL; | |
| gres_ns = gres_state->gres_data; | |
| if (!gres_ns->dev_uuid && gres_ns->alt_gres && | |
| gres_ns->alt_gres->gres_data) { | |
| gres_node_state_t *alt_ns = gres_ns->alt_gres->gres_data; | |
| if (alt_ns->dev_uuid) | |
| gres_ns = alt_ns; | |
| } | |
| if (!gres_ns->dev_uuid || !gres_ns->dev_uuid_cnt) | |
| return NULL; | |
| *dev_uuid_cnt = gres_ns->dev_uuid_cnt; | |
| return gres_ns->dev_uuid; | |
| } | |
| static int _foreach_use_slurmd_indexes(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state = x; | |
| foreach_slurmd_indexes_t *args = arg; | |
| bitstr_t *node_bitmap = args->node_bitmap; | |
| bitstr_t **bit_alloc = NULL; | |
| uint64_t **per_bit = NULL; | |
| uint64_t *cnt = NULL; | |
| uint32_t node_cnt = 0; | |
| int node_inx = -1; | |
| node_record_t *node_ptr; | |
| if (gres_state->state_type == GRES_STATE_TYPE_JOB) { | |
| gres_job_state_t *gres_js = gres_state->gres_data; | |
| bit_alloc = gres_js->gres_bit_alloc; | |
| per_bit = gres_js->gres_per_bit_alloc; | |
| cnt = gres_js->gres_cnt_node_alloc; | |
| node_cnt = gres_js->node_cnt; | |
| } else if (gres_state->state_type == GRES_STATE_TYPE_STEP) { | |
| gres_step_state_t *gres_ss = gres_state->gres_data; | |
| bit_alloc = gres_ss->gres_bit_alloc; | |
| per_bit = gres_ss->gres_per_bit_alloc; | |
| cnt = gres_ss->gres_cnt_node_alloc; | |
| node_cnt = gres_ss->node_cnt; | |
| } else { | |
| return 0; | |
| } | |
| if (!bit_alloc || !node_cnt) | |
| return 0; | |
| for (int i = 0; (node_ptr = next_node_bitmap(node_bitmap, &i)); i++) { | |
| char **dev_uuid; | |
| uint32_t dev_uuid_cnt = 0; | |
| node_inx++; | |
| if ((uint32_t) node_inx >= node_cnt) | |
| break; | |
| if (!bit_alloc[node_inx]) | |
| continue; | |
| dev_uuid = _node_dev_uuid(node_ptr, gres_state->plugin_id, | |
| &dev_uuid_cnt); | |
| if (!dev_uuid) | |
| continue; | |
| if (args->check_only) { | |
| if (_dev_uuid_has_hole(dev_uuid, dev_uuid_cnt, | |
| bit_size(bit_alloc[node_inx]))) { | |
| args->needed = true; | |
| return -1; | |
| } | |
| continue; | |
| } | |
| _remap_bitmap_to_slurmd(&bit_alloc[node_inx], | |
| per_bit ? &per_bit[node_inx] : NULL, | |
| cnt ? &cnt[node_inx] : NULL, dev_uuid, | |
| dev_uuid_cnt, gres_state->gres_name, | |
| node_inx); | |
| } | |
| return 0; | |
| } | |
| extern bool gres_list_needs_slurmd_indexes(list_t *gres_list, | |
| bitstr_t *node_bitmap) | |
| { | |
| foreach_slurmd_indexes_t args = { | |
| .check_only = true, | |
| .node_bitmap = node_bitmap, | |
| }; | |
| if (!gres_list || !node_bitmap) | |
| return false; | |
| (void) list_for_each_ro(gres_list, _foreach_use_slurmd_indexes, &args); | |
| return args.needed; | |
| } | |
| extern void gres_list_use_slurmd_indexes(list_t *gres_list, | |
| bitstr_t *node_bitmap) | |
| { | |
| foreach_slurmd_indexes_t args = { | |
| .node_bitmap = node_bitmap, | |
| }; | |
| if (!gres_list || !node_bitmap) | |
| return; | |
| (void) list_for_each(gres_list, _foreach_use_slurmd_indexes, &args); | |
| } | |
| static int _foreach_prep_build_env(void *x, void *arg) | |
| { | |
| gres_state_t *gres_ptr = x; | |
| list_t **prep_gres_list = arg; | |
| slurm_gres_context_t *gres_ctx; | |
| gres_prep_t *gres_prep; | |
| if (!(gres_ctx = _find_context_by_id(gres_ptr->plugin_id))) { | |
| error("%s: gres not found in context. This should never happen", | |
| __func__); | |
| return 0; | |
| } | |
| if (!gres_ctx->ops.prep_build_env) /* No plugin to call */ | |
| return 0; | |
| gres_prep = (*(gres_ctx->ops.prep_build_env))(gres_ptr->gres_data); | |
| if (!gres_prep) /* No info to add for this plugin */ | |
| return 0; | |
| if (!*prep_gres_list) | |
| *prep_gres_list = list_create(_prep_list_del); | |
| gres_prep->plugin_id = gres_ctx->plugin_id; | |
| list_append(*prep_gres_list, gres_prep); | |
| return 0; | |
| } | |
| /* | |
| * Build List of information needed to set job's Prolog or Epilog environment | |
| * variables | |
| * | |
| * IN job_gres_list - job's GRES allocation info | |
| * RET information about the job's GRES allocation needed by Prolog or Epilog | |
| */ | |
| extern list_t *gres_g_prep_build_env(list_t *job_gres_list) | |
| { | |
| list_t *prep_gres_list = NULL; | |
| if (!job_gres_list) | |
| return NULL; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| (void) list_for_each(job_gres_list, _foreach_prep_build_env, | |
| &prep_gres_list); | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return prep_gres_list; | |
| } | |
| extern list_t *gres_g_prep_build_env_slurmd(list_t *job_gres_list, | |
| bitstr_t *node_bitmap) | |
| { | |
| list_t *copy, *prep; | |
| if (!job_gres_list) | |
| return NULL; | |
| if (!gres_list_needs_slurmd_indexes(job_gres_list, node_bitmap)) | |
| return gres_g_prep_build_env(job_gres_list); | |
| copy = gres_job_state_list_dup(job_gres_list); | |
| if (!copy) | |
| return gres_g_prep_build_env(job_gres_list); | |
| gres_list_use_slurmd_indexes(copy, node_bitmap); | |
| prep = gres_g_prep_build_env(copy); | |
| FREE_NULL_LIST(copy); | |
| return prep; | |
| } | |
| static int _foreach_prep_set_env(void *x, void *arg) | |
| { | |
| gres_prep_t *gres_prep = x; | |
| foreach_prep_set_env_t *foreach_prep_set_env = arg; | |
| slurm_gres_context_t *gres_ctx; | |
| if (!(gres_ctx = _find_context_by_id(gres_prep->plugin_id))) { | |
| error("%s: GRES ID %u not found in context", | |
| __func__, gres_prep->plugin_id); | |
| return 0; | |
| } | |
| if (!gres_ctx->ops.prep_set_env) /* No plugin to call */ | |
| return 0; | |
| (*(gres_ctx->ops.prep_set_env)) | |
| (foreach_prep_set_env->prep_env_ptr, gres_prep, | |
| foreach_prep_set_env->node_inx); | |
| return 0; | |
| } | |
| /* | |
| * Set environment variables as appropriate for a job's prolog or epilog based | |
| * GRES allocated to the job. | |
| * | |
| * IN/OUT prep_env_ptr - environment variable array | |
| * IN prep_gres_list - generated by TBD | |
| * IN node_inx - zero origin node index | |
| */ | |
| extern void gres_g_prep_set_env(char ***prep_env_ptr, | |
| list_t *prep_gres_list, int node_inx) | |
| { | |
| foreach_prep_set_env_t foreach_prep_set_env = { | |
| .node_inx = node_inx, | |
| .prep_env_ptr = prep_env_ptr, | |
| }; | |
| *prep_env_ptr = NULL; | |
| if (!prep_gres_list) | |
| return; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| (void) list_for_each(prep_gres_list, _foreach_prep_set_env, | |
| &foreach_prep_set_env); | |
| slurm_mutex_unlock(&gres_context_lock); | |
| } | |
| /* | |
| * If core bitmap from slurmd differs in size from that in slurmctld, | |
| * then modify bitmap from slurmd so we can use bit_and, bit_or, etc. | |
| */ | |
| static bitstr_t *_core_bitmap_rebuild(bitstr_t *old_core_bitmap, int new_size) | |
| { | |
| int i, j, old_size, ratio; | |
| bitstr_t *new_core_bitmap; | |
| new_core_bitmap = bit_alloc(new_size); | |
| old_size = bit_size(old_core_bitmap); | |
| if (old_size > new_size) { | |
| ratio = old_size / new_size; | |
| for (i = 0; i < new_size; i++) { | |
| for (j = 0; j < ratio; j++) { | |
| if (bit_test(old_core_bitmap, i*ratio+j)) { | |
| bit_set(new_core_bitmap, i); | |
| break; | |
| } | |
| } | |
| } | |
| } else { | |
| ratio = new_size / old_size; | |
| for (i = 0; i < old_size; i++) { | |
| if (!bit_test(old_core_bitmap, i)) | |
| continue; | |
| for (j = 0; j < ratio; j++) { | |
| bit_set(new_core_bitmap, i*ratio+j); | |
| } | |
| } | |
| } | |
| return new_core_bitmap; | |
| } | |
| extern void gres_validate_node_cores(gres_node_state_t *gres_ns, | |
| int cores_ctld, char *node_name) | |
| { | |
| int i, cores_slurmd; | |
| bitstr_t *new_core_bitmap; | |
| int log_mismatch = true; | |
| if (gres_ns->topo_cnt == 0) | |
| return; | |
| if (gres_ns->topo_core_bitmap == NULL) { | |
| error("Gres topo_core_bitmap is NULL on node %s", node_name); | |
| return; | |
| } | |
| for (i = 0; i < gres_ns->topo_cnt; i++) { | |
| if (!gres_ns->topo_core_bitmap[i]) | |
| continue; | |
| cores_slurmd = bit_size(gres_ns->topo_core_bitmap[i]); | |
| if (cores_slurmd == cores_ctld) | |
| continue; | |
| if (log_mismatch) { | |
| debug("Rebuilding node %s gres core bitmap (%d != %d)", | |
| node_name, cores_slurmd, cores_ctld); | |
| log_mismatch = false; | |
| } | |
| new_core_bitmap = _core_bitmap_rebuild( | |
| gres_ns->topo_core_bitmap[i], | |
| cores_ctld); | |
| FREE_NULL_BITMAP(gres_ns->topo_core_bitmap[i]); | |
| gres_ns->topo_core_bitmap[i] = new_core_bitmap; | |
| } | |
| } | |
| static uint32_t _job_test(gres_state_t *gres_state_job, | |
| gres_state_t *gres_state_node, | |
| bool use_total_gres, | |
| int core_start_bit, int core_end_bit, | |
| uint32_t job_id, char *node_name) | |
| { | |
| gres_job_state_t *gres_js = gres_state_job->gres_data; | |
| gres_node_state_t *gres_ns = gres_state_node->gres_data; | |
| char *gres_name = gres_state_job->gres_name; | |
| int i, j, core_size, core_ctld, top_inx = -1; | |
| uint64_t gres_avail = 0, gres_total, gres_tmp; | |
| uint64_t min_gres_node = 0; | |
| uint32_t *cores_addnt = NULL; /* Additional cores avail from this GRES */ | |
| uint32_t *cores_avail = NULL; /* cores initially avail from this GRES */ | |
| uint32_t core_cnt = 0; | |
| bitstr_t *alloc_core_bitmap = NULL; | |
| bitstr_t *avail_core_bitmap = NULL; | |
| bool use_single_dev = (gres_id_shared(gres_state_job->config_flags) && | |
| !(slurm_conf.select_type_param & | |
| SELECT_MULTIPLE_SHARING_GRES_PJ)); | |
| bool use_busy_dev; | |
| if (gres_ns->no_consume) | |
| use_total_gres = true; | |
| use_busy_dev = gres_use_busy_dev(gres_state_node, use_total_gres); | |
| /* Determine minimum GRES count needed on this node */ | |
| if (gres_js->gres_per_job) | |
| min_gres_node = 1; | |
| min_gres_node = MAX(min_gres_node, gres_js->gres_per_node); | |
| min_gres_node = MAX(min_gres_node, gres_js->gres_per_socket); | |
| min_gres_node = MAX(min_gres_node, gres_js->gres_per_task); | |
| if (min_gres_node && gres_ns->topo_cnt) { | |
| /* Need to determine which specific cores can be used */ | |
| gres_avail = gres_ns->gres_cnt_avail; | |
| if (!use_total_gres) { | |
| gres_avail -= MIN(gres_avail, | |
| gres_node_cnt_drain_unalloc(gres_ns)); | |
| gres_avail -= MIN(gres_avail, gres_ns->gres_cnt_alloc); | |
| } | |
| if (min_gres_node > gres_avail) | |
| return (uint32_t) 0; /* insufficient GRES avail */ | |
| core_ctld = core_end_bit - core_start_bit + 1; | |
| for (i = 0; i < gres_ns->topo_cnt; i++) { | |
| if (!gres_ns->topo_core_bitmap[i]) | |
| continue; | |
| core_ctld = bit_size(gres_ns-> | |
| topo_core_bitmap[i]); | |
| break; | |
| } | |
| alloc_core_bitmap = bit_alloc(core_ctld); | |
| bit_set_all(alloc_core_bitmap); | |
| avail_core_bitmap = bit_copy(alloc_core_bitmap); | |
| cores_addnt = xcalloc(gres_ns->topo_cnt, | |
| sizeof(uint32_t)); | |
| cores_avail = xcalloc(gres_ns->topo_cnt, | |
| sizeof(uint32_t)); | |
| for (i = 0; i < gres_ns->topo_cnt; i++) { | |
| /* | |
| * Use the drain-aware accessor: topo_gres_cnt_avail[] is | |
| * raw (drained devices are not baked in), so read it | |
| * through gres_node_topo_cnt_avail() to exclude drained | |
| * devices from this feasibility count. | |
| */ | |
| uint64_t topo_avail = | |
| gres_node_topo_cnt_avail(gres_ns, i, | |
| use_total_gres); | |
| if (!topo_avail) | |
| continue; | |
| if (use_busy_dev && | |
| (gres_ns->topo_gres_cnt_alloc[i] == 0)) | |
| continue; | |
| if (!use_total_gres && | |
| (gres_ns->topo_gres_cnt_alloc[i] >= topo_avail)) | |
| continue; | |
| if (gres_js->type_name && | |
| (!gres_ns->topo_type_name[i] || | |
| (gres_ns->topo_type_id[i] != | |
| gres_js->type_id))) | |
| continue; | |
| if (!gres_ns->topo_core_bitmap[i]) { | |
| cores_avail[i] = core_end_bit - | |
| core_start_bit + 1; | |
| continue; | |
| } | |
| core_size = bit_size(gres_ns->topo_core_bitmap[i]); | |
| for (j = 0; j < core_size; j++) { | |
| if (bit_test(gres_ns-> | |
| topo_core_bitmap[i], j)) { | |
| cores_avail[i]++; | |
| } | |
| } | |
| } | |
| /* Pick the topology entries with the most cores available */ | |
| gres_avail = 0; | |
| gres_total = 0; | |
| while (gres_avail < min_gres_node) { | |
| top_inx = -1; | |
| for (j = 0; j < gres_ns->topo_cnt; j++) { | |
| if ((gres_avail == 0) || | |
| (cores_avail[j] == 0) || | |
| !gres_ns->topo_core_bitmap[j]) { | |
| cores_addnt[j] = cores_avail[j]; | |
| } else { | |
| cores_addnt[j] = cores_avail[j] - | |
| bit_overlap(alloc_core_bitmap, | |
| gres_ns-> | |
| topo_core_bitmap[j]); | |
| } | |
| if (top_inx == -1) { | |
| if (cores_avail[j]) | |
| top_inx = j; | |
| } else if (cores_addnt[j] > cores_addnt[top_inx]) | |
| top_inx = j; | |
| } | |
| if ((top_inx < 0) || (cores_avail[top_inx] == 0)) { | |
| if (gres_total < min_gres_node) | |
| core_cnt = 0; | |
| break; | |
| } | |
| cores_avail[top_inx] = 0; /* Flag as used */ | |
| gres_tmp = gres_node_topo_cnt_avail(gres_ns, top_inx, | |
| use_total_gres); | |
| if (!use_total_gres && | |
| (gres_tmp >= | |
| gres_ns->topo_gres_cnt_alloc[top_inx])) { | |
| gres_tmp -= gres_ns-> | |
| topo_gres_cnt_alloc[top_inx]; | |
| } else if (!use_total_gres) { | |
| gres_tmp = 0; | |
| } | |
| if (gres_id_shared(gres_state_job->config_flags) && | |
| gres_js->gres_per_task) { | |
| /* | |
| * Remove remaining shared gres_per_task | |
| * Because we don't allocate shared | |
| * gres_per_task across multiple sharing gres. | |
| * See _set_shared_task_bits() in | |
| * gres_select_filter.c | |
| */ | |
| gres_tmp -= (gres_tmp % gres_js->gres_per_task); | |
| } | |
| if (gres_tmp == 0) { | |
| error("gres/%s: topology allocation error on node %s", | |
| gres_name, node_name); | |
| break; | |
| } | |
| /* update counts of allocated cores and GRES */ | |
| if (use_single_dev) { | |
| /* | |
| * Process outside of loop after specific | |
| * device selected | |
| */ | |
| } else if (!gres_ns->topo_core_bitmap[top_inx]) { | |
| bit_set_all(alloc_core_bitmap); | |
| } else if (gres_avail) { | |
| bit_or(alloc_core_bitmap, | |
| gres_ns-> | |
| topo_core_bitmap[top_inx]); | |
| } else { | |
| bit_and(alloc_core_bitmap, | |
| gres_ns-> | |
| topo_core_bitmap[top_inx]); | |
| } | |
| if (use_single_dev) { | |
| gres_total = MAX(gres_total, gres_tmp); | |
| gres_avail = gres_total; | |
| } else { | |
| /* | |
| * Available GRES count is up to gres_tmp, | |
| * but take 1 per loop to maximize available | |
| * core count | |
| */ | |
| gres_avail += 1; | |
| gres_total += gres_tmp; | |
| core_cnt = bit_set_count(alloc_core_bitmap); | |
| } | |
| } | |
| if (use_single_dev && (top_inx >= 0) && | |
| (gres_avail >= min_gres_node)) { | |
| if (!gres_ns->topo_core_bitmap[top_inx]) { | |
| bit_set_all(alloc_core_bitmap); | |
| } else { | |
| bit_or(alloc_core_bitmap, | |
| gres_ns-> | |
| topo_core_bitmap[top_inx]); | |
| } | |
| core_cnt = bit_set_count(alloc_core_bitmap); | |
| } | |
| FREE_NULL_BITMAP(alloc_core_bitmap); | |
| FREE_NULL_BITMAP(avail_core_bitmap); | |
| xfree(cores_addnt); | |
| xfree(cores_avail); | |
| return core_cnt; | |
| } else if (gres_js->type_name) { | |
| for (i = 0; i < gres_ns->type_cnt; i++) { | |
| if (gres_ns->type_name[i] && | |
| (gres_ns->type_id[i] == | |
| gres_js->type_id)) | |
| break; | |
| } | |
| if (i >= gres_ns->type_cnt) | |
| return (uint32_t) 0; /* no such type */ | |
| gres_avail = gres_ns->type_cnt_avail[i]; | |
| if (!use_total_gres) { | |
| gres_avail -= | |
| MIN(gres_avail, | |
| gres_node_type_drain_cnt( | |
| gres_ns, gres_ns->type_id[i], | |
| true)); | |
| gres_avail -= | |
| MIN(gres_avail, gres_ns->type_cnt_alloc[i]); | |
| } | |
| gres_tmp = gres_ns->gres_cnt_avail; | |
| if (!use_total_gres) { | |
| gres_tmp -= MIN(gres_tmp, | |
| gres_node_cnt_drain_unalloc(gres_ns)); | |
| gres_tmp -= MIN(gres_tmp, gres_ns->gres_cnt_alloc); | |
| } | |
| gres_avail = MIN(gres_avail, gres_tmp); | |
| if (min_gres_node > gres_avail) | |
| return (uint32_t) 0; /* insufficient GRES avail */ | |
| return NO_VAL; | |
| } else { | |
| gres_avail = gres_ns->gres_cnt_avail; | |
| if (!use_total_gres) { | |
| gres_avail -= MIN(gres_avail, | |
| gres_node_cnt_drain_unalloc(gres_ns)); | |
| gres_avail -= MIN(gres_avail, gres_ns->gres_cnt_alloc); | |
| } | |
| if (min_gres_node > gres_avail) | |
| return (uint32_t) 0; /* insufficient GRES avail */ | |
| return NO_VAL; | |
| } | |
| } | |
| static int _foreach_job_test(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_job = x; | |
| foreach_job_test_t *foreach_job_test = arg; | |
| uint32_t tmp_cnt; | |
| gres_state_t *gres_state_node = | |
| list_find_first(foreach_job_test->node_gres_list, | |
| gres_find_id, | |
| &gres_state_job->plugin_id); | |
| if (!gres_state_node) { | |
| /* node lack resources required by the job */ | |
| foreach_job_test->core_cnt = 0; | |
| return -1; | |
| } | |
| tmp_cnt = _job_test(gres_state_job, gres_state_node, | |
| foreach_job_test->use_total_gres, | |
| foreach_job_test->core_start_bit, | |
| foreach_job_test->core_end_bit, | |
| foreach_job_test->job_id, | |
| foreach_job_test->node_name); | |
| if (tmp_cnt != NO_VAL) { | |
| if (foreach_job_test->core_cnt == NO_VAL) | |
| foreach_job_test->core_cnt = tmp_cnt; | |
| else | |
| foreach_job_test->core_cnt = | |
| MIN(tmp_cnt, foreach_job_test->core_cnt); | |
| } | |
| if (foreach_job_test->core_cnt == 0) | |
| return -1; | |
| return 0; | |
| } | |
| /* | |
| * Determine how many cores on the node can be used by this job | |
| * IN job_gres_list - job's gres_list built by gres_job_state_validate() | |
| * IN node_gres_list - node's gres_list built by gres_node_config_validate() | |
| * IN use_total_gres - if set then consider all gres resources as available, | |
| * and none are committed to running jobs | |
| * IN core_start_bit - index into core_bitmap for this node's first core | |
| * IN core_end_bit - index into core_bitmap for this node's last core | |
| * IN job_id - job's ID (for logging) | |
| * IN node_name - name of the node (for logging) | |
| * IN disable binding- --gres-flags=disable-binding | |
| * RET: NO_VAL - All cores on node are available | |
| * otherwise - Count of available cores | |
| */ | |
| extern uint32_t gres_job_test(list_t *job_gres_list, list_t *node_gres_list, | |
| bool use_total_gres, | |
| int core_start_bit, int core_end_bit, | |
| uint32_t job_id, char *node_name) | |
| { | |
| foreach_job_test_t foreach_job_test = { | |
| .core_cnt = NO_VAL, | |
| .core_end_bit = core_end_bit, | |
| .core_start_bit = core_start_bit, | |
| .job_id = job_id, | |
| .node_gres_list = node_gres_list, | |
| .node_name = node_name, | |
| .use_total_gres = use_total_gres, | |
| }; | |
| if (job_gres_list == NULL) | |
| return NO_VAL; | |
| if (node_gres_list == NULL) | |
| return 0; | |
| (void) list_for_each(job_gres_list, _foreach_job_test, | |
| &foreach_job_test); | |
| return foreach_job_test.core_cnt; | |
| } | |
| extern void gres_sock_delete(void *x) | |
| { | |
| sock_gres_t *sock_gres = (sock_gres_t *) x; | |
| int s; | |
| if (sock_gres) { | |
| FREE_NULL_BITMAP(sock_gres->bits_any_sock); | |
| if (sock_gres->bits_by_sock) { | |
| for (s = 0; s < sock_gres->sock_cnt; s++) | |
| FREE_NULL_BITMAP(sock_gres->bits_by_sock[s]); | |
| xfree(sock_gres->bits_by_sock); | |
| } | |
| xfree(sock_gres->cnt_by_sock); | |
| FREE_NULL_BITMAP(sock_gres->links_pick); | |
| xfree(sock_gres); | |
| } | |
| } | |
| static int _foreach_sock_str(void *x, void *arg) | |
| { | |
| sock_gres_t *sock_gres = x; | |
| foreach_sock_str_t *foreach_sock_str = arg; | |
| char *gres_name = sock_gres->gres_state_job->gres_name; | |
| gres_job_state_t *gres_js = sock_gres->gres_state_job->gres_data; | |
| char *type_name = gres_js->type_name; | |
| if (foreach_sock_str->sock_inx < 0) { | |
| if (sock_gres->cnt_any_sock) { | |
| if (type_name) { | |
| xstrfmtcat(foreach_sock_str->gres_str, | |
| "%s%s:%s:%"PRIu64, | |
| foreach_sock_str->sep, | |
| gres_name, | |
| type_name, | |
| sock_gres->cnt_any_sock); | |
| } else { | |
| xstrfmtcat(foreach_sock_str->gres_str, | |
| "%s%s:%"PRIu64, | |
| foreach_sock_str->sep, gres_name, | |
| sock_gres->cnt_any_sock); | |
| } | |
| foreach_sock_str->sep = " "; | |
| } | |
| return 0; | |
| } | |
| if (!sock_gres->cnt_by_sock || | |
| (sock_gres->cnt_by_sock[foreach_sock_str->sock_inx] == 0)) | |
| return 0; | |
| if (type_name) { | |
| xstrfmtcat(foreach_sock_str->gres_str, "%s%s:%s:%"PRIu64, | |
| foreach_sock_str->sep, | |
| gres_name, type_name, | |
| sock_gres->cnt_by_sock[foreach_sock_str->sock_inx]); | |
| } else { | |
| xstrfmtcat(foreach_sock_str->gres_str, "%s%s:%"PRIu64, | |
| foreach_sock_str->sep, | |
| gres_name, | |
| sock_gres->cnt_by_sock[foreach_sock_str->sock_inx]); | |
| } | |
| foreach_sock_str->sep = " "; | |
| return 0; | |
| } | |
| /* | |
| * Build a string containing the GRES details for a given node and socket | |
| * sock_gres_list IN - List of sock_gres_t entries | |
| * sock_inx IN - zero-origin socket for which information is to be returned | |
| * if value < 0, then report GRES unconstrained by core | |
| * RET string, must call xfree() to release memory | |
| */ | |
| extern char *gres_sock_str(list_t *sock_gres_list, int sock_inx) | |
| { | |
| foreach_sock_str_t foreach_sock_str = { | |
| .gres_str = NULL, | |
| .sep = "", | |
| .sock_inx = sock_inx, | |
| }; | |
| if (!sock_gres_list) | |
| return NULL; | |
| (void) list_for_each(sock_gres_list, _foreach_sock_str, | |
| &foreach_sock_str); | |
| return foreach_sock_str.gres_str; | |
| } | |
| static void _accumulate_job_gres_alloc(gres_job_state_t *gres_js, int node_inx, | |
| bitstr_t **gres_bit_alloc, | |
| uint64_t *gres_cnt, | |
| uint64_t **gres_per_bit) | |
| { | |
| if (gres_js->node_cnt <= node_inx) { | |
| error("gres_job_state_t node count less than node_inx. This should never happen"); | |
| return; | |
| } | |
| if ((node_inx >= 0) && (node_inx < gres_js->node_cnt) && | |
| gres_js->gres_bit_alloc && | |
| gres_js->gres_bit_alloc[node_inx]) { | |
| if (!*gres_bit_alloc) { | |
| *gres_bit_alloc = bit_alloc( | |
| bit_size(gres_js-> | |
| gres_bit_alloc[node_inx])); | |
| } | |
| bit_or(*gres_bit_alloc, gres_js->gres_bit_alloc[node_inx]); | |
| } | |
| if (gres_cnt && gres_js->gres_cnt_node_alloc) | |
| *gres_cnt += gres_js->gres_cnt_node_alloc[node_inx]; | |
| if (gres_per_bit && gres_js->gres_per_bit_alloc && | |
| gres_js->gres_per_bit_alloc[node_inx] && gres_js->gres_bit_alloc && | |
| gres_js->gres_bit_alloc[node_inx]) { | |
| int bit_cnt = bit_size(gres_js->gres_bit_alloc[node_inx]); | |
| if (!*gres_per_bit) | |
| *gres_per_bit = xcalloc(bit_cnt, sizeof(uint64_t)); | |
| for (int i = 0; i < bit_cnt; i++) { | |
| (*gres_per_bit)[i] += | |
| gres_js->gres_per_bit_alloc[node_inx][i]; | |
| } | |
| } | |
| } | |
| static int _accumulate_gres_device(void *x, void *arg) | |
| { | |
| gres_state_t *gres_ptr = x; | |
| foreach_gres_accumulate_device_t *foreach_gres_accumulate_device = arg; | |
| if (gres_ptr->plugin_id != foreach_gres_accumulate_device->plugin_id) | |
| return 0; | |
| if (foreach_gres_accumulate_device->is_job) { | |
| _accumulate_job_gres_alloc( | |
| gres_ptr->gres_data, | |
| foreach_gres_accumulate_device->node_inx, | |
| foreach_gres_accumulate_device->gres_bit_alloc, | |
| &foreach_gres_accumulate_device->gres_cnt, | |
| foreach_gres_accumulate_device->gres_per_bit); | |
| } else { | |
| _accumulate_step_gres_alloc( | |
| gres_ptr, | |
| foreach_gres_accumulate_device->gres_bit_alloc, | |
| &foreach_gres_accumulate_device->gres_cnt, | |
| foreach_gres_accumulate_device->gres_per_bit); | |
| } | |
| /* Does job have a sharing GRES (GPU)? */ | |
| if (gres_id_sharing(foreach_gres_accumulate_device->plugin_id)) | |
| foreach_gres_accumulate_device->sharing_gres_allocated = true; | |
| return 0; | |
| } | |
| /* | |
| * Set environment variables as required for a batch or interactive step | |
| */ | |
| extern void gres_g_job_set_env(stepd_step_rec_t *step, int node_inx) | |
| { | |
| int i; | |
| gres_internal_flags_t flags = GRES_INTERNAL_FLAG_NONE; | |
| bitstr_t *gres_bit_alloc = NULL; | |
| uint64_t *gres_per_bit = NULL; | |
| foreach_gres_accumulate_device_t foreach_gres_accumulate_device = { | |
| .gres_bit_alloc = &gres_bit_alloc, | |
| .gres_per_bit = &gres_per_bit, | |
| .is_job = true, | |
| .node_inx = node_inx, | |
| }; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| slurm_gres_context_t *gres_ctx = &gres_context[i]; | |
| if (!gres_ctx->ops.job_set_env) | |
| continue; /* No plugin to call */ | |
| if (step->job_gres_list) { | |
| foreach_gres_accumulate_device.plugin_id = | |
| gres_ctx->plugin_id; | |
| (void) list_for_each(step->job_gres_list, | |
| _accumulate_gres_device, | |
| &foreach_gres_accumulate_device); | |
| } | |
| /* | |
| * Do not let MPS or Shard (shared GRES) clear any envs set for | |
| * a GPU (sharing GRES) when a GPU is allocated but an | |
| * MPS/Shard is not. Sharing GRES plugins always run before | |
| * shared GRES, so we don't need to protect MPS/Shard from GPU. | |
| */ | |
| if (gres_id_shared(gres_ctx->config_flags) && | |
| foreach_gres_accumulate_device.sharing_gres_allocated) | |
| flags |= GRES_INTERNAL_FLAG_PROTECT_ENV; | |
| if ((step->flags & LAUNCH_EXT_LAUNCHER)) { | |
| /* | |
| * We need the step environment variables, but still | |
| * use all the job's gres. | |
| */ | |
| (*(gres_ctx->ops.step_set_env))( | |
| &step->env, gres_bit_alloc, | |
| foreach_gres_accumulate_device.gres_cnt, | |
| gres_per_bit, flags); | |
| } else | |
| (*(gres_ctx->ops | |
| .job_set_env))(&step->env, gres_bit_alloc, | |
| foreach_gres_accumulate_device | |
| .gres_cnt, | |
| gres_per_bit, flags); | |
| foreach_gres_accumulate_device.gres_cnt = 0; | |
| xfree(gres_per_bit); | |
| FREE_NULL_BITMAP(gres_bit_alloc); | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| } | |
| static int _job_state_log(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_job = x; | |
| uint32_t job_id = *(uint32_t *)arg; | |
| gres_job_state_t *gres_js = gres_state_job->gres_data; | |
| char *sparse_msg = "", tmp_str[128]; | |
| int i; | |
| xassert(gres_js); | |
| info("gres_job_state gres:%s(%u) type:%s(%u) job:%u flags:%s", | |
| gres_state_job->gres_name, gres_state_job->plugin_id, | |
| gres_js->type_name, | |
| gres_js->type_id, job_id, gres_flags2str(gres_js->flags)); | |
| if (gres_js->cpus_per_gres) | |
| info(" cpus_per_gres:%u", gres_js->cpus_per_gres); | |
| else if (gres_js->def_cpus_per_gres) | |
| info(" def_cpus_per_gres:%u", gres_js->def_cpus_per_gres); | |
| if (gres_js->gres_per_job) | |
| info(" gres_per_job:%"PRIu64, gres_js->gres_per_job); | |
| if (gres_js->gres_per_node) { | |
| info(" gres_per_node:%"PRIu64" node_cnt:%u", | |
| gres_js->gres_per_node, gres_js->node_cnt); | |
| } | |
| if (gres_js->gres_per_socket) | |
| info(" gres_per_socket:%"PRIu64, gres_js->gres_per_socket); | |
| if (gres_js->gres_per_task) | |
| info(" gres_per_task:%"PRIu64, gres_js->gres_per_task); | |
| if (gres_js->mem_per_gres) | |
| info(" mem_per_gres:%"PRIu64, gres_js->mem_per_gres); | |
| else if (gres_js->def_mem_per_gres) | |
| info(" def_mem_per_gres:%"PRIu64, gres_js->def_mem_per_gres); | |
| if (gres_js->ntasks_per_gres) | |
| info(" ntasks_per_gres:%u", gres_js->ntasks_per_gres); | |
| /* | |
| * These arrays are only used for resource selection and may include | |
| * data for many nodes not used in the resources eventually allocated | |
| * to this job. | |
| */ | |
| if (gres_js->total_node_cnt) { | |
| sparse_msg = " (sparsely populated for resource selection)"; | |
| info(" total_node_cnt:%u%s", gres_js->total_node_cnt, | |
| sparse_msg); | |
| } | |
| for (i = 0; i < gres_js->total_node_cnt; i++) { | |
| if (gres_js->gres_cnt_node_select && | |
| gres_js->gres_cnt_node_select[i]) { | |
| info(" gres_cnt_node_select[%d]:%"PRIu64, | |
| i, gres_js->gres_cnt_node_select[i]); | |
| } | |
| if (gres_js->gres_bit_select && | |
| gres_js->gres_bit_select[i]) { | |
| bit_fmt(tmp_str, sizeof(tmp_str), | |
| gres_js->gres_bit_select[i]); | |
| info(" gres_bit_select[%d]:%s of %d", i, tmp_str, | |
| (int) bit_size(gres_js->gres_bit_select[i])); | |
| } | |
| if (gres_js->gres_bit_select && | |
| gres_js->gres_bit_select[i] && | |
| gres_js->gres_per_bit_select && | |
| gres_js->gres_per_bit_select[i]) { | |
| for (int j = 0; | |
| (j = bit_ffs_from_bit(gres_js->gres_bit_select[i], | |
| j)) >= 0; | |
| j++) { | |
| info(" gres_per_bit_select[%d][%d]:%"PRIu64, | |
| i, j, gres_js->gres_per_bit_select[i][j]); | |
| } | |
| } | |
| } | |
| if (gres_js->total_gres) | |
| info(" total_gres:%"PRIu64, gres_js->total_gres); | |
| if (gres_js->node_cnt) | |
| info(" node_cnt:%u", gres_js->node_cnt); | |
| for (i = 0; i < gres_js->node_cnt; i++) { | |
| if (gres_js->gres_cnt_node_alloc && | |
| gres_js->gres_cnt_node_alloc[i]) { | |
| info(" gres_cnt_node_alloc[%d]:%"PRIu64, | |
| i, gres_js->gres_cnt_node_alloc[i]); | |
| } else if (gres_js->gres_cnt_node_alloc) | |
| info(" gres_cnt_node_alloc[%d]:NULL", i); | |
| if (gres_js->gres_bit_alloc && gres_js->gres_bit_alloc[i]) { | |
| bit_fmt(tmp_str, sizeof(tmp_str), | |
| gres_js->gres_bit_alloc[i]); | |
| info(" gres_bit_alloc[%d]:%s of %d", i, tmp_str, | |
| (int) bit_size(gres_js->gres_bit_alloc[i])); | |
| } else if (gres_js->gres_bit_alloc) | |
| info(" gres_bit_alloc[%d]:NULL", i); | |
| if (gres_js->gres_bit_alloc && | |
| gres_js->gres_bit_alloc[i] && | |
| gres_js->gres_per_bit_alloc && | |
| gres_js->gres_per_bit_alloc[i]) { | |
| for (int j = 0; | |
| (j = bit_ffs_from_bit(gres_js->gres_bit_alloc[i], | |
| j)) >= 0; | |
| j++) { | |
| info(" gres_per_bit_alloc[%d][%d]:%"PRIu64, | |
| i, j, gres_js->gres_per_bit_alloc[i][j]); | |
| } | |
| } | |
| if (gres_js->gres_bit_step_alloc && | |
| gres_js->gres_bit_step_alloc[i]) { | |
| bit_fmt(tmp_str, sizeof(tmp_str), | |
| gres_js->gres_bit_step_alloc[i]); | |
| info(" gres_bit_step_alloc[%d]:%s of %d", i, tmp_str, | |
| (int) bit_size(gres_js->gres_bit_step_alloc[i])); | |
| } else if (gres_js->gres_bit_step_alloc) | |
| info(" gres_bit_step_alloc[%d]:NULL", i); | |
| if (gres_js->gres_bit_step_alloc && | |
| gres_js->gres_bit_step_alloc[i] && | |
| gres_js->gres_per_bit_step_alloc && | |
| gres_js->gres_per_bit_step_alloc[i]) { | |
| for (int j = 0; | |
| (j = bit_ffs_from_bit( | |
| gres_js->gres_bit_step_alloc[i], j)) >= 0; | |
| j++) { | |
| info(" gres_per_bit_step_alloc[%d][%d]:%"PRIu64, | |
| i, j, | |
| gres_js->gres_per_bit_step_alloc[i][j]); | |
| } | |
| } | |
| if (gres_js->gres_cnt_step_alloc) { | |
| info(" gres_cnt_step_alloc[%d]:%"PRIu64"", i, | |
| gres_js->gres_cnt_step_alloc[i]); | |
| } | |
| } | |
| return 0; | |
| } | |
| static int _foreach_gres_list_cnt(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_ptr = x; | |
| foreach_gres_list_cnt_t *foreach_gres_list_cnt = arg; | |
| uint64_t total_gres; | |
| void *type_name; | |
| if (gres_state_ptr->plugin_id != foreach_gres_list_cnt->plugin_id) | |
| return 0; | |
| if (foreach_gres_list_cnt->is_job) { | |
| gres_job_state_t *gres_js = gres_state_ptr->gres_data; | |
| type_name = gres_js->type_name; | |
| total_gres = gres_js->total_gres; | |
| } else { | |
| gres_step_state_t *gres_ss = gres_state_ptr->gres_data; | |
| type_name = gres_ss->type_name; | |
| total_gres = gres_ss->total_gres; | |
| } | |
| /* If we are filtering on GRES type, ignore other types */ | |
| if (foreach_gres_list_cnt->filter_type && | |
| xstrcasecmp(foreach_gres_list_cnt->gres_type, type_name)) | |
| return 0; | |
| if ((total_gres == NO_VAL64) || (total_gres == 0)) | |
| return 0; | |
| if (foreach_gres_list_cnt->gres_cnt == NO_VAL64) | |
| foreach_gres_list_cnt->gres_cnt = total_gres; | |
| else | |
| foreach_gres_list_cnt->gres_cnt += total_gres; | |
| return 0; | |
| } | |
| /* | |
| * Extract from the job/step gres_list the count of GRES of the specified name | |
| * and (optionally) type. If no type is specified, then the count will include | |
| * all GRES of that name, regardless of type. | |
| * | |
| * IN gres_list - job/step record's gres_list. | |
| * IN gres_name - the name of the GRES to query. | |
| * IN gres_type - (optional) the type of the GRES to query. | |
| * IN is_job - True if the GRES list is for the job, false if for the step. | |
| * RET The number of GRES in the job/step gres_list or NO_VAL64 if not found. | |
| */ | |
| static uint64_t _get_gres_list_cnt(list_t *gres_list, char *gres_name, | |
| char *gres_type, bool is_job) | |
| { | |
| foreach_gres_list_cnt_t foreach_gres_list_cnt = { | |
| .gres_cnt = NO_VAL64, | |
| .gres_type = gres_type, | |
| .is_job = is_job, | |
| }; | |
| if ((gres_list == NULL) || (list_count(gres_list) == 0)) | |
| return foreach_gres_list_cnt.gres_cnt; | |
| foreach_gres_list_cnt.plugin_id = gres_build_id(gres_name); | |
| if (gres_type && (gres_type[0] != '\0')) | |
| foreach_gres_list_cnt.filter_type = true; | |
| (void) list_for_each(gres_list, _foreach_gres_list_cnt, | |
| &foreach_gres_list_cnt); | |
| return foreach_gres_list_cnt.gres_cnt; | |
| } | |
| static uint64_t _get_job_gres_list_cnt(list_t *gres_list, char *gres_name, | |
| char *gres_type) | |
| { | |
| return _get_gres_list_cnt(gres_list, gres_name, gres_type, true); | |
| } | |
| static uint64_t _get_step_gres_list_cnt(list_t *gres_list, char *gres_name, | |
| char *gres_type) | |
| { | |
| return _get_gres_list_cnt(gres_list, gres_name, gres_type, false); | |
| } | |
| /* | |
| * Log a job's current gres state | |
| * IN gres_list - generated by gres_job_state_validate() | |
| * IN job_id - job's ID | |
| */ | |
| extern void gres_job_state_log(list_t *gres_list, uint32_t job_id) | |
| { | |
| if (!(slurm_conf.debug_flags & DEBUG_FLAG_GRES) || !gres_list) | |
| return; | |
| (void) list_for_each(gres_list, _job_state_log, &job_id); | |
| } | |
| static int _find_device(void *x, void *key) | |
| { | |
| gres_device_t *device_x = (gres_device_t *)x; | |
| gres_device_t *device_key = (gres_device_t *)key; | |
| if (!xstrcmp(device_x->path, device_key->path)) | |
| return 1; | |
| return 0; | |
| } | |
| static int _foreach_init_device_list(void *x, void *arg) | |
| { | |
| gres_device_t *gres_device = x; | |
| list_t **device_list = arg; | |
| if (!*device_list) | |
| *device_list = list_create(NULL); | |
| gres_device->alloc = 0; | |
| /* | |
| * Keep the list unique by not adding duplicates (in the | |
| * case of MPS and GPU) | |
| */ | |
| if (!list_find_first(*device_list, _find_device, gres_device)) | |
| list_append(*device_list, gres_device); | |
| return 0; | |
| } | |
| static int _foreach_alloc_gres_device(void *x, void *arg) | |
| { | |
| gres_device_t *gres_device = x; | |
| foreach_alloc_gres_device_t *foreach_alloc_gres_device = arg; | |
| if (!bit_test(foreach_alloc_gres_device->gres_bit_alloc, | |
| gres_device->index)) | |
| return 0; | |
| if (!foreach_alloc_gres_device->usable_gres || | |
| bit_test(foreach_alloc_gres_device->usable_gres, | |
| gres_device->index)) { | |
| /* | |
| * Search for the device among the unique | |
| * devices list (since two plugins could have | |
| * device records that point to the same file, | |
| * like with GPU and MPS) | |
| */ | |
| gres_device_t *gres_device2 = list_find_first( | |
| foreach_alloc_gres_device->device_list, | |
| _find_device, | |
| gres_device); | |
| /* | |
| * Set both, in case they point to different records | |
| */ | |
| gres_device->alloc = 1; | |
| if (gres_device2) | |
| gres_device2->alloc = 1; | |
| } | |
| return 0; | |
| } | |
| extern list_t *gres_g_get_devices(list_t *gres_list, bool is_job, | |
| uint16_t accel_bind_type, char *tres_bind_str, | |
| int local_proc_id, stepd_step_rec_t *step) | |
| { | |
| int j; | |
| bitstr_t *gres_bit_alloc = NULL; | |
| uint64_t *gres_per_bit = NULL; | |
| list_t *gres_devices; | |
| list_t *device_list = NULL; | |
| bitstr_t *usable_gres = NULL; | |
| xassert(gres_context_cnt >= 0); | |
| /* | |
| * Create a unique device list of all possible GRES device files. | |
| * Initialize each device to deny. | |
| */ | |
| slurm_mutex_lock(&gres_context_lock); | |
| for (j = 0; j < gres_context_cnt; j++) { | |
| if (!gres_context[j].ops.get_devices){ | |
| gres_devices = gres_context[j].np_gres_devices; | |
| } else { | |
| gres_devices = (*(gres_context[j].ops.get_devices))(); | |
| } | |
| if (!gres_devices || !list_count(gres_devices)) | |
| continue; | |
| (void) list_for_each(gres_devices, _foreach_init_device_list, | |
| &device_list); | |
| } | |
| if (!gres_list) { | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return device_list; | |
| } | |
| if (accel_bind_type) | |
| _parse_accel_bind_type(accel_bind_type, tres_bind_str); | |
| for (j = 0; j < gres_context_cnt; j++) { | |
| /* We need to get a gres_bit_alloc with all the gres types | |
| * merged (accumulated) together */ | |
| foreach_gres_accumulate_device_t arg = { | |
| .gres_bit_alloc = &gres_bit_alloc, | |
| .gres_per_bit = &gres_per_bit, | |
| .is_job = is_job, | |
| .plugin_id = gres_context[j].plugin_id, | |
| }; | |
| foreach_alloc_gres_device_t foreach_alloc_gres_device = { | |
| .device_list = device_list, | |
| }; | |
| (void) list_for_each(gres_list, _accumulate_gres_device, &arg); | |
| if (!gres_bit_alloc) | |
| continue; | |
| if (!gres_context[j].ops.get_devices){ | |
| gres_devices = gres_context[j].np_gres_devices; | |
| } else { | |
| gres_devices = (*(gres_context[j].ops.get_devices))(); | |
| } | |
| if (!gres_devices) { | |
| error("We should had got gres_devices, but for some reason none were set in the plugin."); | |
| continue; | |
| } | |
| if (_get_usable_gres(j, local_proc_id, tres_bind_str, | |
| &usable_gres, gres_bit_alloc, true, step, | |
| gres_per_bit, NULL) == SLURM_ERROR) | |
| continue; | |
| foreach_alloc_gres_device.gres_bit_alloc = gres_bit_alloc; | |
| foreach_alloc_gres_device.usable_gres = usable_gres; | |
| (void) list_for_each(gres_devices, _foreach_alloc_gres_device, | |
| &foreach_alloc_gres_device); | |
| xfree(gres_per_bit); | |
| FREE_NULL_BITMAP(gres_bit_alloc); | |
| FREE_NULL_BITMAP(usable_gres); | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return device_list; | |
| } | |
| extern list_t *gres_g_get_dmem_devices(void) | |
| { | |
| list_t *dmem_devs = NULL; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| for (int i = 0; i < gres_context_cnt; i++) { | |
| if (!gres_context[i].ops.get_dmem_devices) | |
| continue; | |
| if ((dmem_devs = (*(gres_context[i].ops.get_dmem_devices))())) | |
| break; | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return dmem_devs; | |
| } | |
| extern uint64_t *gres_get_per_bit_alloc(list_t *gres_list, bool is_job, | |
| uint32_t plugin_id, int *bit_cnt) | |
| { | |
| bitstr_t *gres_bit_alloc = NULL; | |
| uint64_t *gres_per_bit = NULL; | |
| foreach_gres_accumulate_device_t arg = { | |
| .gres_bit_alloc = &gres_bit_alloc, | |
| .gres_per_bit = &gres_per_bit, | |
| .is_job = is_job, | |
| .plugin_id = plugin_id, | |
| }; | |
| *bit_cnt = 0; | |
| if (!gres_list) | |
| return NULL; | |
| (void) list_for_each(gres_list, _accumulate_gres_device, &arg); | |
| if (gres_bit_alloc) | |
| *bit_cnt = bit_size(gres_bit_alloc); | |
| FREE_NULL_BITMAP(gres_bit_alloc); | |
| return gres_per_bit; | |
| } | |
| static void _step_state_delete(void *gres_data) | |
| { | |
| int i; | |
| gres_step_state_t *gres_ss = (gres_step_state_t *) gres_data; | |
| if (gres_ss == NULL) | |
| return; | |
| FREE_NULL_BITMAP(gres_ss->node_in_use); | |
| if (gres_ss->gres_bit_alloc) { | |
| for (i = 0; i < gres_ss->node_cnt; i++) | |
| FREE_NULL_BITMAP(gres_ss->gres_bit_alloc[i]); | |
| xfree(gres_ss->gres_bit_alloc); | |
| } | |
| if (gres_ss->gres_per_bit_alloc) { | |
| for (i = 0; i < gres_ss->node_cnt; i++){ | |
| xfree(gres_ss->gres_per_bit_alloc[i]); | |
| } | |
| xfree(gres_ss->gres_per_bit_alloc); | |
| } | |
| xfree(gres_ss->gres_cnt_node_alloc); | |
| xfree(gres_ss->type_name); | |
| xfree(gres_ss); | |
| } | |
| extern void gres_step_list_delete(void *list_element) | |
| { | |
| gres_state_t *gres_state_step = (gres_state_t *) list_element; | |
| _step_state_delete(gres_state_step->gres_data); | |
| gres_state_step->gres_data = NULL; | |
| _gres_state_delete_members(gres_state_step); | |
| } | |
| /* | |
| * TRES specification parse logic | |
| * in_val IN - initial input string | |
| * cnt OUT - count of values | |
| * gres_list IN/OUT - where to search for (or add) new step TRES record | |
| * save_ptr IN/OUT - NULL on initial call, otherwise value from previous call | |
| * rc OUT - unchanged or an error code | |
| * RET gres - step record to set value in, found or created by this function | |
| */ | |
| static gres_state_t *_get_next_step_gres(char *in_val, uint64_t *cnt, | |
| list_t *gres_list, char **save_ptr, | |
| int *rc) | |
| { | |
| static char *prev_save_ptr = NULL; | |
| int context_inx = NO_VAL, my_rc = SLURM_SUCCESS; | |
| gres_step_state_t *gres_ss = NULL; | |
| gres_state_t *gres_state_step = NULL; | |
| gres_key_t step_search_key; | |
| char *type = NULL, *name = NULL; | |
| xassert(save_ptr); | |
| if (!in_val && (*save_ptr == NULL)) { | |
| return NULL; | |
| } | |
| if (*save_ptr == NULL) { | |
| prev_save_ptr = in_val; | |
| } else if (*save_ptr != prev_save_ptr) { | |
| error("%s: parsing error", __func__); | |
| my_rc = SLURM_ERROR; | |
| goto fini; | |
| } | |
| if (prev_save_ptr[0] == '\0') { /* Empty input token */ | |
| *save_ptr = NULL; | |
| return NULL; | |
| } | |
| if ((my_rc = _get_next_gres(in_val, &type, &context_inx, | |
| cnt, &prev_save_ptr)) || | |
| (context_inx == NO_VAL)) { | |
| prev_save_ptr = NULL; | |
| goto fini; | |
| } | |
| /* Find the step GRES record */ | |
| step_search_key.config_flags = gres_context[context_inx].config_flags; | |
| step_search_key.plugin_id = gres_context[context_inx].plugin_id; | |
| step_search_key.type_id = gres_build_id(type); | |
| gres_state_step = list_find_first(gres_list, gres_find_step_by_key, | |
| &step_search_key); | |
| if (gres_state_step) { | |
| gres_ss = gres_state_step->gres_data; | |
| } else { | |
| gres_ss = xmalloc(sizeof(gres_step_state_t)); | |
| gres_ss->type_id = step_search_key.type_id; | |
| gres_ss->type_name = type; | |
| type = NULL; /* String moved above */ | |
| gres_state_step = gres_create_state( | |
| &gres_context[context_inx], GRES_STATE_SRC_CONTEXT_PTR, | |
| GRES_STATE_TYPE_STEP, gres_ss); | |
| list_append(gres_list, gres_state_step); | |
| } | |
| fini: xfree(name); | |
| xfree(type); | |
| if (my_rc != SLURM_SUCCESS) { | |
| prev_save_ptr = NULL; | |
| if (my_rc == ESLURM_INVALID_GRES && running_in_slurmctld()) | |
| info("Invalid GRES step specification %s", in_val); | |
| *rc = my_rc; | |
| } | |
| *save_ptr = prev_save_ptr; | |
| return gres_state_step; | |
| } | |
| static int _handle_ntasks_per_tres_step(list_t *new_step_list, | |
| uint16_t ntasks_per_tres, | |
| uint32_t *num_tasks, | |
| uint32_t *cpu_count) | |
| { | |
| gres_state_t *gres_state_step; | |
| gres_step_state_t *gres_ss; | |
| uint64_t cnt = 0; | |
| int rc = SLURM_SUCCESS; | |
| uint64_t tmp = _get_step_gres_list_cnt(new_step_list, "gpu", NULL); | |
| if ((tmp == NO_VAL64) && (*num_tasks != NO_VAL)) { | |
| /* | |
| * Generate GPUs from ntasks_per_tres when not specified | |
| * and ntasks is specified | |
| */ | |
| uint32_t gpus = *num_tasks / ntasks_per_tres; | |
| /* For now, do type-less GPUs */ | |
| char *save_ptr = NULL, *gres = NULL, *in_val; | |
| xstrfmtcat(gres, "gres/gpu:%u", gpus); | |
| in_val = gres; | |
| if (*num_tasks != ntasks_per_tres * gpus) { | |
| log_flag(GRES, "%s: -n/--ntasks %u is not a multiple of --ntasks-per-gpu=%u", | |
| __func__, *num_tasks, ntasks_per_tres); | |
| return ESLURM_INVALID_GRES; | |
| } | |
| while ((gres_state_step = | |
| _get_next_step_gres(in_val, &cnt, | |
| new_step_list, | |
| &save_ptr, &rc))) { | |
| gres_ss = gres_state_step->gres_data; | |
| /* Simulate a tres_per_job specification */ | |
| gres_ss->gres_per_step = cnt; | |
| gres_ss->ntasks_per_gres = ntasks_per_tres; | |
| gres_ss->total_gres = | |
| MAX(gres_ss->total_gres, cnt); | |
| in_val = NULL; | |
| } | |
| xfree(gres); | |
| xassert(list_count(new_step_list) != 0); | |
| } else if (tmp != NO_VAL64) { | |
| tmp = tmp * ntasks_per_tres; | |
| if (*num_tasks < tmp) { | |
| uint32_t cpus_per_task = *cpu_count / *num_tasks; | |
| *num_tasks = tmp; | |
| tmp = tmp * cpus_per_task; | |
| if (*cpu_count && (*cpu_count < tmp)) { | |
| /* step_spec->cpu_count == 0 means SSF_OVERSUBSCRIBE */ | |
| *cpu_count = tmp; | |
| } | |
| } | |
| } else { | |
| error("%s: ntasks_per_tres was specified, but there was either no task count or no GPU specification to go along with it, or both were already specified.", | |
| __func__); | |
| rc = SLURM_ERROR; | |
| } | |
| return rc; | |
| } | |
| extern int gres_step_state_validate(char *cpus_per_tres, | |
| char *tres_per_step, | |
| char *tres_per_node, | |
| char *tres_per_socket, | |
| char *tres_per_task, | |
| char *mem_per_tres, | |
| uint16_t ntasks_per_tres, | |
| uint32_t step_min_nodes, | |
| list_t **step_gres_list, | |
| uint32_t job_id, | |
| uint32_t step_id, | |
| uint32_t *num_tasks, | |
| uint32_t *cpu_count, char **err_msg) | |
| { | |
| int rc = SLURM_SUCCESS; | |
| gres_step_state_t *gres_ss; | |
| gres_state_t *gres_state_step; | |
| list_t *new_step_list; | |
| uint64_t cnt = 0; | |
| uint16_t cpus_per_gres = 0; | |
| char *cpus_per_gres_name = NULL; | |
| char *cpus_per_gres_type = NULL; | |
| *step_gres_list = NULL; | |
| xassert(gres_context_cnt >= 0); | |
| xassert(num_tasks); | |
| xassert(cpu_count); | |
| slurm_mutex_lock(&gres_context_lock); | |
| new_step_list = list_create(gres_step_list_delete); | |
| if (cpus_per_tres) { | |
| char *in_val = cpus_per_tres, *save_ptr = NULL; | |
| while ((gres_state_step = _get_next_step_gres( | |
| in_val, &cnt, | |
| new_step_list, | |
| &save_ptr, &rc))) { | |
| gres_ss = gres_state_step->gres_data; | |
| gres_ss->cpus_per_gres = cnt; | |
| in_val = NULL; | |
| /* Only a single cpus_per_tres value is allowed. */ | |
| if (cpus_per_gres) { | |
| if (err_msg) | |
| *err_msg = xstrdup("You may only request cpus_per_tres for one tres"); | |
| else | |
| error("You may only request cpus_per_tres for one tres"); | |
| rc = ESLURM_INVALID_GRES; | |
| FREE_NULL_LIST(new_step_list); | |
| goto fini; | |
| } else { | |
| cpus_per_gres = cnt; | |
| cpus_per_gres_name = gres_state_step->gres_name; | |
| cpus_per_gres_type = gres_ss->type_name; | |
| } | |
| } | |
| } | |
| if (tres_per_step) { | |
| char *in_val = tres_per_step, *save_ptr = NULL; | |
| while ((gres_state_step = _get_next_step_gres( | |
| in_val, &cnt, | |
| new_step_list, | |
| &save_ptr, &rc))) { | |
| gres_ss = gres_state_step->gres_data; | |
| gres_ss->gres_per_step = cnt; | |
| in_val = NULL; | |
| gres_ss->total_gres = | |
| MAX(gres_ss->total_gres, cnt); | |
| } | |
| } | |
| if (tres_per_node) { | |
| char *in_val = tres_per_node, *save_ptr = NULL; | |
| while ((gres_state_step = _get_next_step_gres( | |
| in_val, &cnt, | |
| new_step_list, | |
| &save_ptr, &rc))) { | |
| gres_ss = gres_state_step->gres_data; | |
| gres_ss->gres_per_node = cnt; | |
| in_val = NULL; | |
| gres_ss->total_gres = | |
| MAX(gres_ss->total_gres, step_min_nodes * cnt); | |
| } | |
| } | |
| if (tres_per_socket) { | |
| char *in_val = tres_per_socket, *save_ptr = NULL; | |
| while ((gres_state_step = _get_next_step_gres( | |
| in_val, &cnt, | |
| new_step_list, | |
| &save_ptr, &rc))) { | |
| gres_ss = gres_state_step->gres_data; | |
| gres_ss->gres_per_socket = cnt; | |
| in_val = NULL; | |
| // TODO: What is sockets_per_node and ntasks_per_socket? | |
| // if (*sockets_per_node != NO_VAL16) { | |
| // cnt *= *sockets_per_node; | |
| // } else if ((*num_tasks != NO_VAL) && | |
| // (*ntasks_per_socket != NO_VAL16)) { | |
| // cnt *= ROUNDUP(*num_tasks, *ntasks_per_socket); | |
| // } | |
| // gres_ss->total_gres = | |
| // MAX(gres_ss->total_gres, cnt); | |
| } | |
| } | |
| if (tres_per_task) { | |
| char *in_val = tres_per_task, *save_ptr = NULL; | |
| while ((gres_state_step = _get_next_step_gres( | |
| in_val, &cnt, | |
| new_step_list, | |
| &save_ptr, &rc))) { | |
| gres_ss = gres_state_step->gres_data; | |
| gres_ss->gres_per_task = cnt; | |
| in_val = NULL; | |
| if (*num_tasks != NO_VAL) | |
| cnt *= *num_tasks; | |
| gres_ss->total_gres = | |
| MAX(gres_ss->total_gres, cnt); | |
| } | |
| } | |
| if (mem_per_tres) { | |
| char *in_val = mem_per_tres, *save_ptr = NULL; | |
| while ((gres_state_step = _get_next_step_gres( | |
| in_val, &cnt, | |
| new_step_list, | |
| &save_ptr, &rc))) { | |
| gres_ss = gres_state_step->gres_data; | |
| gres_ss->mem_per_gres = cnt; | |
| in_val = NULL; | |
| } | |
| } | |
| if ((ntasks_per_tres != NO_VAL16)) { | |
| rc = _handle_ntasks_per_tres_step(new_step_list, | |
| ntasks_per_tres, | |
| num_tasks, | |
| cpu_count); | |
| } | |
| if ((rc == SLURM_SUCCESS) && cpus_per_gres && *cpu_count && | |
| running_in_slurmctld()) { | |
| /* | |
| * Update cpu_count = the total requested gres * cpus_per_gres | |
| * | |
| * If SSF_OVERCOMMIT (step_spec->cpu_count == 0), don't update. | |
| * Only update if in slurmctld because the step can inherit | |
| * gres from the job_gres_list_req, which only exists in | |
| * slurmctld. | |
| */ | |
| uint64_t gpu_cnt = _get_step_gres_list_cnt(new_step_list, | |
| cpus_per_gres_name, | |
| cpus_per_gres_type); | |
| if (gpu_cnt == NO_VAL64) { | |
| if (err_msg) | |
| *err_msg = xstrdup("cpus_per_gres also requires specifying the same gres"); | |
| else | |
| error("cpus_per_gres also requires specifying the same gres"); | |
| rc = ESLURM_INVALID_GRES; | |
| FREE_NULL_LIST(new_step_list); | |
| } else | |
| *cpu_count = gpu_cnt * cpus_per_gres; | |
| } | |
| if (list_count(new_step_list) == 0) { | |
| FREE_NULL_LIST(new_step_list); | |
| } else { | |
| if (rc == SLURM_SUCCESS) { | |
| job_validate_t job_validate = { | |
| .over_array = xcalloc(list_count(new_step_list), | |
| sizeof(overlap_check_t)), | |
| }; | |
| (void) list_for_each(new_step_list, | |
| _foreach_set_over_array, | |
| &job_validate); | |
| if (job_validate.overlap_merge) | |
| rc = _merge_generic_data(new_step_list, | |
| &job_validate); | |
| xfree(job_validate.over_array); | |
| } | |
| if (rc == SLURM_SUCCESS) | |
| *step_gres_list = new_step_list; | |
| else | |
| FREE_NULL_LIST(new_step_list); | |
| } | |
| fini: | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return rc; | |
| } | |
| static void *_step_state_dup(gres_step_state_t *gres_ss) | |
| { | |
| int i; | |
| gres_step_state_t *new_gres_ss; | |
| xassert(gres_ss); | |
| new_gres_ss = xmalloc(sizeof(gres_step_state_t)); | |
| new_gres_ss->cpus_per_gres = gres_ss->cpus_per_gres; | |
| new_gres_ss->gres_per_step = gres_ss->gres_per_step; | |
| new_gres_ss->gres_per_node = gres_ss->gres_per_node; | |
| new_gres_ss->gres_per_socket = gres_ss->gres_per_socket; | |
| new_gres_ss->gres_per_task = gres_ss->gres_per_task; | |
| new_gres_ss->mem_per_gres = gres_ss->mem_per_gres; | |
| new_gres_ss->node_cnt = gres_ss->node_cnt; | |
| new_gres_ss->total_gres = gres_ss->total_gres; | |
| if (gres_ss->node_in_use) | |
| new_gres_ss->node_in_use = bit_copy(gres_ss->node_in_use); | |
| if (gres_ss->gres_cnt_node_alloc) { | |
| i = sizeof(uint64_t) * gres_ss->node_cnt; | |
| new_gres_ss->gres_cnt_node_alloc = xmalloc(i); | |
| memcpy(new_gres_ss->gres_cnt_node_alloc, | |
| gres_ss->gres_cnt_node_alloc, i); | |
| } | |
| if (gres_ss->gres_bit_alloc) { | |
| new_gres_ss->gres_bit_alloc = xcalloc(gres_ss->node_cnt, | |
| sizeof(bitstr_t *)); | |
| for (i = 0; i < gres_ss->node_cnt; i++) { | |
| if (gres_ss->gres_bit_alloc[i] == NULL) | |
| continue; | |
| new_gres_ss->gres_bit_alloc[i] = | |
| bit_copy(gres_ss->gres_bit_alloc[i]); | |
| } | |
| } | |
| new_gres_ss->gres_per_bit_alloc = | |
| _dup_per_bit(gres_ss->node_cnt, gres_ss->gres_bit_alloc, | |
| gres_ss->gres_per_bit_alloc); | |
| return new_gres_ss; | |
| } | |
| static void *_step_state_dup2(gres_step_state_t *gres_ss, int job_node_index) | |
| { | |
| gres_step_state_t *new_gres_ss; | |
| xassert(gres_ss); | |
| new_gres_ss = xmalloc(sizeof(gres_step_state_t)); | |
| new_gres_ss->cpus_per_gres = gres_ss->cpus_per_gres; | |
| new_gres_ss->gres_per_step = gres_ss->gres_per_step; | |
| new_gres_ss->gres_per_node = gres_ss->gres_per_node; | |
| new_gres_ss->gres_per_socket = gres_ss->gres_per_socket; | |
| new_gres_ss->gres_per_task = gres_ss->gres_per_task; | |
| new_gres_ss->mem_per_gres = gres_ss->mem_per_gres; | |
| new_gres_ss->node_cnt = 1; | |
| new_gres_ss->total_gres = gres_ss->total_gres; | |
| if (gres_ss->node_in_use) | |
| new_gres_ss->node_in_use = bit_copy(gres_ss->node_in_use); | |
| if (gres_ss->gres_cnt_node_alloc) { | |
| new_gres_ss->gres_cnt_node_alloc = xmalloc(sizeof(uint64_t)); | |
| new_gres_ss->gres_cnt_node_alloc[0] = | |
| gres_ss->gres_cnt_node_alloc[job_node_index]; | |
| } | |
| if ((job_node_index < gres_ss->node_cnt) && gres_ss->gres_bit_alloc && | |
| gres_ss->gres_bit_alloc[job_node_index]) { | |
| new_gres_ss->gres_bit_alloc = xmalloc(sizeof(bitstr_t *)); | |
| new_gres_ss->gres_bit_alloc[0] = | |
| bit_copy(gres_ss->gres_bit_alloc[job_node_index]); | |
| } | |
| if (gres_ss->gres_per_bit_alloc && | |
| (job_node_index < gres_ss->node_cnt) && gres_ss->gres_bit_alloc && | |
| gres_ss->gres_bit_alloc[job_node_index]) { | |
| int bit_cnt = bit_size(gres_ss->gres_bit_alloc[job_node_index]); | |
| new_gres_ss->gres_per_bit_alloc = xmalloc(sizeof(uint64_t *)); | |
| new_gres_ss->gres_per_bit_alloc[0] = xcalloc(bit_cnt, | |
| sizeof(uint64_t)); | |
| memcpy(new_gres_ss->gres_per_bit_alloc[0], | |
| gres_ss->gres_per_bit_alloc[job_node_index], | |
| bit_cnt * sizeof(uint64_t)); | |
| } | |
| return new_gres_ss; | |
| } | |
| /* | |
| * Create a copy of a step's gres state | |
| * IN gres_list - List of Gres records for this step to track usage | |
| * RET The copy or NULL on failure | |
| */ | |
| list_t *gres_step_state_list_dup(list_t *gres_list) | |
| { | |
| return gres_step_state_extract(gres_list, -1); | |
| } | |
| static int _foreach_step_state_extract(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_step = x; | |
| foreach_state_list_dup_t *foreach_state_list_dup = arg; | |
| gres_state_t *new_gres_state_step; | |
| void *new_gres_data; | |
| if (foreach_state_list_dup->job_node_index == -1) | |
| new_gres_data = _step_state_dup(gres_state_step->gres_data); | |
| else | |
| new_gres_data = _step_state_dup2( | |
| gres_state_step->gres_data, | |
| foreach_state_list_dup->job_node_index); | |
| if (!foreach_state_list_dup->new_gres_list) | |
| foreach_state_list_dup->new_gres_list = | |
| list_create(gres_step_list_delete); | |
| new_gres_state_step = gres_create_state( | |
| gres_state_step, GRES_STATE_SRC_STATE_PTR, | |
| GRES_STATE_TYPE_STEP, new_gres_data); | |
| list_append(foreach_state_list_dup->new_gres_list, new_gres_state_step); | |
| return 0; | |
| } | |
| /* | |
| * Create a copy of a step's gres state for a particular node index | |
| * IN gres_list - List of Gres records for this step to track usage | |
| * IN job_node_index - zero-origin index to the node | |
| * RET The copy or NULL on failure | |
| */ | |
| list_t *gres_step_state_extract(list_t *gres_list, int job_node_index) | |
| { | |
| foreach_state_list_dup_t foreach_state_list_dup = { | |
| .job_node_index = job_node_index, | |
| }; | |
| if (gres_list) | |
| (void) list_for_each(gres_list, _foreach_step_state_extract, | |
| &foreach_state_list_dup); | |
| return foreach_state_list_dup.new_gres_list; | |
| } | |
| /* | |
| * Pack a step's current gres status, called from slurmctld for save/restore | |
| * IN gres_list - generated by gres_stepmgr_step_alloc() | |
| * IN/OUT buffer - location to write state to | |
| * IN step_id - job and step ID for logging | |
| */ | |
| extern int gres_step_state_pack(list_t *gres_list, buf_t *buffer, | |
| slurm_step_id_t *step_id, | |
| uint16_t protocol_version) | |
| { | |
| pack_state_t pack_state = { | |
| .buffer = buffer, | |
| .magic = GRES_MAGIC, | |
| .protocol_version = protocol_version, | |
| }; | |
| return _pack_state(gres_list, &pack_state, _foreach_step_state_pack); | |
| } | |
| /* | |
| * Unpack a step's current gres status, called from slurmctld for save/restore | |
| * OUT gres_list - restored state stored by gres_step_state_pack() | |
| * IN/OUT buffer - location to read state from | |
| * IN step_id - job and step ID for logging | |
| */ | |
| extern int gres_step_state_unpack(list_t **gres_list, buf_t *buffer, | |
| slurm_step_id_t *step_id, | |
| uint16_t protocol_version) | |
| { | |
| int i, rc = SLURM_SUCCESS; | |
| uint32_t magic = 0, plugin_id = 0, uint32_tmp = 0; | |
| uint16_t rec_cnt = 0; | |
| uint8_t data_flag = 0; | |
| gres_state_t *gres_state_step; | |
| gres_step_state_t *gres_ss = NULL; | |
| bool locked = false; | |
| safe_unpack16(&rec_cnt, buffer); | |
| if (rec_cnt == 0) | |
| return SLURM_SUCCESS; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| locked = true; | |
| if ((gres_context_cnt > 0) && (*gres_list == NULL)) { | |
| *gres_list = list_create(gres_step_list_delete); | |
| } | |
| while ((rc == SLURM_SUCCESS) && (rec_cnt)) { | |
| slurm_gres_context_t *gres_ctx; | |
| if ((buffer == NULL) || (remaining_buf(buffer) == 0)) | |
| break; | |
| rec_cnt--; | |
| if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) { | |
| safe_unpack32(&magic, buffer); | |
| if (magic != GRES_MAGIC) | |
| goto unpack_error; | |
| safe_unpack32(&plugin_id, buffer); | |
| gres_ss = xmalloc(sizeof(gres_step_state_t)); | |
| safe_unpack16(&gres_ss->cpus_per_gres, buffer); | |
| safe_unpack16(&gres_ss->flags, buffer); | |
| safe_unpack64(&gres_ss->gres_per_step, buffer); | |
| safe_unpack64(&gres_ss->gres_per_node, buffer); | |
| safe_unpack64(&gres_ss->gres_per_socket, buffer); | |
| safe_unpack64(&gres_ss->gres_per_task, buffer); | |
| safe_unpack64(&gres_ss->mem_per_gres, buffer); | |
| safe_unpack64(&gres_ss->total_gres, buffer); | |
| safe_unpackstr(&gres_ss->type_name, buffer); | |
| gres_ss->type_id = gres_build_id(gres_ss->type_name); | |
| safe_unpack32(&gres_ss->node_cnt, buffer); | |
| if (gres_ss->node_cnt > NO_VAL) | |
| goto unpack_error; | |
| unpack_bit_str_hex(&gres_ss->node_in_use, buffer); | |
| safe_unpack8(&data_flag, buffer); | |
| if (data_flag) { | |
| safe_unpack64_array( | |
| &gres_ss->gres_cnt_node_alloc, | |
| &uint32_tmp, buffer); | |
| } | |
| safe_unpack8(&data_flag, buffer); | |
| if (data_flag) { | |
| gres_ss->gres_bit_alloc = | |
| xcalloc(gres_ss->node_cnt, | |
| sizeof(bitstr_t *)); | |
| for (i = 0; i < gres_ss->node_cnt; i++) { | |
| unpack_bit_str_hex(&gres_ss-> | |
| gres_bit_alloc[i], | |
| buffer); | |
| } | |
| } | |
| for (i = 0; i < gres_ss->node_cnt; i++) { | |
| safe_unpack8(&data_flag, buffer); | |
| if (!data_flag) | |
| continue; | |
| if (!gres_ss->gres_per_bit_alloc) | |
| safe_xcalloc( | |
| gres_ss->gres_per_bit_alloc, | |
| gres_ss->node_cnt, | |
| sizeof(uint64_t *)); | |
| safe_unpack64_array( | |
| &gres_ss->gres_per_bit_alloc[i], | |
| &uint32_tmp, buffer); | |
| } | |
| } else { | |
| error("%s: protocol_version %hu not supported", | |
| __func__, protocol_version); | |
| goto unpack_error; | |
| } | |
| if (!(gres_ctx = _find_context_by_id(plugin_id))) { | |
| /* | |
| * A likely sign that GresPlugins has changed. | |
| * Not a fatal error, skip over the data. | |
| */ | |
| info("%s: no plugin configured to unpack data type %u from %ps", | |
| __func__, plugin_id, step_id); | |
| _step_state_delete(gres_ss); | |
| gres_ss = NULL; | |
| continue; | |
| } | |
| gres_state_step = gres_create_state( | |
| gres_ctx, GRES_STATE_SRC_CONTEXT_PTR, | |
| GRES_STATE_TYPE_STEP, gres_ss); | |
| gres_ss = NULL; | |
| list_append(*gres_list, gres_state_step); | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return rc; | |
| unpack_error: | |
| error("%s: unpack error from %ps", __func__, step_id); | |
| if (gres_ss) | |
| _step_state_delete(gres_ss); | |
| if (locked) | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return SLURM_ERROR; | |
| } | |
| static int _foreach_step_count(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_step = x; | |
| foreach_gres_list_cnt_t *foreach_gres_list_cnt = arg; | |
| gres_step_state_t *gres_ss = gres_state_step->gres_data; | |
| if (gres_state_step->plugin_id != foreach_gres_list_cnt->plugin_id) | |
| return 0; | |
| /* gres_cnt_node_alloc has one element in slurmstepd */ | |
| if (foreach_gres_list_cnt->gres_cnt == NO_VAL64) | |
| foreach_gres_list_cnt->gres_cnt = | |
| gres_ss->gres_cnt_node_alloc[0]; | |
| else | |
| foreach_gres_list_cnt->gres_cnt += | |
| gres_ss->gres_cnt_node_alloc[0]; | |
| return 0; | |
| } | |
| /* Return the count of GRES of a specific name on this machine | |
| * IN step_gres_list - generated by gres_stepmgr_step_alloc() | |
| * IN gres_name - name of the GRES to match | |
| * RET count of GRES of this specific name available to the job or NO_VAL64 | |
| */ | |
| extern uint64_t gres_step_count(list_t *step_gres_list, char *gres_name) | |
| { | |
| foreach_gres_list_cnt_t foreach_gres_list_cnt = { | |
| .gres_cnt = NO_VAL64, | |
| }; | |
| if (step_gres_list) { | |
| slurm_mutex_lock(&gres_context_lock); | |
| for (int i = 0; i < gres_context_cnt; i++) { | |
| if (!xstrcmp(gres_context[i].gres_name, gres_name)) { | |
| foreach_gres_list_cnt.plugin_id = | |
| gres_context[i].plugin_id; | |
| (void) list_for_each(step_gres_list, | |
| _foreach_step_count, | |
| &foreach_gres_list_cnt); | |
| break; | |
| } | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| } | |
| return foreach_gres_list_cnt.gres_cnt; | |
| } | |
| /* | |
| * Here we convert usable_gres from a mask just for the gres in the allocation | |
| * to one for the gres on the node. Essentially putting in a '0' for gres not | |
| * in the allocation | |
| * | |
| * IN/OUT - usable_gres | |
| * IN - gres_bit_alloc | |
| */ | |
| static void _translate_step_to_global_device_index(bitstr_t **usable_gres, | |
| bitstr_t *gres_bit_alloc) | |
| { | |
| bitstr_t *tmp = bit_alloc(bit_size(gres_bit_alloc)); | |
| int i_last, bit, bit2 = 0; | |
| i_last = bit_fls(gres_bit_alloc); | |
| for (bit = 0; bit <= i_last; bit++) { | |
| if (bit_test(gres_bit_alloc, bit)) { | |
| if (bit_test(*usable_gres, bit2)) { | |
| bit_set(tmp, bit); | |
| } | |
| bit2++; | |
| } | |
| } | |
| FREE_NULL_BITMAP(*usable_gres); | |
| *usable_gres = tmp; | |
| } | |
| static bitstr_t *_cpu_set_to_bit_str(xcpuset_t *cpu_set, int cpu_count) | |
| { | |
| bitstr_t *cpu_bitstr = bit_alloc(cpu_count); | |
| if (cpu_set) { | |
| for (int i = 0; i < cpu_count; i++) | |
| if (XCPU_ISSET(i, cpu_set)) | |
| bit_set(cpu_bitstr, i); | |
| } else { | |
| bit_set_all(cpu_bitstr); | |
| } | |
| return cpu_bitstr; | |
| } | |
| static int _foreach_closest_usable_gres(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x; | |
| foreach_closest_usable_gres_t *foreach_closest_usable_gres = arg; | |
| if (gres_slurmd_conf->plugin_id != | |
| foreach_closest_usable_gres->plugin_id) | |
| return 0; | |
| if ((foreach_closest_usable_gres->gres_inx + gres_slurmd_conf->count) > | |
| foreach_closest_usable_gres->bitmap_size) { | |
| error("GRES %s bitmap overflow ((%d + %"PRIu64") > %d)", | |
| gres_slurmd_conf->name, | |
| foreach_closest_usable_gres->gres_inx, | |
| gres_slurmd_conf->count, | |
| foreach_closest_usable_gres->bitmap_size); | |
| return 0; | |
| } | |
| if (!gres_slurmd_conf->cpus_bitmap || | |
| bit_overlap_any(gres_slurmd_conf->cpus_bitmap, | |
| foreach_closest_usable_gres->task_cpus_bitmap)) { | |
| bit_nset(foreach_closest_usable_gres->usable_gres, | |
| foreach_closest_usable_gres->gres_inx, | |
| foreach_closest_usable_gres->gres_inx + | |
| gres_slurmd_conf->count - 1); | |
| } | |
| foreach_closest_usable_gres->gres_inx += gres_slurmd_conf->count; | |
| return 0; | |
| } | |
| /* | |
| * Given a GRES context index, return a bitmap representing those GRES | |
| * which are available from the CPUs current allocated to this process. | |
| * This function only works with task/cgroup and constrained devices or | |
| * if the job step has access to the entire node's resources. | |
| */ | |
| static bitstr_t *_get_closest_usable_gres(uint32_t plugin_id, | |
| bitstr_t *gres_bit_alloc, | |
| xcpuset_t *task_cpu_set) | |
| { | |
| foreach_closest_usable_gres_t foreach_closest_usable_gres = { | |
| .gres_inx = 0, | |
| .plugin_id = plugin_id, | |
| }; | |
| if (!gres_conf_list) { | |
| error("gres_conf_list is null!"); | |
| return NULL; | |
| } | |
| foreach_closest_usable_gres.task_cpus_bitmap = | |
| _cpu_set_to_bit_str(task_cpu_set, | |
| ((gres_slurmd_conf_t *) | |
| list_peek(gres_conf_list)) | |
| ->cpu_cnt); | |
| foreach_closest_usable_gres.bitmap_size = bit_size(gres_bit_alloc); | |
| foreach_closest_usable_gres.usable_gres = | |
| bit_alloc(foreach_closest_usable_gres.bitmap_size); | |
| (void) list_for_each(gres_conf_list, _foreach_closest_usable_gres, | |
| &foreach_closest_usable_gres); | |
| FREE_NULL_BITMAP(foreach_closest_usable_gres.task_cpus_bitmap); | |
| bit_and(foreach_closest_usable_gres.usable_gres, gres_bit_alloc); | |
| return foreach_closest_usable_gres.usable_gres; | |
| } | |
| static int _foreach_gres_to_task(void *x, void *arg) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf = x; | |
| foreach_gres_to_task_t *foreach_gres_to_task = arg; | |
| int start, end; | |
| if (gres_slurmd_conf->plugin_id != foreach_gres_to_task->plugin_id) | |
| return 0; | |
| start = foreach_gres_to_task->gres_inx * | |
| foreach_gres_to_task->ntasks_per_gres; | |
| foreach_gres_to_task->gres_inx += gres_slurmd_conf->count; | |
| end = foreach_gres_to_task->gres_inx * | |
| foreach_gres_to_task->ntasks_per_gres; | |
| if (!bit_set_count_range(foreach_gres_to_task->gres_slots, start, end)) | |
| return 0; | |
| if (gres_slurmd_conf->cpus_bitmap) { | |
| if (bit_super_set(foreach_gres_to_task->task_cpus_bitmap, | |
| gres_slurmd_conf->cpus_bitmap)) { | |
| foreach_gres_to_task->best_slot = bit_ffs_from_bit( | |
| foreach_gres_to_task->gres_slots, start); | |
| return -1; | |
| } | |
| if (foreach_gres_to_task->overlap) | |
| return 0; | |
| if (bit_overlap_any(foreach_gres_to_task->task_cpus_bitmap, | |
| gres_slurmd_conf->cpus_bitmap)) { | |
| foreach_gres_to_task->best_slot = bit_ffs_from_bit( | |
| foreach_gres_to_task->gres_slots, start); | |
| foreach_gres_to_task->overlap = true; | |
| return 0; | |
| } | |
| } | |
| if (foreach_gres_to_task->best_slot == -1) | |
| foreach_gres_to_task->best_slot = bit_ffs_from_bit( | |
| foreach_gres_to_task->gres_slots, start); | |
| return 0; | |
| } | |
| /* Select the best available gres from gres_slots */ | |
| static int _assign_gres_to_task(xcpuset_t *task_cpu_set, int ntasks_per_gres, | |
| bitstr_t *gres_slots, uint32_t plugin_id) | |
| { | |
| foreach_gres_to_task_t foreach_gres_to_task = { | |
| .best_slot = -1, | |
| .gres_inx = 0, | |
| .gres_slots = gres_slots, | |
| .ntasks_per_gres = ntasks_per_gres, | |
| .overlap = false, | |
| .plugin_id = plugin_id, | |
| .task_cpus_bitmap = | |
| _cpu_set_to_bit_str(task_cpu_set, | |
| ((gres_slurmd_conf_t *) | |
| list_peek(gres_conf_list)) | |
| ->cpu_cnt), | |
| }; | |
| (void) list_for_each(gres_conf_list, _foreach_gres_to_task, | |
| &foreach_gres_to_task); | |
| FREE_NULL_BITMAP(foreach_gres_to_task.task_cpus_bitmap); | |
| if (foreach_gres_to_task.best_slot != -1) { | |
| bit_clear(foreach_gres_to_task.gres_slots, | |
| foreach_gres_to_task.best_slot); | |
| return (foreach_gres_to_task.best_slot / | |
| foreach_gres_to_task.ntasks_per_gres); | |
| } else { | |
| log_flag(GRES, "%s Can't find free slot", __func__); | |
| return -1; | |
| } | |
| } | |
| /* | |
| * Given the cpu affinity of all tasks, return a bitmap binding a single gres to | |
| * this task. | |
| */ | |
| static bitstr_t *_get_single_usable_gres(int context_inx, | |
| int ntasks_per_gres, | |
| int local_proc_id, | |
| stepd_step_rec_t *step, | |
| bitstr_t *gres_bit_alloc) | |
| { | |
| int idx = 0; | |
| bitstr_t *usable_gres = NULL; | |
| bitstr_t *gres_slots = NULL; | |
| int32_t gres_count = bit_set_count(gres_bit_alloc); | |
| /* No need to select gres if there is only 1 to use */ | |
| if (gres_count <= 1) { | |
| log_flag(GRES, "%s: (task %d) No need to select single gres since count is 0 or 1", | |
| __func__, local_proc_id); | |
| return bit_copy(gres_bit_alloc); | |
| } | |
| /* | |
| * Create bitmap called gres_slots. This represents the available slots | |
| * for tasks on that gres based off of ntasks_per_gres and if that gres | |
| * is allocated to the step. | |
| */ | |
| if (ntasks_per_gres == 1) | |
| gres_slots = bit_copy(gres_bit_alloc); | |
| else { | |
| gres_slots = bit_alloc(bit_size(gres_bit_alloc) * | |
| ntasks_per_gres); | |
| for (int i = -1; | |
| (i = bit_ffs_from_bit(gres_bit_alloc, i + 1)) >= 0;) { | |
| bit_nset(gres_slots, i * ntasks_per_gres, | |
| (((i + 1) * ntasks_per_gres) - 1)); | |
| } | |
| } | |
| /* | |
| * To ensure no task gets more than ntasks_per_gres, here we one by one, | |
| * select an available gres_slot for each task and clear a gres_slot. | |
| * Once we reach the current task we can take the gres assignment and | |
| * quit the loop | |
| */ | |
| for (int i = 0; i <= local_proc_id; i++) { | |
| idx = _assign_gres_to_task(step->task[i]->cpu_set, | |
| ntasks_per_gres, gres_slots, | |
| gres_context[context_inx].plugin_id); | |
| } | |
| FREE_NULL_BITMAP(gres_slots); | |
| /* Return a bitmap with this as the only usable GRES */ | |
| usable_gres = bit_alloc(bit_size(gres_bit_alloc)); | |
| if (idx < 0) { | |
| int n; | |
| error("%s Can't find free slot for local_proc_id = %d, continue using block distribution", | |
| __func__, local_proc_id); | |
| n = local_proc_id % gres_count; | |
| idx = bit_get_bit_num(gres_bit_alloc, n); | |
| } | |
| bit_set(usable_gres, idx); | |
| if (slurm_conf.debug_flags & DEBUG_FLAG_GRES){ | |
| char *usable_gres_str = bit_fmt_hexmask_trim(usable_gres); | |
| log_flag(GRES, "%s: local_proc_id = %d; usable_gres: %s", | |
| __func__, local_proc_id, usable_gres_str); | |
| xfree(usable_gres_str); | |
| } | |
| return usable_gres; | |
| } | |
| /* | |
| * Configure the GRES hardware allocated to the current step while privileged | |
| * | |
| * The devices of every record of a GRES allocated to the step on this node | |
| * are set up together. A GRES with no such device is skipped. | |
| * | |
| * IN step_gres_list - Step's GRES specification | |
| * IN settings - string containing configuration settings for the hardware | |
| */ | |
| extern void gres_g_step_hardware_init(list_t *step_gres_list, | |
| char *settings) | |
| { | |
| if (!step_gres_list) | |
| return; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| for (int i = 0; i < gres_context_cnt; i++) { | |
| bitstr_t *devices = NULL; | |
| foreach_gres_accumulate_device_t arg = { | |
| .gres_bit_alloc = &devices, | |
| .is_job = false, | |
| .plugin_id = gres_context[i].plugin_id, | |
| }; | |
| int device_cnt = 0; | |
| if (gres_context[i].ops.step_hardware_init == NULL) | |
| continue; | |
| (void) list_for_each(step_gres_list, _accumulate_gres_device, | |
| &arg); | |
| if (!devices) | |
| continue; | |
| device_cnt = bit_set_count(devices); | |
| if (!device_cnt) { | |
| log_flag(GRES, "%s: no gres/%s device is allocated to the step on this node, skipping its hardware setup", | |
| __func__, gres_context[i].gres_name); | |
| FREE_NULL_BITMAP(devices); | |
| continue; | |
| } | |
| log_flag(GRES, "%s: setting up %d gres/%s device(s) allocated to the step on this node", | |
| __func__, device_cnt, gres_context[i].gres_name); | |
| if (settings) | |
| debug2("settings: %s", settings); | |
| (*(gres_context[i].ops.step_hardware_init))(devices, settings); | |
| FREE_NULL_BITMAP(devices); | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| } | |
| /* | |
| * Optionally undo GRES hardware configuration while privileged | |
| */ | |
| extern void gres_g_step_hardware_fini(void) | |
| { | |
| int i; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| if (gres_context[i].ops.step_hardware_fini == NULL) { | |
| continue; | |
| } | |
| (*(gres_context[i].ops.step_hardware_fini)) (); | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| } | |
| /* | |
| * Given a set of GRES masks or maps and the local process ID, return the bitmap | |
| * of GRES that should be available to this task. | |
| * | |
| * IN map_or_mask | |
| * IN local_proc_id | |
| * IN gres_bit_alloc | |
| * IN is_map | |
| * IN get_devices | |
| * | |
| * RET usable_gres | |
| */ | |
| static bitstr_t *_get_usable_gres_map_or_mask(char *map_or_mask, | |
| int local_proc_id, | |
| bitstr_t *gres_bit_alloc, | |
| bool is_map, | |
| bool get_devices) | |
| { | |
| bitstr_t *usable_gres = NULL; | |
| char *tmp, *tok, *save_ptr = NULL, *mult; | |
| int i, task_offset = 0, task_mult, bitmap_size; | |
| int value, min, max; | |
| if (!map_or_mask || !map_or_mask[0]) | |
| return NULL; | |
| bitmap_size = bit_size(gres_bit_alloc); | |
| min = (is_map ? 0 : 1); | |
| max = (is_map ? bitmap_size - 1 : ~(-1 << bitmap_size)); | |
| while (usable_gres == NULL) { | |
| tmp = xstrdup(map_or_mask); | |
| strtok(tmp,"+"); | |
| tok = strtok_r(tmp, ",", &save_ptr); | |
| while (tok) { | |
| if ((mult = strchr(tok, '*'))) | |
| task_mult = atoi(mult + 1); | |
| else | |
| task_mult = 1; | |
| if (task_mult == 0) { | |
| error("Repetition count of 0 not allowed in gres binding mask, using 1 instead"); | |
| task_mult = 1; | |
| } | |
| if ((local_proc_id >= task_offset) && | |
| (local_proc_id <= (task_offset + task_mult - 1))) { | |
| value = strtol(tok, NULL, 0); | |
| usable_gres = bit_alloc(bitmap_size); | |
| if ((value < min) || (value > max)) { | |
| error("Invalid map or mask value specified."); | |
| xfree(tmp); | |
| goto end; /* Bad value */ | |
| } | |
| if (is_map) | |
| bit_set(usable_gres, value); | |
| else | |
| for (i = 0; i < bitmap_size; i++) { | |
| if ((value >> i) & 0x1) | |
| bit_set(usable_gres, i); | |
| } | |
| break; /* All done */ | |
| } else { | |
| task_offset += task_mult; | |
| } | |
| tok = strtok_r(NULL, ",", &save_ptr); | |
| } | |
| xfree(tmp); | |
| } | |
| end: | |
| if (gres_use_local_device_index()) { | |
| if (get_devices) | |
| _translate_step_to_global_device_index( | |
| &usable_gres, gres_bit_alloc); | |
| else{ | |
| bit_consolidate(usable_gres); | |
| } | |
| } else { | |
| bit_and(usable_gres, gres_bit_alloc); | |
| } | |
| return usable_gres; | |
| } | |
| static void _accumulate_step_gres_alloc(gres_state_t *gres_state_step, | |
| bitstr_t **gres_bit_alloc, | |
| uint64_t *gres_cnt, | |
| uint64_t **gres_per_bit) | |
| { | |
| gres_step_state_t *gres_ss = | |
| (gres_step_state_t *)gres_state_step->gres_data; | |
| /* Since this should only run on the node node_cnt should always be 1 */ | |
| if (gres_ss->node_cnt != 1) { | |
| error("gres_step_state_t node count not 1 while on node. This should never happen"); | |
| return; | |
| } | |
| if (gres_ss->gres_bit_alloc && | |
| gres_ss->gres_bit_alloc[0]) { | |
| if (!*gres_bit_alloc) { | |
| *gres_bit_alloc = bit_alloc( | |
| bit_size(gres_ss->gres_bit_alloc[0])); | |
| } | |
| bit_or(*gres_bit_alloc, gres_ss->gres_bit_alloc[0]); | |
| } | |
| if (gres_cnt && gres_ss->gres_cnt_node_alloc) | |
| *gres_cnt += gres_ss->gres_cnt_node_alloc[0]; | |
| if (gres_per_bit && | |
| gres_ss->gres_per_bit_alloc && | |
| gres_ss->gres_per_bit_alloc[0] && | |
| gres_ss->gres_bit_alloc && | |
| gres_ss->gres_bit_alloc[0]) { | |
| if (!*gres_per_bit) | |
| *gres_per_bit = xcalloc( | |
| bit_size(gres_ss->gres_bit_alloc[0]), | |
| sizeof(uint64_t)); | |
| for (int i = 0; i < bit_size(gres_ss->gres_bit_alloc[0]); i++) { | |
| (*gres_per_bit)[i] += gres_ss->gres_per_bit_alloc[0][i]; | |
| } | |
| } | |
| } | |
| static void _filter_gres_per_task(bitstr_t *test_gres, | |
| bitstr_t *gres_bit_avail, | |
| bitstr_t *usable_gres, | |
| uint64_t *gres_needed, | |
| bool set_usable_gres) | |
| { | |
| for (int bit = 0; | |
| *gres_needed && (bit = bit_ffs_from_bit(test_gres, bit)) >= 0; | |
| bit++) { | |
| (*gres_needed)--; | |
| bit_clear(gres_bit_avail, bit); | |
| if (set_usable_gres) | |
| bit_set(usable_gres, bit); | |
| } | |
| } | |
| /* | |
| * Given a required gres_per_task count, determine which gres should be assigned | |
| * to this task. Prefer gres with cpu affinity that match the task. | |
| * | |
| * RET usable_gres | |
| */ | |
| static bitstr_t *_get_gres_per_task(bitstr_t *gres_bit_alloc, | |
| uint64_t gres_per_task, | |
| stepd_step_rec_t *step, | |
| uint32_t plugin_id, | |
| int local_proc_id) | |
| { | |
| uint64_t gres_needed; | |
| bitstr_t *usable_gres, *gres_bit_avail; | |
| usable_gres = bit_alloc(bit_size(gres_bit_alloc)); | |
| gres_bit_avail = bit_copy(gres_bit_alloc); | |
| /* | |
| * We must determine what the previous tasks are taking first to know | |
| * which gres are available to be assigned to this task. | |
| */ | |
| for (int i = 0; i <= local_proc_id; i++) { | |
| gres_needed = gres_per_task; | |
| /* First: Try to select device with with cpu affinity */ | |
| if (gres_needed) { | |
| bitstr_t *closest_gres = _get_closest_usable_gres( | |
| plugin_id, gres_bit_avail, | |
| step->task[i]->cpu_set); | |
| _filter_gres_per_task(closest_gres, gres_bit_avail, | |
| usable_gres, &gres_needed, | |
| (i == local_proc_id)); | |
| FREE_NULL_BITMAP(closest_gres); | |
| } | |
| /* Second: Select any available device */ | |
| if (gres_needed) | |
| _filter_gres_per_task(gres_bit_avail, gres_bit_avail, | |
| usable_gres, &gres_needed, | |
| (i == local_proc_id)); | |
| if (gres_needed) { | |
| error("Not enough gres to bind %"PRIu64" per task", | |
| gres_per_task); | |
| break; | |
| } | |
| } | |
| FREE_NULL_BITMAP(gres_bit_avail); | |
| return usable_gres; | |
| } | |
| static void _filter_shared_gres_per_task(bitstr_t *test_gres, | |
| bitstr_t *usable_gres, | |
| uint64_t *gres_per_bit_avail, | |
| uint64_t *gres_needed, | |
| bool use_single_dev, | |
| bool set_usable_gres) | |
| { | |
| for (int bit = 0; | |
| *gres_needed && (bit = bit_ffs_from_bit(test_gres, bit)) >= 0; | |
| bit++) { | |
| uint64_t dec = MIN(gres_per_bit_avail[bit], *gres_needed); | |
| if (dec < (use_single_dev ? *gres_needed : 1)) | |
| continue; | |
| gres_per_bit_avail[bit] -= dec; | |
| *gres_needed -= dec; | |
| if (set_usable_gres) | |
| bit_set(usable_gres, bit); | |
| } | |
| } | |
| /* | |
| * Given a required gres_per_task count, determine which shared gres should be | |
| * assigned to this task. Prefer gres with core affinity that match the task | |
| * and prefer allocating shared gres belonging to a single device if possible. | |
| */ | |
| static bitstr_t *_get_shared_gres_per_task(bitstr_t *gres_bit_alloc, | |
| uint64_t *gres_per_bit, | |
| uint64_t gres_per_task, | |
| stepd_step_rec_t *step, | |
| uint32_t sharing_plugin_id, | |
| int local_proc_id) | |
| { | |
| uint64_t gres_needed; | |
| bitstr_t *usable_gres, *closest_gres; | |
| uint64_t *gres_per_bit_avail; | |
| usable_gres = bit_alloc(bit_size(gres_bit_alloc)); | |
| gres_per_bit_avail = xcalloc(bit_size(gres_bit_alloc), | |
| sizeof(uint64_t)); | |
| memcpy(gres_per_bit_avail, gres_per_bit, | |
| bit_size(gres_bit_alloc) * sizeof(uint64_t)); | |
| /* | |
| * We must determine what the previous tasks are taking first to know | |
| * which gres are available to be assigned to this task. | |
| */ | |
| for (int i = 0; i <= local_proc_id; i++) { | |
| closest_gres = _get_closest_usable_gres(sharing_plugin_id, | |
| gres_bit_alloc, | |
| step->task[i]->cpu_set); | |
| gres_needed = gres_per_task; | |
| /* | |
| * Compare this selection priority with _set_shared_task_bits() | |
| * in gres_select_filter.c | |
| * | |
| * First: Get a single device with core affinity with sufficient | |
| * available shared gres. | |
| * Second: Get a single device with sufficient available shared | |
| * gres | |
| * Third: Get devices with core affinity with any available | |
| * shared gres | |
| * Fourth: Get devices with any available shared gres | |
| */ | |
| if (gres_needed) | |
| _filter_shared_gres_per_task(closest_gres, usable_gres, | |
| gres_per_bit_avail, | |
| &gres_needed, true, | |
| (i == local_proc_id)); | |
| if (gres_needed) | |
| _filter_shared_gres_per_task(gres_bit_alloc, | |
| usable_gres, | |
| gres_per_bit_avail, | |
| &gres_needed, true, | |
| (i == local_proc_id)); | |
| if (gres_needed) | |
| _filter_shared_gres_per_task(closest_gres, usable_gres, | |
| gres_per_bit_avail, | |
| &gres_needed, false, | |
| (i == local_proc_id)); | |
| if (gres_needed) | |
| _filter_shared_gres_per_task(gres_bit_alloc, | |
| usable_gres, | |
| gres_per_bit_avail, | |
| &gres_needed, false, | |
| (i == local_proc_id)); | |
| FREE_NULL_BITMAP(closest_gres); | |
| if (gres_needed) { | |
| error("Not enough shared gres to bind %"PRIu64" per task", | |
| gres_per_task); | |
| break; | |
| } | |
| } | |
| xfree(gres_per_bit_avail); | |
| return usable_gres; | |
| } | |
| /* Convert old binding options to current gres binding format | |
| * | |
| * IN accel_bind_type - GRES binding options (old format, a bitmap) | |
| * IN/OUT tres_bind_str - TRES binding directives (new format, a string) | |
| */ | |
| static void _parse_accel_bind_type(uint16_t accel_bind_type, char *tres_bind_str) | |
| { | |
| if (accel_bind_type & ACCEL_BIND_CLOSEST_GPU) { | |
| xstrfmtcat(tres_bind_str, "%sgres/gpu:closest", | |
| tres_bind_str ? "+" : ""); | |
| } | |
| if (accel_bind_type & ACCEL_BIND_CLOSEST_NIC) { | |
| xstrfmtcat(tres_bind_str, "%sgres/nic:closest", | |
| tres_bind_str ? "+" : ""); | |
| } | |
| } | |
| static int _get_usable_gres(int context_inx, int proc_id, | |
| char *tres_bind_str, bitstr_t **usable_gres_ptr, | |
| bitstr_t *gres_bit_alloc, bool get_devices, | |
| stepd_step_rec_t *step, uint64_t *gres_per_bit, | |
| gres_internal_flags_t *flags) | |
| { | |
| char *tres_name = NULL, *sep; | |
| bitstr_t *usable_gres = NULL; | |
| uint32_t plugin_id = gres_context[context_inx].plugin_id; | |
| *usable_gres_ptr = NULL; | |
| if (!gres_bit_alloc || !tres_bind_str) | |
| return SLURM_SUCCESS; | |
| tres_name = xstrdup_printf("gres/%s:", | |
| gres_context[context_inx].gres_name); | |
| sep = xstrstr(tres_bind_str, tres_name); | |
| if (!sep) { | |
| xfree(tres_name); | |
| return SLURM_SUCCESS; | |
| } | |
| sep += strlen(tres_name); | |
| xfree(tres_name); | |
| if (!xstrncasecmp(sep, "verbose,", 8)){ | |
| sep += 8; | |
| if (flags) | |
| *flags |= GRES_INTERNAL_FLAG_VERBOSE; | |
| } | |
| if (step->flags & LAUNCH_GRES_ALLOW_TASK_SHARING) { | |
| if (get_devices) | |
| return SLURM_SUCCESS; | |
| /* | |
| * Overwrite device index setting to use the global node/job GRES | |
| * index, rather than the index local to the task. This ensures | |
| * that the GRES environment variable is set correctly on the | |
| * task when multiple devices are constrained to the task, and | |
| * only the environment variables are bound to specific GRES. | |
| */ | |
| use_local_index = false; | |
| dev_index_mode_set = true; | |
| /* | |
| * Consolidate allocated gres bitstring so that we get the GRES | |
| * device index of the GRES within the context of the job, and | |
| * not within the context of the whole node, unless specifically | |
| * required with the GRES_CONF_UUID flag. | |
| */ | |
| if (!(gres_context[context_inx].config_flags & GRES_CONF_UUID)) | |
| bit_consolidate(gres_bit_alloc); | |
| } | |
| if (gres_context[context_inx].config_flags & GRES_CONF_UUID) { | |
| use_local_index = false; | |
| dev_index_mode_set = true; | |
| } | |
| if (!gres_id_shared(gres_context[context_inx].config_flags)) { | |
| if (!xstrncasecmp(sep, "map_gpu:", 8)) { // Old Syntax | |
| usable_gres = _get_usable_gres_map_or_mask( | |
| (sep + 8), proc_id, gres_bit_alloc, | |
| true, get_devices); | |
| } else if (!xstrncasecmp(sep, "mask_gpu:", 9)) { // Old Syntax | |
| usable_gres = _get_usable_gres_map_or_mask( | |
| (sep + 9), proc_id, gres_bit_alloc, | |
| false, get_devices); | |
| } else if (!xstrncasecmp(sep, "map:", 4)) { | |
| usable_gres = _get_usable_gres_map_or_mask( | |
| (sep + 4), proc_id, gres_bit_alloc, | |
| true, get_devices); | |
| } else if (!xstrncasecmp(sep, "mask:", 5)) { | |
| usable_gres = _get_usable_gres_map_or_mask( | |
| (sep + 5), proc_id, gres_bit_alloc, | |
| false, get_devices); | |
| } else if (!xstrncasecmp(sep, "single:", 7)) { | |
| if (!get_devices && gres_use_local_device_index()) { | |
| usable_gres = bit_alloc( | |
| bit_size(gres_bit_alloc)); | |
| bit_set(usable_gres, 0); | |
| } else { | |
| usable_gres = _get_single_usable_gres( | |
| context_inx, slurm_atoul(sep + 7), | |
| proc_id, step, gres_bit_alloc); | |
| } | |
| } else if (!xstrncasecmp(sep, "closest", 7)) { | |
| usable_gres = _get_closest_usable_gres( | |
| plugin_id, gres_bit_alloc, | |
| step->task[proc_id]->cpu_set); | |
| if (!get_devices && gres_use_local_device_index()) | |
| bit_consolidate(usable_gres); | |
| } else if (!xstrncasecmp(sep, "per_task:", 9)) { | |
| if (!get_devices && gres_use_local_device_index()) { | |
| usable_gres = bit_alloc( | |
| bit_size(gres_bit_alloc)); | |
| bit_nset(usable_gres, 0, | |
| slurm_atoul(sep + 9) - 1); | |
| } else { | |
| usable_gres = _get_gres_per_task( | |
| gres_bit_alloc, slurm_atoul(sep + 9), | |
| step, plugin_id, proc_id); | |
| } | |
| } else if (!xstrncasecmp(sep, "none", 4)) { | |
| usable_gres = bit_copy(gres_bit_alloc); | |
| } else | |
| return SLURM_ERROR; | |
| } else { // Shared gres only support per_task binding for now | |
| if (!xstrncasecmp(sep, "per_task:", 9)) { | |
| usable_gres = _get_shared_gres_per_task( | |
| gres_bit_alloc, gres_per_bit, | |
| slurm_atoul(sep + 9), | |
| step, gpu_plugin_id, proc_id); | |
| if (!get_devices && gres_use_local_device_index()) | |
| bit_consolidate(usable_gres); | |
| } else if (!xstrncasecmp(sep, "none", 4)) { | |
| usable_gres = bit_copy(gres_bit_alloc); | |
| } else | |
| return SLURM_ERROR; | |
| } | |
| if (usable_gres && !bit_set_count(usable_gres)) { | |
| error("Bind request %s does not specify any devices within the allocation for task %d. Binding to the first device in the allocation instead.", | |
| tres_bind_str, proc_id); | |
| if (!get_devices && gres_use_local_device_index()) | |
| bit_set(usable_gres, 0); | |
| else | |
| bit_set(usable_gres, bit_ffs(gres_bit_alloc)); | |
| } | |
| *usable_gres_ptr = usable_gres; | |
| return SLURM_SUCCESS; | |
| } | |
| /* | |
| * Set environment as required for all tasks of a job step | |
| */ | |
| extern void gres_g_step_set_env(stepd_step_rec_t *step) | |
| { | |
| int i; | |
| bitstr_t *gres_bit_alloc = NULL; | |
| uint64_t *gres_per_bit = NULL; | |
| gres_internal_flags_t flags = GRES_INTERNAL_FLAG_NONE; | |
| foreach_gres_accumulate_device_t foreach_gres_accumulate_device = { | |
| .gres_bit_alloc = &gres_bit_alloc, | |
| .gres_per_bit = &gres_per_bit, | |
| .is_job = false, | |
| }; | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| slurm_gres_context_t *gres_ctx = &gres_context[i]; | |
| if (!gres_ctx->ops.step_set_env) | |
| continue; /* No plugin to call */ | |
| if (!step->step_gres_list) { | |
| /* Clear GRES environment variables */ | |
| (*(gres_ctx->ops | |
| .step_set_env))(&step->env, NULL, 0, NULL, | |
| GRES_INTERNAL_FLAG_NONE); | |
| continue; | |
| } | |
| foreach_gres_accumulate_device.plugin_id = gres_ctx->plugin_id; | |
| (void) list_for_each(step->step_gres_list, | |
| _accumulate_gres_device, | |
| &foreach_gres_accumulate_device); | |
| /* | |
| * Do not let MPS or Shard (shared GRES) clear any envs set for | |
| * a GPU (sharing GRES) when a GPU is allocated but an | |
| * MPS/Shard is not. Sharing GRES plugins always run before | |
| * shared GRES, so we don't need to protect MPS/Shard from GPU. | |
| */ | |
| if (gres_id_shared(gres_ctx->config_flags) && | |
| foreach_gres_accumulate_device.sharing_gres_allocated) | |
| flags |= GRES_INTERNAL_FLAG_PROTECT_ENV; | |
| (*(gres_ctx->ops.step_set_env))(&step->env, gres_bit_alloc, | |
| foreach_gres_accumulate_device | |
| .gres_cnt, | |
| gres_per_bit, flags); | |
| foreach_gres_accumulate_device.gres_cnt = 0; | |
| xfree(gres_per_bit); | |
| FREE_NULL_BITMAP(gres_bit_alloc); | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| } | |
| /* | |
| * Change the task's inherited environment (from the step, and set by | |
| * gres_g_step_set_env()). Use this to implement GPU task binding. | |
| */ | |
| extern void gres_g_task_set_env(stepd_step_rec_t *step, int local_proc_id) | |
| { | |
| int i; | |
| bitstr_t *usable_gres = NULL; | |
| bitstr_t *gres_bit_alloc = NULL; | |
| uint64_t *gres_per_bit = NULL; | |
| foreach_gres_accumulate_device_t foreach_gres_accumulate_device = { | |
| .gres_bit_alloc = &gres_bit_alloc, | |
| .gres_per_bit = &gres_per_bit, | |
| .is_job = false, | |
| }; | |
| if (step->accel_bind_type) | |
| _parse_accel_bind_type(step->accel_bind_type, step->tres_bind); | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| for (i = 0; i < gres_context_cnt; i++) { | |
| gres_internal_flags_t flags = GRES_INTERNAL_FLAG_NONE; | |
| slurm_gres_context_t *gres_ctx = &gres_context[i]; | |
| if (!gres_ctx->ops.task_set_env) | |
| continue; /* No plugin to call */ | |
| if (!step->step_gres_list) { | |
| /* Clear GRES environment variables */ | |
| (*(gres_ctx->ops | |
| .task_set_env))(&step->envtp->env, NULL, 0, | |
| NULL, NULL, | |
| GRES_INTERNAL_FLAG_NONE); | |
| continue; | |
| } | |
| foreach_gres_accumulate_device.plugin_id = gres_ctx->plugin_id; | |
| (void) list_for_each(step->step_gres_list, | |
| _accumulate_gres_device, | |
| &foreach_gres_accumulate_device); | |
| if (_get_usable_gres(i, local_proc_id, step->tres_bind, | |
| &usable_gres, gres_bit_alloc, false, step, | |
| gres_per_bit, &flags) == SLURM_ERROR) { | |
| goto next; | |
| } | |
| /* | |
| * Do not let MPS or Shard (shared GRES) clear any envs set for | |
| * a GPU (sharing GRES) when a GPU is allocated but an | |
| * MPS/Shard is not. Sharing GRES plugins always run before | |
| * shared GRES, so we don't need to protect MPS/Shard from GPU. | |
| */ | |
| if (gres_id_shared(gres_ctx->config_flags) && | |
| foreach_gres_accumulate_device.sharing_gres_allocated) | |
| flags |= GRES_INTERNAL_FLAG_PROTECT_ENV; | |
| (*(gres_ctx->ops.task_set_env))(&step->envtp->env, | |
| gres_bit_alloc, | |
| foreach_gres_accumulate_device | |
| .gres_cnt, | |
| gres_per_bit, usable_gres, | |
| flags); | |
| next: | |
| foreach_gres_accumulate_device.gres_cnt = 0; | |
| xfree(gres_per_bit); | |
| FREE_NULL_BITMAP(gres_bit_alloc); | |
| FREE_NULL_BITMAP(usable_gres); | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| } | |
| static void _step_state_log_node(gres_step_state_t *gres_ss, int i) | |
| { | |
| char tmp_str[128]; | |
| if (gres_ss->gres_bit_alloc[i]) { | |
| bit_fmt(tmp_str, sizeof(tmp_str), gres_ss->gres_bit_alloc[i]); | |
| info(" gres_bit_alloc[%d]:%s of %d", i, tmp_str, | |
| (int)bit_size(gres_ss->gres_bit_alloc[i])); | |
| } else | |
| info(" gres_bit_alloc[%d]:NULL", i); | |
| if (gres_ss->gres_per_bit_alloc && gres_ss->gres_per_bit_alloc[i]) { | |
| for (int j = 0; | |
| (j = bit_ffs_from_bit(gres_ss->gres_bit_alloc[i], j)) >= 0; | |
| j++) { | |
| info(" gres_per_bit_alloc[%d][%d]:%" PRIu64, i, j, | |
| gres_ss->gres_per_bit_alloc[i][j]); | |
| } | |
| } | |
| } | |
| static int _step_state_log(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_step = x; | |
| gres_step_state_t *gres_ss = gres_state_step->gres_data; | |
| char *gres_name = gres_state_step->gres_name; | |
| slurm_step_id_t *step_id = arg; | |
| int i; | |
| xassert(gres_ss); | |
| info("gres:%s type:%s(%u) %ps flags:%s state", gres_name, | |
| gres_ss->type_name, gres_ss->type_id, step_id, | |
| gres_flags2str(gres_ss->flags)); | |
| if (gres_ss->cpus_per_gres) | |
| info(" cpus_per_gres:%u", gres_ss->cpus_per_gres); | |
| if (gres_ss->gres_per_step) | |
| info(" gres_per_step:%"PRIu64, gres_ss->gres_per_step); | |
| if (gres_ss->gres_per_node) { | |
| info(" gres_per_node:%"PRIu64" node_cnt:%u", | |
| gres_ss->gres_per_node, gres_ss->node_cnt); | |
| } | |
| if (gres_ss->gres_per_socket) | |
| info(" gres_per_socket:%"PRIu64, gres_ss->gres_per_socket); | |
| if (gres_ss->gres_per_task) | |
| info(" gres_per_task:%"PRIu64, gres_ss->gres_per_task); | |
| if (gres_ss->mem_per_gres) | |
| info(" mem_per_gres:%"PRIu64, gres_ss->mem_per_gres); | |
| if (gres_ss->node_in_use == NULL) | |
| info(" node_in_use:NULL"); | |
| else if (gres_ss->gres_bit_alloc == NULL) | |
| info(" gres_bit_alloc:NULL"); | |
| else { | |
| for (i = 0; i < gres_ss->node_cnt; i++) { | |
| if (bit_test(gres_ss->node_in_use, i)) | |
| _step_state_log_node(gres_ss, i); | |
| } | |
| } | |
| return 0; | |
| } | |
| /* | |
| * Log a step's current gres state | |
| * IN gres_list - generated by gres_stepmgr_step_alloc() | |
| * IN job_id - job's ID | |
| * IN step_id - step's ID | |
| */ | |
| extern void gres_step_state_log(list_t *gres_list, uint32_t job_id, | |
| uint32_t step_id) | |
| { | |
| slurm_step_id_t tmp_step_id = { | |
| .job_id = job_id, | |
| .step_het_comp = NO_VAL, | |
| .step_id = step_id, | |
| }; | |
| if (!(slurm_conf.debug_flags & DEBUG_FLAG_GRES) || !gres_list) | |
| return; | |
| (void) list_for_each(gres_list, _step_state_log, &tmp_step_id); | |
| } | |
| static int _foreach_gres_init(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_job = x; | |
| bool *rc = arg; | |
| gres_job_state_t *gres_js = gres_state_job->gres_data; | |
| if (!gres_js->gres_per_job) | |
| return 0; | |
| gres_js->total_gres = 0; | |
| gres_js->gres_per_job_segment = 0; | |
| *rc = true; | |
| return 0; | |
| } | |
| /* | |
| * Clear GRES allocation info for all job GRES at start of scheduling cycle | |
| * Return TRUE if any gres_per_job constraints to satisfy | |
| */ | |
| extern bool gres_sched_init(list_t *job_gres_list) | |
| { | |
| bool rc = false; | |
| if (!job_gres_list) | |
| return rc; | |
| (void) list_for_each(job_gres_list, _foreach_gres_init, &rc); | |
| return rc; | |
| } | |
| static int _foreach_gres_reset(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_job = x; | |
| gres_job_state_t *gres_js = gres_state_job->gres_data; | |
| if (gres_js->gres_per_job) | |
| gres_js->total_gres = 0; | |
| return 0; | |
| } | |
| extern void gres_sched_reset(list_t *job_gres_list) | |
| { | |
| if (!job_gres_list) | |
| return; | |
| (void) list_for_each(job_gres_list, _foreach_gres_reset, NULL); | |
| } | |
| static int _foreach_gres_segment_set(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_job = x; | |
| gres_job_state_t *gres_js = gres_state_job->gres_data; | |
| int segment_cnt = *(int *) arg; | |
| if (!gres_js->gres_per_job) | |
| return 0; | |
| /* | |
| * On rejection (<0) earlier entries keep a non-zero | |
| * gres_per_job_segment, but the caller aborts the evaluation and the | |
| * next gres_sched_init() clears it before it can be used. | |
| */ | |
| if (gres_js->gres_per_job % segment_cnt) | |
| return -1; /* Not evenly divisible */ | |
| gres_js->gres_per_job_segment = gres_js->gres_per_job / segment_cnt; | |
| return 0; | |
| } | |
| extern bool gres_sched_segment_set(list_t *job_gres_list, int segment_cnt) | |
| { | |
| if (!job_gres_list || (segment_cnt <= 1)) | |
| return true; | |
| return (list_for_each(job_gres_list, _foreach_gres_segment_set, | |
| &segment_cnt) >= 0); | |
| } | |
| /* | |
| * Return TRUE if this plugin ID consumes GRES count > 1 for a single device | |
| * file (e.g. MPS) | |
| */ | |
| extern bool gres_id_shared(uint32_t config_flags) | |
| { | |
| if (config_flags & GRES_CONF_SHARED) | |
| return true; | |
| return false; | |
| } | |
| /* | |
| * Return TRUE if this plugin ID shares resources with another GRES that | |
| * consumes subsets of its resources (e.g. GPU) | |
| */ | |
| extern bool gres_id_sharing(uint32_t plugin_id) | |
| { | |
| if (plugin_id == gpu_plugin_id) | |
| return true; | |
| return false; | |
| } | |
| static int _foreach_node_count(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_node = x; | |
| foreach_node_count_t *foreach_node_count = arg; | |
| gres_node_state_t *gres_ns = gres_state_node->gres_data; | |
| uint64_t val = 0; | |
| xassert(gres_ns); | |
| switch (foreach_node_count->val_type) { | |
| case GRES_VAL_TYPE_FOUND: | |
| val = gres_ns->gres_cnt_found; | |
| break; | |
| case GRES_VAL_TYPE_CONFIG: | |
| val = gres_ns->gres_cnt_config; | |
| break; | |
| case GRES_VAL_TYPE_AVAIL: | |
| val = gres_ns->gres_cnt_avail; | |
| break; | |
| case GRES_VAL_TYPE_ALLOC: | |
| val = gres_ns->gres_cnt_alloc; | |
| break; | |
| } | |
| foreach_node_count->gres_count_ids[foreach_node_count->index] = | |
| gres_state_node->plugin_id; | |
| foreach_node_count->gres_count_vals[foreach_node_count->index] = val; | |
| if (++foreach_node_count->index >= foreach_node_count->array_len) | |
| return -1; | |
| return 0; | |
| } | |
| /* | |
| * Fill in an array of GRES type ids contained within the given node gres_list | |
| * and an array of corresponding counts of those GRES types. | |
| * IN gres_list - a List of GRES types found on a node. | |
| * IN arrlen - Length of the arrays (the number of elements in the gres_list). | |
| * IN gres_count_ids, gres_count_vals - the GRES type ID's and values found | |
| * in the gres_list. | |
| * IN val_type - Type of value desired, see GRES_VAL_TYPE_* | |
| * RET SLURM_SUCCESS or error code | |
| */ | |
| extern int gres_node_count(list_t *gres_list, int arr_len, | |
| uint32_t *gres_count_ids, | |
| uint64_t *gres_count_vals, | |
| int val_type) | |
| { | |
| foreach_node_count_t foreach_node_count = { | |
| .array_len = arr_len, | |
| .gres_count_ids = gres_count_ids, | |
| .gres_count_vals = gres_count_vals, | |
| .val_type = val_type, | |
| }; | |
| if (arr_len <= 0) | |
| return EINVAL; | |
| (void) list_for_each(gres_list, _foreach_node_count, | |
| &foreach_node_count); | |
| return SLURM_SUCCESS; | |
| } | |
| static void _gres_device_pack( | |
| void *in, uint16_t protocol_version, buf_t *buffer) | |
| { | |
| gres_device_t *gres_device = in; | |
| /* DON'T PACK gres_device->alloc */ | |
| pack32(gres_device->index, buffer); | |
| pack32(gres_device->dev_num, buffer); | |
| pack32(gres_device->dev_desc.type, buffer); | |
| pack32(gres_device->dev_desc.major, buffer); | |
| pack32(gres_device->dev_desc.minor, buffer); | |
| packstr(gres_device->path, buffer); | |
| packstr(gres_device->unique_id, buffer); | |
| pack32(gres_device->flags, buffer); | |
| packstr(gres_device->pci_addr, buffer); | |
| pack_bit_str_hex(gres_device->cpus_bitmap, buffer); | |
| packbool((gres_device->dmem != NULL), buffer); | |
| if (gres_device->dmem) { | |
| pack64(gres_device->dmem->capacity, buffer); | |
| packbool(gres_device->dmem->from_conf, buffer); | |
| packstr(gres_device->dmem->region, buffer); | |
| pack64(gres_device->dmem->shards, buffer); | |
| pack64(gres_device->dmem->slice, buffer); | |
| pack32((uint32_t) gres_device->dmem->state, buffer); | |
| } | |
| } | |
| extern void gres_send_stepd(buf_t *buffer, list_t *gres_devices) | |
| { | |
| slurm_pack_list(gres_devices, _gres_device_pack, buffer, | |
| SLURM_PROTOCOL_VERSION); | |
| } | |
| static int _gres_device_unpack(void **object, uint16_t protocol_version, | |
| buf_t *buffer) | |
| { | |
| uint32_t uint32_tmp = 0; | |
| bool has_dmem = false; | |
| gres_device_t *gres_device = xmalloc(sizeof(gres_device_t)); | |
| safe_unpack32(&uint32_tmp, buffer); | |
| gres_device->index = uint32_tmp; | |
| safe_unpack32(&uint32_tmp, buffer); | |
| gres_device->dev_num = uint32_tmp; | |
| safe_unpack32(&uint32_tmp, buffer); | |
| gres_device->dev_desc.type = uint32_tmp; | |
| safe_unpack32(&uint32_tmp, buffer); | |
| gres_device->dev_desc.major = uint32_tmp; | |
| safe_unpack32(&uint32_tmp, buffer); | |
| gres_device->dev_desc.minor = uint32_tmp; | |
| safe_unpackstr(&gres_device->path, buffer); | |
| safe_unpackstr(&gres_device->unique_id, buffer); | |
| safe_unpack32(&gres_device->flags, buffer); | |
| safe_unpackstr(&gres_device->pci_addr, buffer); | |
| unpack_bit_str_hex(&gres_device->cpus_bitmap, buffer); | |
| safe_unpackbool(&has_dmem, buffer); | |
| if (has_dmem) { | |
| gres_device->dmem = xmalloc(sizeof(*gres_device->dmem)); | |
| safe_unpack64(&gres_device->dmem->capacity, buffer); | |
| safe_unpackbool(&gres_device->dmem->from_conf, buffer); | |
| safe_unpackstr(&gres_device->dmem->region, buffer); | |
| safe_unpack64(&gres_device->dmem->shards, buffer); | |
| safe_unpack64(&gres_device->dmem->slice, buffer); | |
| safe_unpack32(&uint32_tmp, buffer); | |
| gres_device->dmem->state = (gres_dmem_state_t) uint32_tmp; | |
| } | |
| /* info("adding %d %s %s", gres_device->dev_num, */ | |
| /* gres_device->major, gres_device->path); */ | |
| *object = gres_device; | |
| return SLURM_SUCCESS; | |
| unpack_error: | |
| error("%s: failed", __func__); | |
| destroy_gres_device(gres_device); | |
| return SLURM_ERROR; | |
| } | |
| extern void gres_recv_stepd(buf_t *buffer, list_t **gres_devices) | |
| { | |
| (void) slurm_unpack_list(gres_devices, _gres_device_unpack, | |
| destroy_gres_device, | |
| buffer, SLURM_PROTOCOL_VERSION); | |
| } | |
| /* Send GRES information to slurmstepd on the specified file descriptor */ | |
| extern void gres_g_send_stepd(int fd, slurm_msg_t *msg) | |
| { | |
| int len; | |
| uint32_t step_id; | |
| cred_data_enum_t check; | |
| slurm_cred_t *cred = NULL; | |
| /* Setup the gres_device list and other plugin-specific data */ | |
| xassert(gres_context_cnt >= 0); | |
| slurm_mutex_lock(&gres_context_lock); | |
| xassert(gres_context_buf); | |
| len = get_buf_offset(gres_context_buf); | |
| safe_write(fd, &len, sizeof(len)); | |
| safe_write(fd, get_buf_data(gres_context_buf), len); | |
| slurm_mutex_unlock(&gres_context_lock); | |
| if (msg->msg_type == REQUEST_BATCH_JOB_LAUNCH) { | |
| batch_job_launch_msg_t *job = msg->data; | |
| step_id = SLURM_BATCH_SCRIPT; | |
| cred = job->cred; | |
| } else { | |
| launch_tasks_request_msg_t *job = msg->data; | |
| step_id = job->step_id.step_id; | |
| cred = job->cred; | |
| } | |
| /* If we are a special step we get the JOB_GRES_LIST */ | |
| if (step_id >= SLURM_MAX_NORMAL_STEP_ID) | |
| check = CRED_DATA_JOB_GRES_LIST; | |
| else | |
| check = CRED_DATA_STEP_GRES_LIST; | |
| /* Send the merged slurm.conf/gres.conf and autodetect data */ | |
| if (slurm_cred_get(cred, check)) { | |
| len = get_buf_offset(gres_conf_buf); | |
| safe_write(fd, &len, sizeof(len)); | |
| safe_write(fd, get_buf_data(gres_conf_buf), len); | |
| } | |
| return; | |
| rwfail: | |
| error("%s: failed", __func__); | |
| slurm_mutex_unlock(&gres_context_lock); | |
| return; | |
| } | |
| /* Receive GRES information from slurmd on the specified file descriptor */ | |
| extern int gres_g_recv_stepd(int fd, slurm_msg_t *msg) | |
| { | |
| int len, rc = SLURM_ERROR; | |
| buf_t *buffer = NULL; | |
| uint32_t step_id; | |
| cred_data_enum_t check; | |
| slurm_cred_t *cred = NULL; | |
| slurm_mutex_lock(&gres_context_lock); | |
| safe_read(fd, &len, sizeof(int)); | |
| if ((len <= 0) || (len > MAX_MSG_SIZE)) { | |
| error("%s: invalid context length %d", __func__, len); | |
| goto rwfail; | |
| } | |
| buffer = init_buf(len); | |
| safe_read(fd, buffer->head, len); | |
| rc = _unpack_context_buf(buffer); | |
| if (rc == SLURM_ERROR) | |
| goto rwfail; | |
| FREE_NULL_BUFFER(buffer); | |
| if (msg->msg_type == REQUEST_BATCH_JOB_LAUNCH) { | |
| batch_job_launch_msg_t *job = msg->data; | |
| step_id = SLURM_BATCH_SCRIPT; | |
| cred = job->cred; | |
| } else { | |
| launch_tasks_request_msg_t *job = msg->data; | |
| step_id = job->step_id.step_id; | |
| cred = job->cred; | |
| } | |
| /* If we are a special step we get the JOB_GRES_LIST */ | |
| if (step_id >= SLURM_MAX_NORMAL_STEP_ID) | |
| check = CRED_DATA_JOB_GRES_LIST; | |
| else | |
| check = CRED_DATA_STEP_GRES_LIST; | |
| /* Recv the merged slurm.conf/gres.conf and autodetect data */ | |
| if (slurm_cred_get(cred, check)) { | |
| safe_read(fd, &len, sizeof(int)); | |
| if ((len <= 0) || (len > MAX_MSG_SIZE)) { | |
| error("%s: invalid gres.conf length %d", | |
| __func__, len); | |
| goto rwfail; | |
| } | |
| buffer = init_buf(len); | |
| safe_read(fd, buffer->head, len); | |
| rc = _unpack_gres_conf(buffer); | |
| if (rc == SLURM_ERROR) | |
| goto rwfail; | |
| FREE_NULL_BUFFER(buffer); | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| /* Set debug flags only */ | |
| (void) gres_init(); | |
| rc = _load_specific_gres_plugins(NULL); | |
| return rc; | |
| rwfail: | |
| FREE_NULL_BUFFER(buffer); | |
| error("%s: failed", __func__); | |
| slurm_mutex_unlock(&gres_context_lock); | |
| /* Set debug flags only */ | |
| (void) gres_init(); | |
| rc = _load_specific_gres_plugins(NULL); | |
| return rc; | |
| } | |
| /* Get generic GRES data types here. Call the plugin for others */ | |
| static int _get_step_info(gres_step_state_t *gres_ss, | |
| uint32_t node_inx, enum gres_step_data_type data_type, | |
| void *data) | |
| { | |
| uint64_t *u64_data = (uint64_t *) data; | |
| bitstr_t **bit_data = (bitstr_t **) data; | |
| int rc = SLURM_SUCCESS; | |
| if (!gres_ss || !data) | |
| return EINVAL; | |
| if (node_inx >= gres_ss->node_cnt) | |
| return ESLURM_INVALID_NODE_COUNT; | |
| switch (data_type) { | |
| case GRES_STEP_DATA_COUNT: | |
| *u64_data += gres_ss->gres_cnt_node_alloc[node_inx]; | |
| break; | |
| case GRES_STEP_DATA_BITMAP: | |
| if (gres_ss->gres_bit_alloc) { | |
| if (!*bit_data) { | |
| *bit_data = bit_copy( | |
| gres_ss->gres_bit_alloc[node_inx]); | |
| } else { | |
| xassert(bit_size(*bit_data) == | |
| bit_size(gres_ss->gres_bit_alloc[ | |
| node_inx])); | |
| bit_or(*bit_data, | |
| gres_ss->gres_bit_alloc[node_inx]); | |
| } | |
| } | |
| break; | |
| default: | |
| error("%s: unknown enum given %d", __func__, data_type); | |
| rc = EINVAL; | |
| break; | |
| } | |
| return rc; | |
| } | |
| static int _foreach_get_step_info(void *x, void *arg) | |
| { | |
| gres_state_t *gres_state_step = x; | |
| foreach_step_info_t *foreach_step_info = arg; | |
| if (gres_state_step->plugin_id != foreach_step_info->plugin_id) | |
| return 0; | |
| foreach_step_info->rc = _get_step_info(gres_state_step->gres_data, | |
| foreach_step_info->node_inx, | |
| foreach_step_info->data_type, | |
| foreach_step_info->data); | |
| if (foreach_step_info->rc != SLURM_SUCCESS) | |
| return -1; | |
| return 0; | |
| } | |
| /* | |
| * get data from a step's GRES data structure | |
| * IN step_gres_list - step's GRES data structure | |
| * IN gres_name - name of a GRES type | |
| * IN node_inx - zero-origin index of the node within the job's allocation | |
| * for which data is desired. Note this can differ from the step's | |
| * node allocation index. | |
| * IN data_type - type of data to get from the step's data | |
| * OUT data - pointer to the data from step's GRES data structure | |
| * DO NOT FREE: This is a pointer into the step's data structure | |
| * RET - SLURM_SUCCESS or error code | |
| */ | |
| extern int gres_get_step_info(list_t *step_gres_list, char *gres_name, | |
| uint32_t node_inx, | |
| enum gres_step_data_type data_type, void *data) | |
| { | |
| foreach_step_info_t foreach_step_info = { | |
| .data = data, | |
| .data_type = data_type, | |
| .node_inx = node_inx, | |
| .rc = ESLURM_INVALID_GRES, | |
| }; | |
| if (data == NULL) | |
| return EINVAL; | |
| if (step_gres_list == NULL) /* No GRES allocated */ | |
| return ESLURM_INVALID_GRES; | |
| xassert(gres_context_cnt >= 0); | |
| foreach_step_info.plugin_id = gres_build_id(gres_name); | |
| (void) list_for_each(step_gres_list, _foreach_get_step_info, | |
| &foreach_step_info); | |
| return foreach_step_info.rc; | |
| } | |
| extern uint32_t gres_get_autodetect_flags(void) | |
| { | |
| return autodetect_flags; | |
| } | |
| extern void gres_autodetect_flags_set_gpu(uint32_t gpu_flags_part) | |
| { | |
| autodetect_flags = (autodetect_flags & ~GRES_AUTODETECT_GPU_FLAGS) | | |
| (gpu_flags_part & GRES_AUTODETECT_GPU_FLAGS); | |
| } | |
| extern void gres_clear_tres_cnt(uint64_t *tres_cnt, bool locked) | |
| { | |
| assoc_mgr_lock_t locks = { .tres = READ_LOCK }; | |
| /* | |
| * If gres_context_lock is ever locked/unlocked here, it should happen | |
| * in between assoc_mgr_lock() and before assoc_mgr_unlock(). | |
| */ | |
| if (!locked) | |
| assoc_mgr_lock(&locks); | |
| /* Initialize all GRES counters to zero. Increment them later. */ | |
| for (int i = 0; i < g_tres_count; ++i) { | |
| /* Skip all non-GRES TRES */ | |
| if (xstrcasecmp(assoc_mgr_tres_array[i]->type, "gres")) | |
| continue; | |
| tres_cnt[i] = 0; | |
| } | |
| if (!locked) | |
| assoc_mgr_unlock(&locks); | |
| } | |
| extern char *gres_device_id2str(gres_device_id_t *gres_dev) | |
| { | |
| char *res = NULL; | |
| xstrfmtcat(res, "%c %u:%u rwm", | |
| gres_dev->type == DEV_TYPE_BLOCK ? 'b' : 'c', | |
| gres_dev->major, gres_dev->minor); | |
| return res; | |
| } | |
| /* Free memory for gres_device_t record */ | |
| extern void destroy_gres_device(void *gres_device_ptr) | |
| { | |
| gres_device_t *gres_device = (gres_device_t *) gres_device_ptr; | |
| if (!gres_device) | |
| return; | |
| FREE_NULL_BITMAP(gres_device->cpus_bitmap); | |
| if (gres_device->dmem) { | |
| xfree(gres_device->dmem->region); | |
| xfree(gres_device->dmem); | |
| } | |
| xfree(gres_device->path); | |
| xfree(gres_device->pci_addr); | |
| xfree(gres_device->unique_id); | |
| xfree(gres_device); | |
| } | |
| /* Destroy a gres_slurmd_conf_t record, free it's memory */ | |
| extern void destroy_gres_slurmd_conf(void *x) | |
| { | |
| gres_slurmd_conf_t *p = (gres_slurmd_conf_t *) x; | |
| xassert(p); | |
| xfree(p->chunk_order); | |
| xfree(p->chunk_sizes); | |
| xfree(p->cpus); | |
| FREE_NULL_BITMAP(p->cpus_bitmap); | |
| xfree(p->dmem_region); | |
| xfree(p->file); /* Only used by slurmd */ | |
| xfree(p->links); | |
| xfree(p->name); | |
| xfree(p->pci_addr); | |
| xfree(p->type_name); | |
| xfree(p->unique_id); | |
| xfree(p); | |
| } | |
| /* | |
| * Convert GRES config_flags to a string. The pointer returned references local | |
| * storage in this function, which is not re-entrant. | |
| */ | |
| extern char *gres_flags2str(uint32_t config_flags) | |
| { | |
| static char flag_str[256]; | |
| char *sep = ""; | |
| flag_str[0] = '\0'; | |
| if (config_flags & GRES_CONF_COUNT_ONLY) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "CountOnly"); | |
| sep = ","; | |
| } | |
| if (config_flags & GRES_CONF_EXPLICIT) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "Explicit"); | |
| sep = ","; | |
| } | |
| if (config_flags & GRES_CONF_HAS_FILE) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "HAS_FILE"); | |
| sep = ","; | |
| } | |
| if (config_flags & GRES_CONF_HAS_MULT) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "HAS_MULT"); | |
| sep = ","; | |
| } | |
| if (config_flags & GRES_CONF_LOADED) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "LOADED"); | |
| sep = ","; | |
| } | |
| if (config_flags & GRES_CONF_HAS_TYPE) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "HAS_TYPE"); | |
| sep = ","; | |
| } | |
| if (config_flags & GRES_CONF_ENV_NVML) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "ENV_NVML"); | |
| sep = ","; | |
| } | |
| if (config_flags & GRES_CONF_UUID) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "UUID"); | |
| sep = ","; | |
| } | |
| if (config_flags & GRES_CONF_ENV_RSMI) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "ENV_RSMI"); | |
| sep = ","; | |
| } | |
| if (config_flags & GRES_CONF_ENV_ONEAPI) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "ENV_ONEAPI"); | |
| sep = ","; | |
| } | |
| if (config_flags & GRES_CONF_ENV_OPENCL) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "ENV_OPENCL"); | |
| sep = ","; | |
| } | |
| if (config_flags & GRES_CONF_ENV_DEF) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "ENV_DEFAULT"); | |
| sep = ","; | |
| } | |
| if (config_flags & GRES_CONF_SHARED) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "SHARED"); | |
| sep = ","; | |
| } | |
| if (config_flags & GRES_CONF_ONE_SHARING) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "ONE_SHARING"); | |
| sep = ","; | |
| } | |
| if (config_flags & GRES_CONF_MIG) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "MIG"); | |
| sep = ","; | |
| } | |
| if (config_flags & GRES_CONF_GENERATED) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "GENERATED"); | |
| sep = ","; | |
| } | |
| if (config_flags & GRES_CONF_AUTODETECT) { | |
| strcat(flag_str, sep); | |
| strcat(flag_str, "AUTODETECT"); | |
| sep = ","; | |
| } | |
| return flag_str; | |
| } | |
| /* | |
| * Creates a gres_slurmd_conf_t record to add to a list of gres_slurmd_conf_t | |
| * records | |
| */ | |
| extern void add_gres_to_list(list_t *gres_list, | |
| gres_slurmd_conf_t *gres_slurmd_conf_in) | |
| { | |
| gres_slurmd_conf_t *gres_slurmd_conf; | |
| bool use_empty_first_record = false; | |
| /* | |
| * If the first record already exists and has a count of 0 then | |
| * overwrite it. | |
| * This is a placeholder record created in _merge_config() | |
| */ | |
| gres_slurmd_conf = list_peek(gres_list); | |
| if (gres_slurmd_conf && (gres_slurmd_conf->count == 0)) | |
| use_empty_first_record = true; | |
| else | |
| gres_slurmd_conf = xmalloc(sizeof(gres_slurmd_conf_t)); | |
| gres_slurmd_conf->cpu_cnt = gres_slurmd_conf_in->cpu_cnt; | |
| if (gres_slurmd_conf_in->cpus_bitmap) { | |
| bitstr_t *cpu_aff = bit_copy(gres_slurmd_conf_in->cpus_bitmap); | |
| /* | |
| * Size down (or possibly up) cpus_bitmap, if necessary, so that | |
| * the size of cpus_bitmap for system-detected devices matches | |
| * the size of cpus_bitmap for configured devices. | |
| */ | |
| if (bit_size(cpu_aff) != gres_slurmd_conf_in->cpu_cnt) { | |
| /* Calculate minimum size to hold CPU affinity */ | |
| int64_t size = bit_fls(cpu_aff) + 1; | |
| if (size > gres_slurmd_conf_in->cpu_cnt) { | |
| char *cpu_str = bit_fmt_hexmask_trim(cpu_aff); | |
| fatal("This CPU affinity bitmask (%s) does not fit within the CPUs configured for this node (%d). Make sure that the node's CPU count is configured correctly.", | |
| cpu_str, gres_slurmd_conf_in->cpu_cnt); | |
| xfree(cpu_str); | |
| } | |
| bit_realloc(cpu_aff, gres_slurmd_conf_in->cpu_cnt); | |
| } | |
| gres_slurmd_conf->cpus_bitmap = cpu_aff; | |
| } | |
| /* Set default env flags, if necessary */ | |
| if ((gres_slurmd_conf_in->config_flags & GRES_CONF_ENV_DEF) && | |
| ((gres_slurmd_conf_in->config_flags & GRES_CONF_ENV_SET) != | |
| GRES_CONF_ENV_SET)) | |
| gres_slurmd_conf_in->config_flags |= GRES_CONF_ENV_SET; | |
| gres_slurmd_conf->config_flags = gres_slurmd_conf_in->config_flags; | |
| if (gres_slurmd_conf_in->file) { | |
| hostlist_t *hl = hostlist_create(gres_slurmd_conf_in->file); | |
| gres_slurmd_conf->config_flags |= GRES_CONF_HAS_FILE; | |
| if (hostlist_count(hl) > 1) | |
| gres_slurmd_conf->config_flags |= GRES_CONF_HAS_MULT; | |
| hostlist_destroy(hl); | |
| } | |
| if (gres_slurmd_conf_in->type_name) | |
| gres_slurmd_conf->config_flags |= GRES_CONF_HAS_TYPE; | |
| gres_slurmd_conf->cpus = xstrdup(gres_slurmd_conf_in->cpus); | |
| gres_slurmd_conf->dmem_region = | |
| xstrdup(gres_slurmd_conf_in->dmem_region); | |
| gres_slurmd_conf->pci_addr = xstrdup(gres_slurmd_conf_in->pci_addr); | |
| gres_slurmd_conf->type_name = xstrdup(gres_slurmd_conf_in->type_name); | |
| gres_slurmd_conf->name = xstrdup(gres_slurmd_conf_in->name); | |
| gres_slurmd_conf->file = xstrdup(gres_slurmd_conf_in->file); | |
| gres_slurmd_conf->links = xstrdup(gres_slurmd_conf_in->links); | |
| gres_slurmd_conf->chunk_order = | |
| xstrdup(gres_slurmd_conf_in->chunk_order); | |
| gres_slurmd_conf->chunk_sizes = | |
| xstrdup(gres_slurmd_conf_in->chunk_sizes); | |
| gres_slurmd_conf->unique_id = xstrdup(gres_slurmd_conf_in->unique_id); | |
| gres_slurmd_conf->count = gres_slurmd_conf_in->count; | |
| gres_slurmd_conf->plugin_id = gres_build_id(gres_slurmd_conf_in->name); | |
| if (!use_empty_first_record) | |
| list_append(gres_list, gres_slurmd_conf); | |
| } | |
| extern char *gres_prepend_tres_type(const char *gres_str) | |
| { | |
| char *output = NULL; | |
| if (gres_str) { | |
| output = xstrdup_printf("gres/%s", gres_str); | |
| xstrsubstituteall(output, ",", ",gres/"); | |
| xstrsubstituteall(output, "gres/gres/", "gres/"); | |
| } | |
| return output; | |
| } | |
| extern bool gres_use_busy_dev(gres_state_t *gres_state_node, | |
| bool use_total_gres) | |
| { | |
| gres_node_state_t *gres_ns = gres_state_node->gres_data; | |
| if (!use_total_gres && | |
| gres_id_shared(gres_state_node->config_flags) && | |
| (gres_state_node->config_flags & GRES_CONF_ONE_SHARING) && | |
| (gres_ns->gres_cnt_alloc != 0)) { | |
| /* We must use the ONE already active GRES of this type */ | |
| return true; | |
| } | |
| return false; | |
| } | |
| /* Return the plugin id made from gres_build_id("gpu") */ | |
| extern uint32_t gres_get_gpu_plugin_id(void) | |
| { | |
| return gpu_plugin_id; | |
| } | |
| extern bool gres_valid_name(char *name) | |
| { | |
| if (!name || (name[0] == '\0')) | |
| return false; | |
| if (gres_get_system_cnt(name, false) != NO_VAL64) | |
| return true; | |
| return false; | |
| } | |
| static void _parse_gres_conf_values(char *gres_str, char *node_name) | |
| { | |
| char *key, *saveptr; | |
| gres_slurmd_conf_t *gsc; | |
| gsc = xmalloc(sizeof(gres_slurmd_conf_t)); | |
| for (key = strtok_r(gres_str, ",", &saveptr); | |
| key; | |
| key = strtok_r(NULL, ",", &saveptr)) { | |
| char *val = strchr(key, '='); | |
| if (!val) { | |
| error("Missing value in str: %s", key); | |
| continue; | |
| } | |
| *val = '\0'; | |
| val++; | |
| if (!xstrncasecmp(key, "count", strlen("count"))) | |
| s_p_handle_uint64(&gsc->count, key, val); | |
| else if (!xstrncasecmp(key, "cpu_cnt", strlen("cpu_cnt"))) | |
| s_p_handle_uint32(&gsc->cpu_cnt, key, val); | |
| else if (!xstrncasecmp(key, "flags", strlen("flags"))) | |
| s_p_handle_uint32(&gsc->config_flags, key, val); | |
| else if (!xstrncasecmp(key, "cpus", strlen("cpus"))) { | |
| xfree(gsc->cpus); | |
| gsc->cpus = xstrdup(val); | |
| } else if (!xstrncasecmp(key, "file", strlen("file"))) { | |
| xfree(gsc->file); | |
| gsc->file = xstrdup(val); | |
| } else if (!xstrncasecmp(key, "links", strlen("links"))) { | |
| xfree(gsc->links); | |
| gsc->links = xstrdup(val); | |
| } else if (!xstrncasecmp(key, "name", strlen("name"))) { | |
| xfree(gsc->name); | |
| gsc->name = xstrdup(val); | |
| } else if (!xstrncasecmp(key, "type", strlen("type_name"))) { | |
| xfree(gsc->type_name); | |
| gsc->type_name = xstrdup(val); | |
| } else if (!xstrncasecmp(key, "unique_id", | |
| strlen("unique_id"))) { | |
| xfree(gsc->unique_id); | |
| gsc->unique_id = xstrdup(val); | |
| } else if (!xstrncasecmp(key, "plugin_id", strlen("plugin_id"))) | |
| s_p_handle_uint32(&gsc->plugin_id, key, val); | |
| else | |
| error("invalid key: %s", key); | |
| } | |
| if (gsc->file) | |
| gsc->config_flags |= GRES_CONF_HAS_FILE; | |
| if (gsc->type_name) | |
| gsc->config_flags |= GRES_CONF_HAS_TYPE; | |
| if (!gsc->plugin_id) { | |
| for (int i = 0; i < gres_context_cnt; i++) { | |
| if (!xstrcmp(gres_context[i].gres_name, gsc->name)) { | |
| gsc->plugin_id = gres_context[i].plugin_id; | |
| break; | |
| } | |
| } | |
| xassert(gsc->plugin_id); | |
| } | |
| if (_add_to_gres_conf_list(gsc, node_name)) | |
| destroy_gres_slurmd_conf(gsc); | |
| } | |
| extern void gres_add_dynamic_gres(char *gres_str, char *node_name) | |
| { | |
| char *tmp_gres_str; | |
| char *tok, *saveptr; | |
| FREE_NULL_LIST(gres_conf_list); | |
| gres_conf_list = list_create(destroy_gres_slurmd_conf); | |
| tmp_gres_str = xstrdup(gres_str); | |
| slurm_mutex_lock(&gres_context_lock); | |
| for (tok = strtok_r(gres_str, "+", &saveptr); | |
| tok; | |
| tok = strtok_r(NULL, "+", &saveptr)) { | |
| _parse_gres_conf_values(tok, node_name); | |
| } | |
| slurm_mutex_unlock(&gres_context_lock); | |
| xfree(tmp_gres_str); | |
| } |