| /*****************************************************************************\ |
| * job_resources.c - Functions for structures dealing with resources unique to |
| * the select plugin. |
| ***************************************************************************** |
| * Copyright (C) SchedMD LLC. |
| * Derived in large part from select/cons_[res|tres] plugins |
| * |
| * 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 "select_cons_tres.h" |
| |
| #include "src/slurmctld/slurmctld.h" |
| #include "src/slurmctld/licenses.h" |
| |
| #include "src/stepmgr/gres_stepmgr.h" |
| |
| bool select_state_initializing = true; |
| |
| typedef enum { |
| HANDLE_JOB_RES_ADD, |
| HANDLE_JOB_RES_REM, |
| HANDLE_JOB_RES_TEST |
| } handle_job_res_t; |
| |
| static bitstr_t *_create_core_bitmap(int node_inx) |
| { |
| xassert(node_inx < node_record_count); |
| |
| if (!node_record_table_ptr[node_inx]) |
| return NULL; |
| |
| return bit_alloc(node_record_table_ptr[node_inx]->tot_cores); |
| } |
| |
| /* |
| * Handle job resource allocation to record of resources allocated to all nodes |
| * IN job_resrcs_ptr - resources allocated to a job |
| * IN r_ptr - row we are trying to fit |
| * IN/OUT r_ptr->row_bitmap - bitmap array (one per node) of |
| * available cores, allocated as needed |
| * IN type - add/rem/test |
| * RET 1 on success, 0 otherwise |
| */ |
| static int _handle_job_res(job_resources_t *job_resrcs_ptr, |
| part_row_data_t *r_ptr, |
| handle_job_res_t type) |
| { |
| int c, c_off = 0; |
| bitstr_t **core_array; |
| uint16_t cores_per_node; |
| node_record_t *node_ptr; |
| |
| if (!job_resrcs_ptr->core_bitmap) |
| return 1; |
| |
| /* create row_bitmap data structure as needed */ |
| if (!r_ptr->row_bitmap) { |
| if (type == HANDLE_JOB_RES_TEST) |
| return 1; |
| core_array = build_core_array(); |
| r_ptr->row_bitmap = core_array; |
| r_ptr->row_set_count = 0; |
| for (int i = 0; i < node_record_count; i++) |
| core_array[i] = _create_core_bitmap(i); |
| } else |
| core_array = r_ptr->row_bitmap; |
| |
| for (int i = 0; |
| (node_ptr = next_node_bitmap(job_resrcs_ptr->node_bitmap, &i)); |
| i++) { |
| cores_per_node = node_ptr->tot_cores; |
| |
| /* |
| * This segment properly handles the core counts when whole |
| * nodes are allocated, including when explicitly requesting |
| * specialized cores. |
| */ |
| if (job_resrcs_ptr->whole_node == 1) { |
| if (!core_array[i]) { |
| if (type != HANDLE_JOB_RES_TEST) |
| error("core_array for node %d is NULL %d", |
| i, type); |
| continue; /* Move to next node */ |
| } |
| |
| switch (type) { |
| case HANDLE_JOB_RES_ADD: |
| bit_set_all(core_array[i]); |
| r_ptr->row_set_count += cores_per_node; |
| break; |
| case HANDLE_JOB_RES_REM: |
| bit_clear_all(core_array[i]); |
| r_ptr->row_set_count -= cores_per_node; |
| break; |
| case HANDLE_JOB_RES_TEST: |
| if (bit_ffs(core_array[i]) != -1) |
| return 0; |
| break; |
| } |
| continue; /* Move to next node */ |
| } |
| |
| for (c = 0; c < cores_per_node; c++) { |
| if (!bit_test(job_resrcs_ptr->core_bitmap, c_off + c)) |
| continue; |
| if (!core_array[i]) { |
| if (type != HANDLE_JOB_RES_TEST) |
| error("core_array for node %d is NULL %d", |
| i, type); |
| continue; /* Move to next node */ |
| } |
| switch (type) { |
| case HANDLE_JOB_RES_ADD: |
| bit_set(core_array[i], c); |
| r_ptr->row_set_count++; |
| break; |
| case HANDLE_JOB_RES_REM: |
| bit_clear(core_array[i], c); |
| r_ptr->row_set_count--; |
| break; |
| case HANDLE_JOB_RES_TEST: |
| if (bit_test(core_array[i], c)) |
| return 0; /* Core conflict on node */ |
| break; |
| } |
| } |
| c_off += cores_per_node; |
| } |
| |
| return 1; |
| } |
| |
| static void _log_tres_state(node_use_record_t *node_usage, |
| part_res_record_t *part_record_ptr) |
| { |
| #if _DEBUG |
| node_record_t *node_ptr; |
| part_res_record_t *p_ptr; |
| int i; |
| |
| for (i = 0; (node_ptr = next_node(&i)); i++) { |
| list_t *gres_list; |
| info("Node:%s AllocMem:%"PRIu64" of %"PRIu64, |
| node_ptr->name, |
| node_usage[i].alloc_memory, |
| node_ptr->real_memory); |
| if (node_usage[node_ptr->index].gres_list) |
| gres_list = select_node_usage[node_ptr->index]. |
| gres_list; |
| else |
| gres_list = node_ptr->gres_list; |
| if (gres_list) |
| gres_node_state_log(gres_list, node_ptr->name); |
| } |
| for (p_ptr = part_record_ptr; p_ptr; p_ptr = p_ptr->next) { |
| part_data_dump_res(p_ptr); |
| } |
| #endif |
| } |
| |
| extern char *job_res_job_action_string(job_res_job_action_t action) |
| { |
| switch (action) { |
| case JOB_RES_ACTION_NORMAL: |
| return "normal"; |
| break; |
| case JOB_RES_ACTION_SUSPEND: |
| return "suspend"; |
| break; |
| case JOB_RES_ACTION_RESUME: |
| return "resume"; |
| break; |
| default: |
| return "unknown"; |
| } |
| } |
| |
| /* |
| * Add job resource allocation to record of resources allocated to all nodes |
| * IN job_resrcs_ptr - resources allocated to a job |
| * IN r_ptr - row we are trying to fit |
| * IN/OUT r_ptr->row_bitmap - bitmap array (one per node) of |
| * available cores, allocated as needed |
| * NOTE: Patterned after add_job_to_cores() in src/common/job_resources.c |
| */ |
| extern void job_res_add_cores(job_resources_t *job_resrcs_ptr, |
| part_row_data_t *r_ptr) |
| { |
| (void)_handle_job_res(job_resrcs_ptr, r_ptr, |
| HANDLE_JOB_RES_ADD); |
| } |
| |
| |
| /* |
| * Remove job resource allocation to record of resources allocated to all nodes |
| * IN job_resrcs_ptr - resources allocated to a job |
| * IN r_ptr - row we are trying to fit |
| * IN/OUT r_ptr->row_bitmap - bitmap array (one per node) of |
| * available cores, allocated as needed |
| */ |
| extern void job_res_rm_cores(job_resources_t *job_resrcs_ptr, |
| part_row_data_t *r_ptr) |
| { |
| (void)_handle_job_res(job_resrcs_ptr, r_ptr, |
| HANDLE_JOB_RES_REM); |
| } |
| |
| /* |
| * Test if job can fit into the given set of core_bitmaps |
| * IN job_resrcs_ptr - resources allocated to a job |
| * IN r_ptr - row we are trying to fit |
| * RET 1 on success, 0 otherwise |
| * NOTE: Patterned after job_fits_into_cores() in src/common/job_resources.c |
| */ |
| extern int job_res_fit_in_row(job_resources_t *job_resrcs_ptr, |
| part_row_data_t *r_ptr) |
| { |
| if ((r_ptr->num_jobs == 0) || !r_ptr->row_bitmap) |
| return 1; |
| |
| return _handle_job_res(job_resrcs_ptr, r_ptr, |
| HANDLE_JOB_RES_TEST); |
| } |
| |
| /* |
| * allocate resources to the given job |
| * - add 'struct job_resources' resources to 'part_res_record_t' |
| * - add job's memory requirements to 'node_res_record_t' |
| * |
| * if action = JOB_RES_ACTION_NORMAL then add cores, memory + GRES |
| * (starting new job) |
| * if action = JOB_RES_ACTION_SUSPEND then add memory + GRES |
| * (adding suspended job at restart) |
| * if action = JOB_RES_ACTION_RESUME then only add cores |
| * (suspended job is resumed) |
| * |
| * See also: job_res_rm_job() |
| */ |
| extern int job_res_add_job(job_record_t *job_ptr, job_res_job_action_t action) |
| { |
| struct job_resources *job = job_ptr->job_resrcs; |
| node_record_t *node_ptr; |
| part_res_record_t *p_ptr; |
| list_t *node_gres_list; |
| int i, n; |
| bitstr_t *core_bitmap; |
| bool new_alloc = true; |
| |
| if (!job || !job->core_bitmap) { |
| error("%pJ has no job_resrcs info", |
| job_ptr); |
| return SLURM_ERROR; |
| } |
| |
| debug3("%pJ action:%s", job_ptr, |
| job_res_job_action_string(action)); |
| |
| if (slurm_conf.debug_flags & DEBUG_FLAG_SELECT_TYPE) |
| log_job_resources(job_ptr); |
| |
| if (job_ptr->gres_list_alloc) |
| new_alloc = false; |
| for (i = 0, n = -1; (node_ptr = next_node_bitmap(job->node_bitmap, &i)); |
| i++) { |
| n++; |
| if (job->cpus[n] == 0) |
| continue; /* node removed by job resize */ |
| |
| if (action != JOB_RES_ACTION_RESUME) { |
| if (select_node_usage[i].gres_list) |
| node_gres_list = select_node_usage[i].gres_list; |
| else |
| node_gres_list = node_ptr->gres_list; |
| core_bitmap = copy_job_resources_node(job, n); |
| if (job_ptr->details && |
| (job_ptr->details->whole_node & |
| WHOLE_NODE_REQUIRED)) |
| gres_stepmgr_job_alloc_whole_node( |
| job_ptr->gres_list_req, |
| &job_ptr->gres_list_alloc, |
| node_gres_list, job->nhosts, |
| i, n, job_ptr->job_id, |
| node_ptr->name, core_bitmap, new_alloc); |
| else |
| gres_stepmgr_job_alloc( |
| job_ptr->gres_list_req, |
| &job_ptr->gres_list_alloc, |
| node_gres_list, job->nhosts, |
| i, n, job_ptr->job_id, |
| node_ptr->name, core_bitmap, new_alloc); |
| |
| gres_node_state_log(node_gres_list, node_ptr->name); |
| FREE_NULL_BITMAP(core_bitmap); |
| |
| if (job->memory_allocated[n] == 0) |
| continue; /* node lost by job resizing */ |
| select_node_usage[i].alloc_memory += |
| job->memory_allocated[n]; |
| if ((select_node_usage[i].alloc_memory > |
| node_ptr->real_memory)) { |
| error("node %s memory is " |
| "overallocated (%"PRIu64") for %pJ", |
| node_ptr->name, |
| select_node_usage[i].alloc_memory, |
| job_ptr); |
| } |
| } |
| } |
| |
| if (action != JOB_RES_ACTION_RESUME) { |
| gres_stepmgr_job_build_details(job_ptr->gres_list_alloc, |
| job_ptr->nodes, |
| &job_ptr->gres_detail_cnt, |
| &job_ptr->gres_detail_str, |
| &job_ptr->gres_used); |
| } |
| |
| /* add cores */ |
| if (action != JOB_RES_ACTION_SUSPEND) { |
| for (p_ptr = select_part_record; p_ptr; p_ptr = p_ptr->next) { |
| if (p_ptr->part_ptr == job_ptr->part_ptr) |
| break; |
| } |
| if (!p_ptr) { |
| char *part_name; |
| if (job_ptr->part_ptr) |
| part_name = job_ptr->part_ptr->name; |
| else |
| part_name = job_ptr->partition; |
| error("could not find partition %s", |
| part_name); |
| return SLURM_ERROR; |
| } |
| |
| if (p_ptr->rebuild_rows) |
| part_data_build_row_bitmaps(p_ptr, NULL); |
| |
| if (!p_ptr->row) { |
| p_ptr->row = xcalloc(p_ptr->num_rows, |
| sizeof(part_row_data_t)); |
| } |
| |
| /* find a row to add this job */ |
| for (i = 0; i < p_ptr->num_rows; i++) { |
| if (!job_res_fit_in_row(job, &(p_ptr->row[i]))) |
| continue; |
| debug3("adding %pJ to part %s row %u", |
| job_ptr, |
| p_ptr->part_ptr->name, i); |
| part_data_add_job_to_row(job, &(p_ptr->row[i])); |
| break; |
| } |
| if (i >= p_ptr->num_rows) { |
| /* |
| * Job started or resumed and it's allocated resources |
| * are already in use by some other job. Typically due |
| * to manually resuming a job. |
| */ |
| error("job overflow: " |
| "could not find idle resources for %pJ", |
| job_ptr); |
| /* No row available to record this job */ |
| } |
| /* update the node state */ |
| for (i = 0, n = -1; next_node_bitmap(job->node_bitmap, &i); |
| i++) { |
| n++; |
| if (job->cpus[n] == 0) |
| continue; /* node lost by job resize */ |
| select_node_usage[i].node_state += job->node_req; |
| if (!select_node_usage[i].jobs) |
| select_node_usage[i].jobs = list_create(NULL); |
| if (action == JOB_RES_ACTION_NORMAL) |
| list_append(select_node_usage[i].jobs, job_ptr); |
| } |
| if (slurm_conf.debug_flags & DEBUG_FLAG_SELECT_TYPE) { |
| info("DEBUG: (after):"); |
| part_data_dump_res(p_ptr); |
| } |
| } |
| return SLURM_SUCCESS; |
| } |
| |
| /* |
| * Deallocate resources previously allocated to the given job |
| * - subtract 'struct job_resources' resources from 'part_res_record_t' |
| * - subtract job's memory requirements from 'node_res_record_t' |
| * |
| * if action = JOB_RES_ACTION_NORMAL then subtract cores, memory + GRES |
| * (running job was terminated) |
| * if action = JOB_RES_ACTION_SUSPEND then subtract memory + GRES |
| * (suspended job was terminated) |
| * if action = JOB_RES_ACTION_RESUME then only subtract cores |
| * (job is suspended) |
| * |
| * RET SLURM_SUCCESS or error code |
| * |
| * See also: job_res_add_job() |
| */ |
| extern int job_res_rm_job(part_res_record_t *part_record_ptr, |
| node_use_record_t *node_usage, list_t *license_list, |
| job_record_t *job_ptr, job_res_job_action_t action, |
| bitstr_t *node_map) |
| { |
| struct job_resources *job = job_ptr->job_resrcs; |
| node_record_t *node_ptr; |
| int i, n; |
| bool old_job = false; |
| |
| if (select_state_initializing) { |
| /* |
| * Ignore job removal until select/cons_tres data structures |
| * values are set by select_p_reconfigure() |
| */ |
| info("plugin still initializing"); |
| return SLURM_SUCCESS; |
| } |
| if (!job || !job->core_bitmap) { |
| if (job_ptr->details && (job_ptr->details->min_nodes == 0)) |
| return SLURM_SUCCESS; |
| error("%pJ has no job_resrcs info", |
| job_ptr); |
| return SLURM_ERROR; |
| } |
| |
| if (slurm_conf.debug_flags & DEBUG_FLAG_SELECT_TYPE) { |
| info("%pJ action:%s", |
| job_ptr, job_res_job_action_string(action)); |
| log_job_resources(job_ptr); |
| _log_tres_state(node_usage, part_record_ptr); |
| } else { |
| debug3("%pJ action:%s", |
| job_ptr, job_res_job_action_string(action)); |
| } |
| |
| if (license_list) |
| license_job_return_to_list(job_ptr, license_list, false, true); |
| |
| if (job_ptr->start_time < slurmctld_config.boot_time) |
| old_job = true; |
| for (i = 0, n = -1; (node_ptr = next_node_bitmap(job->node_bitmap, &i)); |
| i++) { |
| n++; |
| |
| if (node_map && !bit_test(node_map, i)) |
| continue; |
| if (job->cpus[n] == 0) |
| continue; /* node lost by job resize */ |
| |
| if (action != JOB_RES_ACTION_RESUME) { |
| list_t *node_gres_list; |
| |
| if (node_usage[i].gres_list) |
| node_gres_list = node_usage[i].gres_list; |
| else |
| node_gres_list = node_ptr->gres_list; |
| |
| gres_stepmgr_job_dealloc(job_ptr->gres_list_alloc, |
| node_gres_list, |
| n, job_ptr->job_id, |
| node_ptr->name, old_job, |
| false); |
| gres_node_state_log(node_gres_list, node_ptr->name); |
| |
| if (node_usage[i].alloc_memory < |
| job->memory_allocated[n]) { |
| error("node %s memory is " |
| "under-allocated (%"PRIu64"-%"PRIu64") " |
| "for %pJ", |
| node_ptr->name, |
| node_usage[i].alloc_memory, |
| job->memory_allocated[n], |
| job_ptr); |
| node_usage[i].alloc_memory = 0; |
| } else { |
| node_usage[i].alloc_memory -= |
| job->memory_allocated[n]; |
| } |
| } |
| } |
| |
| /* |
| * Subtract cores JOB_RES_ACTION_SUSPEND isn't used at this moment, but |
| * we will keep this check just to be clear what we are doing. |
| */ |
| if (action != JOB_RES_ACTION_SUSPEND) { |
| /* reconstruct rows with remaining jobs */ |
| part_res_record_t *p_ptr; |
| |
| /* |
| * Clean up node_usage[].jobs first to prevent dangling pointers |
| * if we encounter errors or don't find the job in partition |
| * rows. |
| */ |
| if (action == JOB_RES_ACTION_NORMAL) { |
| for (i = 0; next_node_bitmap(job->node_bitmap, &i); |
| i++) { |
| if (node_map && !bit_test(node_map, i)) |
| continue; |
| if (node_usage[i].jobs) |
| list_delete_first( |
| node_usage[i].jobs, |
| slurm_find_ptr_in_list, |
| job_ptr); |
| } |
| } |
| |
| if (!job_ptr->part_ptr) { |
| error("removed %pJ does not have a partition assigned", |
| job_ptr); |
| return SLURM_ERROR; |
| } |
| |
| for (p_ptr = part_record_ptr; p_ptr; p_ptr = p_ptr->next) { |
| if (p_ptr->part_ptr == job_ptr->part_ptr) |
| break; |
| } |
| if (!p_ptr) { |
| error("removed %pJ could not find part %s", |
| job_ptr, |
| job_ptr->part_ptr->name); |
| return SLURM_ERROR; |
| } |
| |
| if (!p_ptr->row) |
| return SLURM_SUCCESS; |
| |
| /* remove the job from the job_list */ |
| n = 0; |
| for (i = 0; i < p_ptr->num_rows; i++) { |
| uint32_t j; |
| for (j = 0; j < p_ptr->row[i].num_jobs; j++) { |
| if (p_ptr->row[i].job_list[j] != job) |
| continue; |
| debug3("removed %pJ from part %s row %u", |
| job_ptr, |
| p_ptr->part_ptr->name, i); |
| for ( ; j < p_ptr->row[i].num_jobs-1; j++) { |
| p_ptr->row[i].job_list[j] = |
| p_ptr->row[i].job_list[j+1]; |
| } |
| p_ptr->row[i].job_list[j] = NULL; |
| p_ptr->row[i].num_jobs--; |
| /* found job - we're done */ |
| n = 1; |
| i = p_ptr->num_rows; |
| break; |
| } |
| } |
| if (n) { |
| /* job was found and removed, so bitmaps need be rebuild */ |
| if (p_ptr->num_rows == 1) |
| part_data_build_row_bitmaps(p_ptr, job_ptr); |
| else |
| p_ptr->rebuild_rows = true; |
| /* |
| * Adjust the node_state of all nodes affected by |
| * the removal of this job. If all cores are now |
| * available, set node_state = NODE_CR_AVAILABLE |
| */ |
| for (i = 0, n = -1; |
| (node_ptr = |
| next_node_bitmap(job->node_bitmap, &i)); |
| i++) { |
| n++; |
| if (job->cpus[n] == 0) |
| continue; /* node lost by job resize */ |
| if (node_map && !bit_test(node_map, i)) |
| continue; |
| if (node_usage[i].node_state >= |
| job->node_req) { |
| node_usage[i].node_state -= |
| job->node_req; |
| } else { |
| error("node_state mis-count (%pJ job_cnt:%u node:%s node_cnt:%u)", |
| job_ptr, |
| job->node_req, node_ptr->name, |
| node_usage[i].node_state); |
| node_usage[i].node_state = |
| NODE_CR_AVAILABLE; |
| } |
| } |
| } |
| } |
| if (slurm_conf.debug_flags & DEBUG_FLAG_SELECT_TYPE) { |
| info("%pJ finished", job_ptr); |
| _log_tres_state(node_usage, part_record_ptr); |
| } |
| |
| return SLURM_SUCCESS; |
| } |