/* * Copyright (c) 2004-2005 The Trustees of Indiana University and Indiana * University Research and Technology * Corporation. All rights reserved. * Copyright (c) 2004-2005 The University of Tennessee and The University * of Tennessee Research Foundation. All rights * reserved. * Copyright (c) 2004-2005 High Performance Computing Center Stuttgart, * University of Stuttgart. All rights reserved. * Copyright (c) 2004-2005 The Regents of the University of California. * All rights reserved. * Copyright (c) 2013-2020 Cisco Systems, Inc. All rights reserved * Copyright (c) 2014-2020 Intel, Inc. All rights reserved. * Copyright (c) 2015 Research Organization for Information Science * and Technology (RIST). All rights reserved. * Copyright (c) 2016 IBM Corporation. All rights reserved. * Copyright (c) 2021-2023 Nanook Consulting. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow * * $HEADER$ */ #include "prte_config.h" #include #include #include "constants.h" #include "types.h" #include "src/util/pmix_argv.h" #include "src/util/pmix_if.h" #include "src/util/pmix_net.h" #include "src/util/proc_info.h" #include "src/util/pmix_show_help.h" #include "src/mca/errmgr/errmgr.h" #include "src/mca/plm/plm_types.h" #include "src/mca/ras/base/base.h" #include "src/runtime/prte_globals.h" #include "dash_host.h" static bool quickmatch(prte_node_t *nd, char *name) { int n; if (0 == strcmp(nd->name, name)) { return true; } if (0 == strcmp(nd->name, prte_process_info.nodename) && (0 == strcmp(name, "localhost") || 0 == strcmp(name, "127.0.0.1"))) { return true; } if (NULL != nd->aliases) { for (n=0; NULL != nd->aliases[n]; n++) { if (0 == strcmp(nd->aliases[n], name)) { return true; } } } return false; } int prte_util_dash_host_compute_slots(prte_node_t *node, char *hosts) { char **specs, *cptr; int slots = 0; int n; specs = PMIX_ARGV_SPLIT_COMPAT(hosts, ','); /* see if this node appears in the list */ for (n = 0; NULL != specs[n]; n++) { /* check if the #slots was specified */ if (NULL != (cptr = strchr(specs[n], ':'))) { *cptr = '\0'; ++cptr; } else { cptr = NULL; } if (quickmatch(node, specs[n])) { if (NULL != cptr) { if ('*' == *cptr || 0 == strcmp(cptr, "auto")) { slots += node->slots - node->slots_inuse; } else { slots += strtol(cptr, NULL, 10); } } else { ++slots; } } } PMIX_ARGV_FREE_COMPAT(specs); return slots; } /* we can only enter this routine if no other allocation * was found, so we only need to know that finding any * relative node syntax should generate an immediate error */ int prte_util_add_dash_host_nodes(pmix_list_t *nodes, char *hosts, bool allocating) { pmix_list_item_t *item; int32_t i, j, k; int rc, nodeidx; char **host_argv = NULL; char **mapped_nodes = NULL, **mini_map, *ndname; prte_node_t *node, *nd; pmix_list_t adds; bool needcheck; int slots = 0; bool slots_given; char *cptr; char *shortname; char *rawname; bool add_slots = false; PMIX_OUTPUT_VERBOSE((1, prte_ras_base_framework.framework_output, "%s dashhost: parsing args %s", PRTE_NAME_PRINT(PRTE_PROC_MY_NAME), hosts)); PMIX_CONSTRUCT(&adds, pmix_list_t); host_argv = PMIX_ARGV_SPLIT_COMPAT(hosts, ','); if (0 < pmix_list_get_size(nodes)) { needcheck = true; } else { needcheck = false; } /* Accumulate all of the host name mappings */ for (j = 0; j < PMIX_ARGV_COUNT_COMPAT(host_argv); ++j) { mini_map = PMIX_ARGV_SPLIT_COMPAT(host_argv[j], ','); if (mapped_nodes == NULL) { mapped_nodes = mini_map; } else { for (k = 0; NULL != mini_map[k]; ++k) { rc = PMIX_ARGV_APPEND_NOSIZE_COMPAT(&mapped_nodes, mini_map[k]); if (PRTE_SUCCESS != rc) { PMIX_ARGV_FREE_COMPAT(host_argv); PMIX_ARGV_FREE_COMPAT(mini_map); goto cleanup; } } PMIX_ARGV_FREE_COMPAT(mini_map); } } PMIX_ARGV_FREE_COMPAT(host_argv); mini_map = NULL; /* Did we find anything? If not, then do nothing */ if (NULL == mapped_nodes) { rc = PRTE_SUCCESS; goto cleanup; } for (i = 0; NULL != mapped_nodes[i]; ++i) { /* if the specified node contains a relative node syntax, * and we are allocating, then ignore it */ if ('+' == mapped_nodes[i][0]) { if (!allocating) { if ('e' == mapped_nodes[i][1] || 'E' == mapped_nodes[i][1]) { /* request for empty nodes - do they want * all of them? */ if (NULL != (cptr = strchr(mapped_nodes[i], ':'))) { /* the colon indicates a specific # are requested */ ++cptr; j = strtoul(cptr, NULL, 10); } else if ('\0' != mapped_nodes[0][2]) { j = strtoul(&mapped_nodes[0][2], NULL, 10); } else { /* add them all */ j = prte_node_pool->size; } for (k = 0; 0 < j && k < prte_node_pool->size; k++) { if (NULL != (node = (prte_node_t *) pmix_pointer_array_get_item(prte_node_pool, k))) { if (0 == node->num_procs) { PMIX_ARGV_APPEND_NOSIZE_COMPAT(&mini_map, node->name); --j; } } } } else if ('n' == mapped_nodes[i][1] || 'N' == mapped_nodes[i][1]) { /* they want a specific relative node #, so * look it up on global pool */ if ('\0' == mapped_nodes[i][2]) { /* they forgot to tell us the # */ pmix_show_help("help-dash-host.txt", "dash-host:invalid-relative-node-syntax", true, mapped_nodes[i]); rc = PRTE_ERR_SILENT; goto cleanup; } nodeidx = strtol(&mapped_nodes[i][2], NULL, 10); if (nodeidx < 0 || nodeidx > (int) prte_node_pool->size) { /* this is an error */ pmix_show_help("help-dash-host.txt", "dash-host:relative-node-out-of-bounds", true, nodeidx, mapped_nodes[i]); rc = PRTE_ERR_SILENT; goto cleanup; } /* if the HNP is not allocated, then we need to * adjust the index as the node pool is offset * by one */ if (!prte_hnp_is_allocated) { nodeidx++; } /* see if that location is filled */ node = (prte_node_t *) pmix_pointer_array_get_item(prte_node_pool, nodeidx); if (NULL == node) { /* this is an error */ pmix_show_help("help-dash-host.txt", "dash-host:relative-node-not-found", true, nodeidx, mapped_nodes[i]); rc = PRTE_ERR_SILENT; goto cleanup; } /* add this node to the list */ PMIX_ARGV_APPEND_NOSIZE_COMPAT(&mini_map, node->name); } else { /* invalid relative node syntax */ pmix_show_help("help-dash-host.txt", "dash-host:invalid-relative-node-syntax", true, mapped_nodes[i]); rc = PRTE_ERR_SILENT; goto cleanup; } } } else { /* just one node was given */ PMIX_ARGV_APPEND_NOSIZE_COMPAT(&mini_map, mapped_nodes[i]); } } if (NULL == mini_map) { rc = PRTE_SUCCESS; goto cleanup; } /* go through the names found and add them to the host list. If they're not unique, then bump the slots count for each duplicate */ for (i = 0; NULL != mini_map[i]; i++) { PMIX_OUTPUT_VERBOSE((1, prte_ras_base_framework.framework_output, "%s dashhost: working node %s", PRTE_NAME_PRINT(PRTE_PROC_MY_NAME), mini_map[i])); /* see if the node contains the number of slots */ slots_given = false; if (NULL != (cptr = strchr(mini_map[i], ':'))) { *cptr = '\0'; ++cptr; if ('*' == *cptr || 0 == strcmp(cptr, "auto")) { /* auto-detect #slots */ slots = -1; slots_given = false; } else { slots = strtol(cptr, NULL, 10); if ('+' == *cptr || '-' == *cptr) { // mark that we are being asked to increase/decrease #slots add_slots = true; } slots_given = true; } } /* check for local name and compute non-fqdn name */ shortname = NULL; rawname = NULL; if (prte_check_host_is_local(mini_map[i])) { ndname = prte_process_info.nodename; } else { ndname = mini_map[i]; /* compute the non-fqdn version */ if (!prte_keep_fqdn_hostnames && !pmix_net_isaddr(ndname)) { cptr = strchr(ndname, '.'); if (NULL != cptr) { rawname = strdup(ndname); *cptr = '\0'; shortname = strdup(ndname); *cptr = '.'; } } } /* see if a node of this name is already on the list */ node = prte_node_match(&adds, ndname); if (NULL == node && NULL != shortname) { node = prte_node_match(&adds, shortname); } if (NULL != node) { if (slots_given) { node->slots += slots; PRTE_FLAG_SET(node, PRTE_NODE_FLAG_SLOTS_GIVEN); if (add_slots) { prte_set_attribute(&node->attributes, PRTE_NODE_ADD_SLOTS, PRTE_ATTR_GLOBAL, NULL, PMIX_BOOL); } } else if (slots < 0) { node->slots = 0; PRTE_FLAG_UNSET(node, PRTE_NODE_FLAG_SLOTS_GIVEN); } else { ++node->slots; PRTE_FLAG_SET(node, PRTE_NODE_FLAG_SLOTS_GIVEN); } PMIX_OUTPUT_VERBOSE((1, prte_ras_base_framework.framework_output, "%s dashhost: node %s already on list - slots %d", PRTE_NAME_PRINT(PRTE_PROC_MY_NAME), node->name, node->slots)); if (NULL != shortname) { free(shortname); shortname = NULL; } if (NULL != rawname) { node->rawname = rawname; rawname = NULL; } } else { /* if we didn't find it, add it to the list */ node = PMIX_NEW(prte_node_t); if (NULL == node) { PMIX_ARGV_FREE_COMPAT(mapped_nodes); if (NULL != shortname) { free(shortname); } if (NULL != rawname) { free(rawname); } return PRTE_ERR_OUT_OF_RESOURCE; } if (prte_keep_fqdn_hostnames || NULL == shortname) { node->name = strdup(ndname); } else { node->name = strdup(shortname); } if (NULL != rawname) { node->rawname = rawname; rawname = NULL; } PMIX_OUTPUT_VERBOSE((1, prte_ras_base_framework.framework_output, "%s dashhost: added node %s to list - slots %d", PRTE_NAME_PRINT(PRTE_PROC_MY_NAME), node->name, slots)); node->state = PRTE_NODE_STATE_UP; node->slots_inuse = 0; node->slots_max = 0; if (slots_given) { node->slots = slots; PRTE_FLAG_SET(node, PRTE_NODE_FLAG_SLOTS_GIVEN); if (add_slots) { prte_set_attribute(&node->attributes, PRTE_NODE_ADD_SLOTS, PRTE_ATTR_GLOBAL, NULL, PMIX_BOOL); } } else if (slots < 0) { node->slots = 0; PRTE_FLAG_UNSET(node, PRTE_NODE_FLAG_SLOTS_GIVEN); } else { node->slots = 1; PRTE_FLAG_SET(node, PRTE_NODE_FLAG_SLOTS_GIVEN); } pmix_list_append(&adds, &node->super); } if (0 != strcmp(node->name, mini_map[i])) { // add the mini_map name to the list of aliases PMIX_ARGV_APPEND_UNIQUE_COMPAT(&node->aliases, mini_map[i]); } // ensure the non-fqdn version is saved if (NULL != shortname && 0 != strcmp(shortname, node->name)) { PMIX_ARGV_APPEND_UNIQUE_COMPAT(&node->aliases, shortname); } if (NULL != shortname) { free(shortname); } if (NULL != rawname) { free(rawname); } } PMIX_ARGV_FREE_COMPAT(mini_map); /* transfer across all unique nodes */ while (NULL != (item = pmix_list_remove_first(&adds))) { nd = (prte_node_t *) item; if (needcheck) { node = prte_node_match(nodes, nd->name); if (NULL != node) { PMIX_OUTPUT_VERBOSE((1, prte_ras_base_framework.framework_output, "%s dashhost: found existing node %s on input list - adding slots", PRTE_NAME_PRINT(PRTE_PROC_MY_NAME), node->name)); if (PRTE_FLAG_TEST(nd, PRTE_NODE_FLAG_SLOTS_GIVEN)) { /* transfer across the number of slots */ node->slots += nd->slots; PRTE_FLAG_SET(node, PRTE_NODE_FLAG_SLOTS_GIVEN); } PMIX_RELEASE(item); } else { PMIX_OUTPUT_VERBOSE((1, prte_ras_base_framework.framework_output, "%s dashhost: adding node %s with %d slots to final list", PRTE_NAME_PRINT(PRTE_PROC_MY_NAME), nd->name, nd->slots)); pmix_list_append(nodes, &nd->super); } } else { PMIX_OUTPUT_VERBOSE((1, prte_ras_base_framework.framework_output, "%s dashhost: adding node %s with %d slots to final list", PRTE_NAME_PRINT(PRTE_PROC_MY_NAME), nd->name, nd->slots)); pmix_list_append(nodes, &nd->super); } } if (prte_managed_allocation && !allocating) { prte_node_t *node_from_pool = NULL; PMIX_LIST_FOREACH(node, nodes, prte_node_t) { needcheck = true; for (i = 0; i < prte_node_pool->size; i++) { node_from_pool = (prte_node_t *) pmix_pointer_array_get_item(prte_node_pool, i); if (NULL == node_from_pool) { continue; } if (prte_nptr_match(node_from_pool, node)) { needcheck = false; if (node->slots < node_from_pool->slots) { node_from_pool->slots = node->slots; } break; } } if (needcheck) { // node in -host was not in allocation - this is not allowed pmix_show_help("help-dash-host.txt", "not-all-mapped-alloc", true, node->name); rc = PRTE_ERR_SILENT; goto cleanup; } } } rc = PRTE_SUCCESS; cleanup: if (NULL != mapped_nodes) { PMIX_ARGV_FREE_COMPAT(mapped_nodes); } PMIX_LIST_DESTRUCT(&adds); return rc; } /* the -host option can always be used in both absolute * and relative mode, so we have to check for pre-existing * allocations if we are to use relative node syntax */ static int parse_dash_host(char ***mapped_nodes, char *hosts) { int32_t j, k; int rc = PRTE_SUCCESS; char **mini_map = NULL, *cptr; int nodeidx; prte_node_t *node; char **host_argv = NULL; host_argv = PMIX_ARGV_SPLIT_COMPAT(hosts, ','); /* Accumulate all of the host name mappings */ for (j = 0; j < PMIX_ARGV_COUNT_COMPAT(host_argv); ++j) { mini_map = PMIX_ARGV_SPLIT_COMPAT(host_argv[j], ','); for (k = 0; NULL != mini_map[k]; ++k) { if ('+' == mini_map[k][0]) { /* see if we specified empty nodes */ if ('e' == mini_map[k][1] || 'E' == mini_map[k][1]) { /* request for empty nodes - do they want * all of them? */ if (NULL != (cptr = strchr(mini_map[k], ':'))) { /* the colon indicates a specific # are requested */ *cptr = '*'; PMIX_ARGV_APPEND_NOSIZE_COMPAT(mapped_nodes, cptr); } else { /* add a marker to the list */ PMIX_ARGV_APPEND_NOSIZE_COMPAT(mapped_nodes, "*"); } } else if ('n' == mini_map[k][1] || 'N' == mini_map[k][1]) { /* they want a specific relative node #, so * look it up on global pool */ nodeidx = strtol(&mini_map[k][2], NULL, 10); if (nodeidx < 0 || nodeidx > (int) prte_node_pool->size) { /* this is an error */ pmix_show_help("help-dash-host.txt", "dash-host:relative-node-out-of-bounds", true, nodeidx, mini_map[k]); rc = PRTE_ERR_SILENT; goto cleanup; } /* if the HNP is not allocated, then we need to * adjust the index as the node pool is offset * by one */ if (!prte_hnp_is_allocated) { nodeidx++; } /* see if that location is filled */ node = (prte_node_t *) pmix_pointer_array_get_item(prte_node_pool, nodeidx); if (NULL == node) { /* this is an error */ pmix_show_help("help-dash-host.txt", "dash-host:relative-node-not-found", true, nodeidx, mini_map[k]); rc = PRTE_ERR_SILENT; goto cleanup; } /* add this node to the list */ PMIX_ARGV_APPEND_NOSIZE_COMPAT(mapped_nodes, node->name); } else { /* invalid relative node syntax */ pmix_show_help("help-dash-host.txt", "dash-host:invalid-relative-node-syntax", true, mini_map[k]); rc = PRTE_ERR_SILENT; goto cleanup; } } else { /* non-relative syntax - add to list */ /* remove any modifier */ if (NULL != (cptr = strchr(mini_map[k], ':'))) { *cptr = '\0'; } /* check for local alias */ if (prte_check_host_is_local(mini_map[k])) { PMIX_ARGV_APPEND_NOSIZE_COMPAT(mapped_nodes, prte_process_info.nodename); } else { PMIX_ARGV_APPEND_NOSIZE_COMPAT(mapped_nodes, mini_map[k]); } } } PMIX_ARGV_FREE_COMPAT(mini_map); mini_map = NULL; } cleanup: if (NULL != host_argv) { PMIX_ARGV_FREE_COMPAT(host_argv); } if (NULL != mini_map) { PMIX_ARGV_FREE_COMPAT(mini_map); } return rc; } int prte_util_filter_dash_host_nodes(pmix_list_t *nodes, char *hosts, bool remove) { pmix_list_item_t *item; pmix_list_item_t *next; int32_t i, j, len_mapped_node = 0; int rc, test; char **mapped_nodes = NULL; prte_node_t *node; int num_empty = 0; pmix_list_t keep; bool want_all_empty = false; char *cptr; size_t lst, lmn; /* if the incoming node list is empty, then there * is nothing to filter! */ if (pmix_list_is_empty(nodes)) { return PRTE_SUCCESS; } if (PRTE_SUCCESS != (rc = parse_dash_host(&mapped_nodes, hosts))) { PRTE_ERROR_LOG(rc); return rc; } /* Did we find anything? If not, then do nothing */ if (NULL == mapped_nodes) { return PRTE_SUCCESS; } /* NOTE: The following logic is based on knowing that * any node can only be included on the incoming * nodes list ONCE. */ len_mapped_node = PMIX_ARGV_COUNT_COMPAT(mapped_nodes); /* setup a working list so we can put the final list * of nodes in order. This way, if the user specifies a * set of nodes, we will use them in the order in which * they were specifed. Note that empty node requests * will always be appended to the end */ PMIX_CONSTRUCT(&keep, pmix_list_t); for (i = 0; i < len_mapped_node; ++i) { /* check if we are supposed to add some number of empty * nodes here */ if ('*' == mapped_nodes[i][0]) { /* if there is a number after the '*', then we are * to insert a specific # of nodes */ if ('\0' == mapped_nodes[i][1]) { /* take all empty nodes from the list */ num_empty = INT_MAX; want_all_empty = true; } else { /* extract number of nodes to take */ num_empty = strtol(&mapped_nodes[i][1], NULL, 10); } /* search for empty nodes and take them */ item = pmix_list_get_first(nodes); while (0 < num_empty && item != pmix_list_get_end(nodes)) { next = pmix_list_get_next(item); /* save this position */ node = (prte_node_t *) item; /* see if this node is empty */ if (0 == node->slots_inuse) { /* check to see if it is specified later */ for (j = i + 1; j < len_mapped_node; j++) { if (0 == strcmp(mapped_nodes[j], node->name)) { /* specified later - skip this one */ goto skipnode; } } if (remove) { /* remove item from list */ pmix_list_remove_item(nodes, item); /* xfer to keep list */ pmix_list_append(&keep, item); } else { /* mark the node as found */ PRTE_FLAG_SET(node, PRTE_NODE_FLAG_MAPPED); } --num_empty; } skipnode: item = next; } } else { /* remove any modifier */ if (NULL != (cptr = strchr(mapped_nodes[i], ':'))) { *cptr = '\0'; } /* we are looking for a specific node on the list. */ cptr = NULL; lmn = strtoul(mapped_nodes[i], &cptr, 10); item = pmix_list_get_first(nodes); while (item != pmix_list_get_end(nodes)) { next = pmix_list_get_next(item); /* save this position */ node = (prte_node_t *) item; /* search -host list to see if this one is found */ if (prte_managed_allocation && (NULL == cptr || 0 == strlen(cptr))) { /* if we are only given a number, then we test the * value against the number in the node name. This allows support for * launch_id-based environments. For example, a hostname * of "nid0015" can be referenced by "--host 15" */ for (j = strlen(node->name) - 1; 0 < j; j--) { if (!isdigit(node->name[j])) { j++; break; } } if (j >= (int) (strlen(node->name) - 1)) { test = 0; } else { lst = strtoul(&node->name[j], NULL, 10); test = (lmn == lst) ? 0 : 1; } } else { test = (quickmatch(node, mapped_nodes[i])) ? 0 : 1; } if (0 == test) { if (remove) { /* remove item from list */ pmix_list_remove_item(nodes, item); /* xfer to keep list */ pmix_list_append(&keep, item); } else { /* mark the node as found */ PRTE_FLAG_SET(node, PRTE_NODE_FLAG_MAPPED); } break; } item = next; } } /* done with the mapped entry */ free(mapped_nodes[i]); mapped_nodes[i] = NULL; } /* was something specified that was -not- found? */ for (i = 0; i < len_mapped_node; i++) { if (NULL != mapped_nodes[i]) { pmix_show_help("help-dash-host.txt", "not-all-mapped-alloc", true, mapped_nodes[i]); rc = PRTE_ERR_SILENT; goto cleanup; } } if (!remove) { /* all done */ rc = PRTE_SUCCESS; goto cleanup; } /* clear the rest of the nodes list */ while (NULL != (item = pmix_list_remove_first(nodes))) { PMIX_RELEASE(item); } /* the nodes list has been cleared - rebuild it in order */ while (NULL != (item = pmix_list_remove_first(&keep))) { pmix_list_append(nodes, item); } /* did they ask for more than we could provide */ if (!want_all_empty && 0 < num_empty) { pmix_show_help("help-dash-host.txt", "dash-host:not-enough-empty", true, num_empty); rc = PRTE_ERR_SILENT; goto cleanup; } rc = PRTE_SUCCESS; /* done filtering existing list */ cleanup: for (i = 0; i < len_mapped_node; i++) { if (NULL != mapped_nodes[i]) { free(mapped_nodes[i]); mapped_nodes[i] = NULL; } } if (NULL != mapped_nodes) { free(mapped_nodes); } return rc; } int prte_util_get_ordered_dash_host_list(pmix_list_t *nodes, char *hosts) { int rc, i; char **mapped_nodes = NULL; prte_node_t *node; if (PRTE_SUCCESS != (rc = parse_dash_host(&mapped_nodes, hosts))) { PRTE_ERROR_LOG(rc); } /* for each entry, create a node entry on the list */ for (i = 0; NULL != mapped_nodes[i]; i++) { node = PMIX_NEW(prte_node_t); node->name = strdup(mapped_nodes[i]); pmix_list_append(nodes, &node->super); } /* cleanup */ PMIX_ARGV_FREE_COMPAT(mapped_nodes); return rc; }