blob: 3723714d2f1f41df3fce603f1288870c7f2159a9 [file] [log] [blame]
/*****************************************************************************\
* node_features_helpers.c - Plugin for supporting arbitrary node features
* using external helper binaries
*****************************************************************************
* Copyright (C) 2021 NVIDIA CORPORATION. All rights reserved.
* Written by NVIDIA CORPORATION.
*
* 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.
\*****************************************************************************/
#define _GNU_SOURCE
#include <ctype.h>
#include <stdio.h>
#include "slurm/slurm_errno.h"
#include "src/common/list.h"
#include "src/common/job_features.h"
#include "src/common/node_conf.h"
#include "src/common/read_config.h"
#include "src/common/run_command.h"
#include "src/common/uid.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "src/slurmd/slurmd/slurmd.h"
#define FEATURE_FLAG_NO_REBOOT SLURM_BIT(0)
/*
* These are defined here so when we link with something other than
* the slurmctld we will have these symbols defined. They will get
* overwritten when linking with the slurmctld.
*/
#if defined (__APPLE__)
extern slurmd_conf_t *conf __attribute__((weak_import));
#else
slurmd_conf_t *conf = NULL;
#endif
const char plugin_name[] = "node_features helpers plugin";
const char plugin_type[] = "node_features/helpers";
const uint32_t plugin_version = SLURM_VERSION_NUMBER;
static uid_t *allowed_uid = NULL;
static int allowed_uid_cnt = 0;
static list_t *helper_features = NULL;
static list_t *helper_exclusives = NULL;
static uint32_t boot_time = (5 * 60);
static uint32_t exec_time = 10;
typedef struct {
list_t *final_list;
bitstr_t *job_node_bitmap;
} build_valid_feature_set_args_t;
typedef struct {
char *final_feature_str;
bitstr_t *job_node_bitmap;
} valid_feature_args_t;
typedef struct {
const char *name;
const char *helper;
uint64_t flags;
} plugin_feature_t;
static s_p_options_t feature_options[] = {
{"Feature", S_P_STRING},
{"Helper", S_P_STRING},
{"Flags", S_P_STRING},
{NULL},
};
static int _cmp_str(void *x, void *key)
{
return !xstrcmp(x, key);
}
static int _cmp_features(void *x, void *key)
{
plugin_feature_t *feature = x;
return !xstrcmp(feature->name, key);
}
static bool _is_feature_valid(const char *k)
{
if (!k || k[0] == '\0')
return false;
if (!isalpha(k[0]) && k[0] != '_' && k[0] != '=')
return false;
for (int i = 1; k[i] != '\0'; ++i) {
if (!isalnum(k[i]) && k[i] != '_' && k[i] != '.' && k[i] != '=')
return false;
}
return true;
}
static void _make_uid_array(char *uid_str)
{
char *save_ptr = NULL, *tmp_str, *tok;
int uid_cnt = 0;
if (!uid_str)
return;
/* Count the number of users */
for (int i = 0; uid_str[i]; i++) {
if (uid_str[i] == ',')
uid_cnt++;
}
uid_cnt++;
allowed_uid = xcalloc(uid_cnt, sizeof(uid_t));
allowed_uid_cnt = 0;
tmp_str = xstrdup(uid_str);
tok = strtok_r(tmp_str, ",", &save_ptr);
while (tok) {
if (uid_from_string(tok, &allowed_uid[allowed_uid_cnt++]) !=
SLURM_SUCCESS)
fatal("helpers.conf: Invalid AllowUserBoot: %s", tok);
tok = strtok_r(NULL, ",", &save_ptr);
}
xfree(tmp_str);
}
static int _list_make_str(void *x, void *y)
{
char *feature = (char *) x;
char **string = (char **) y;
xstrfmtcat(*string, "%s%s", (*string ? "," : ""), feature);
return 0;
}
static plugin_feature_t *_feature_create(const char *name, const char *helper,
uint64_t flags)
{
plugin_feature_t *feature = xmalloc(sizeof(*feature));
feature->name = xstrdup(name);
feature->helper = xstrdup(helper);
feature->flags = flags;
return feature;
}
static void _feature_destroy(plugin_feature_t *feature)
{
if (!feature)
return;
xfree(feature->name);
xfree(feature->helper);
xfree(feature);
}
static int _feature_set_state(const plugin_feature_t *feature)
{
char *output, **argv = NULL;
int rc = 0;
run_command_args_t run_command_args = {
.max_wait = (exec_time * 1000),
.status = &rc };
if (!feature->helper)
return SLURM_ERROR;
argv = xcalloc(3, sizeof(char *)); /* NULL terminated */
argv[0] = xstrdup(feature->helper);
argv[1] = xstrdup(feature->name);
run_command_args.script_argv = argv;
run_command_args.script_path = feature->helper;
run_command_args.script_type = "set_state";
output = run_command(&run_command_args);
if (rc != SLURM_SUCCESS) {
error("failed to set new value for feature: %s", feature->name);
}
xfree_array(argv);
xfree(output);
return rc;
}
static list_t *_feature_get_state(const plugin_feature_t *feature)
{
char *tmp, *saveptr;
char *output = NULL;
int rc = 0;
list_t *result = list_create(xfree_ptr);
run_command_args_t run_command_args = {
.max_wait = (exec_time * 1000),
.script_path = feature->helper,
.script_type = "get_state",
.status = &rc };
output = run_command(&run_command_args);
if (rc != SLURM_SUCCESS) {
goto cleanup;
}
for (tmp = strtok_r(output, "\n", &saveptr); tmp;
tmp = strtok_r(NULL, "\n", &saveptr)) {
list_append(result, xstrdup(tmp));
}
cleanup:
xfree(output);
return result;
}
static char * _feature_flag2str(uint64_t flags) {
if (flags & FEATURE_FLAG_NO_REBOOT)
return "rebootless";
else if (!flags)
return "(none)";
else
return "unknown";
}
static int _feature_register(const char *name, const char *helper,
uint64_t flags)
{
const plugin_feature_t *existing;
plugin_feature_t *feature = NULL;
existing = list_find_first(helper_features, _cmp_features,
(char *) name);
if (existing) {
if (running_in_slurmctld()) {
/* The controller just needs the feature names */
return SLURM_SUCCESS;
} else if (xstrcmp(existing->helper, helper)) {
error("feature \"%s\" previously registered with different helper \"%s\"",
name, existing->helper);
return SLURM_ERROR;
} else {
debug("feature \"%s\" previously registered same helper \"%s\"",
name, existing->helper);
return SLURM_SUCCESS;
}
}
feature = _feature_create(name, helper, flags);
info("Adding new feature \"%s\" Flags=%s",
feature->name, _feature_flag2str(feature->flags));
list_append(helper_features, feature);
return SLURM_SUCCESS;
}
static int _exclusive_register(const char *listp)
{
list_t *data_list = list_create(xfree_ptr);
char *input = xstrdup(listp);
char *entry, *saveptr;
for (entry = strtok_r(input, ",", &saveptr); entry;
entry = strtok_r(NULL, ",", &saveptr)) {
if (list_find_first(data_list, _cmp_str, entry)) {
error("Feature \"%s\" already in exclusive list",
entry);
continue;
}
list_append(data_list, xstrdup(entry));
}
xfree(input);
list_append(helper_exclusives, data_list);
return SLURM_SUCCESS;
}
static int _parse_feature(void **data, slurm_parser_enum_t type,
const char *key, const char *name,
const char *line, char **leftover)
{
s_p_hashtbl_t *tbl = NULL;
char *tmp_flags = NULL;
char *path = NULL;
int rc = -1;
char *tmp_name;
char *tmp_str = NULL;
char *last = NULL;
char *tok = NULL;
uint64_t flags = 0;
tbl = s_p_hashtbl_create(feature_options);
if (!s_p_parse_line(tbl, *leftover, leftover))
goto fail;
if (name) {
tmp_name = xstrdup(name);
} else if (!s_p_get_string(&tmp_name, "Feature", tbl)) {
error("Invalid FEATURE data, no type Feature (%s)", line);
goto fail;
}
s_p_get_string(&path, "Helper", tbl);
if (s_p_get_string(&tmp_flags, "Flags", tbl)) {
tmp_str = xstrdup(tmp_flags);
tok = strtok_r(tmp_str, ",", &last);
while (tok) {
if (!xstrcasecmp(tok, "rebootless"))
flags |= FEATURE_FLAG_NO_REBOOT;
else
error("helpers.conf: Ignoring invalid Flags=%s",
tok);
tok = strtok_r(NULL, ",", &last);
}
}
/* In slurmctld context, we can have path == NULL */
*data = _feature_create(tmp_name, path, flags);
xfree(path);
xfree(tmp_str);
xfree(tmp_flags);
rc = 1;
fail:
xfree(tmp_name);
s_p_hashtbl_destroy(tbl);
return rc;
}
static int _parse_feature_node(void **data, slurm_parser_enum_t type, const char *key,
const char *name, const char *line, char **leftover)
{
s_p_hashtbl_t *tbl = NULL;
if (!running_in_slurmctld() && conf->node_name && name) {
bool match = false;
hostlist_t *hl;
hl = hostlist_create(name);
if (hl) {
match = (hostlist_find(hl, conf->node_name) >= 0);
hostlist_destroy(hl);
}
if (!match) {
debug("skipping Feature for NodeName=%s %s", name, line);
tbl = s_p_hashtbl_create(feature_options);
s_p_parse_line(tbl, *leftover, leftover);
s_p_hashtbl_destroy(tbl);
return 0;
}
}
return _parse_feature(data, type, key, NULL, line, leftover);
}
static int _parse_exclusives(void **data, slurm_parser_enum_t type,
const char *key, const char *name,
const char *line, char **leftover)
{
*data = xstrdup(name);
return 1;
}
static s_p_options_t conf_options[] = {
{"AllowUserBoot", S_P_STRING},
{"BootTime", S_P_UINT32},
{"ExecTime", S_P_UINT32},
{"Feature", S_P_ARRAY, _parse_feature, (ListDelF) _feature_destroy},
{"MutuallyExclusive", S_P_ARRAY, _parse_exclusives, xfree_ptr},
{"NodeName", S_P_ARRAY, _parse_feature_node,
(ListDelF) _feature_destroy},
{NULL},
};
static int _handle_config_features(plugin_feature_t **features, int count)
{
for (int i = 0; i < count; ++i) {
const plugin_feature_t *feature = features[i];
char *tmp_name, *tok, *saveptr;
tmp_name = xstrdup(feature->name);
for (tok = strtok_r(tmp_name, ",", &saveptr); tok;
tok = strtok_r(NULL, ",", &saveptr)) {
if (!_is_feature_valid(tok)) {
errno = ESLURM_INVALID_FEATURE;
xfree(tmp_name);
return SLURM_ERROR;
}
/* In slurmctld context, we can have path == NULL */
if (_feature_register(tok, feature->helper,
feature->flags)) {
xfree(tmp_name);
return SLURM_ERROR;
}
}
xfree(tmp_name);
}
return SLURM_SUCCESS;
}
static int _read_config_file(void)
{
s_p_hashtbl_t *tbl = NULL;
char *confpath = NULL;
char *tmp_str = NULL;
void **features = NULL;
void **exclusives = NULL;
int count = 0;
int rc = SLURM_ERROR;
xfree(allowed_uid);
allowed_uid_cnt = 0;
FREE_NULL_LIST(helper_features);
helper_features = list_create((ListDelF) _feature_destroy);
FREE_NULL_LIST(helper_exclusives);
helper_exclusives = list_create((ListDelF) list_destroy);
tbl = s_p_hashtbl_create(conf_options);
confpath = get_extra_conf_path("helpers.conf");
if (s_p_parse_file(tbl, NULL, confpath, 0, NULL) == SLURM_ERROR) {
error("could not parse configuration file: %s", confpath);
goto fail;
}
xfree(confpath);
if (s_p_get_array(&features, &count, "Feature", tbl) &&
_handle_config_features((plugin_feature_t **)features, count))
goto fail;
if (s_p_get_array(&features, &count, "NodeName", tbl) &&
_handle_config_features((plugin_feature_t **)features, count))
goto fail;
if (s_p_get_string(&tmp_str, "AllowUserBoot", tbl)) {
_make_uid_array(tmp_str);
xfree(tmp_str);
}
if (s_p_get_array(&exclusives, &count, "MutuallyExclusive", tbl) != 0) {
for (int i = 0; i < count; ++i) {
if (_exclusive_register(exclusives[i]))
goto fail;
}
}
if (!s_p_get_uint32(&boot_time, "BootTime", tbl))
info("BootTime not specified, using default value: %u",
boot_time);
if (!s_p_get_uint32(&exec_time, "ExecTime", tbl))
info("ExecTime not specified, using default value: %u",
exec_time);
rc = SLURM_SUCCESS;
fail:
s_p_hashtbl_destroy(tbl);
return rc;
}
static int _build_valid_feature_set(void *x, void *arg)
{
job_feature_t *job_feat_ptr = x;
build_valid_feature_set_args_t *args = arg;
if (bit_super_set(args->job_node_bitmap,
job_feat_ptr->node_bitmap_avail)) {
/* Valid - only include changeable features */
if (!job_feat_ptr->changeable)
return 0;
/* The list should be unique already */
list_append(args->final_list, xstrdup(job_feat_ptr->name));
} else {
/* Invalid */
log_flag(NODE_FEATURES, "Feature %s is invalid",
job_feat_ptr->name);
return -1; /* Exit list_for_each */
}
return 0;
}
static int _reconcile_job_features(void *x, void *arg)
{
int rc = 0;
list_t *final_list = NULL;
list_t *features_list = x;
valid_feature_args_t *valid_arg = arg;
build_valid_feature_set_args_t build_arg = {
.job_node_bitmap = valid_arg->job_node_bitmap,
};
final_list = list_create(xfree_ptr);
build_arg.final_list = final_list;
if (slurm_conf.debug_flags & DEBUG_FLAG_NODE_FEATURES) {
char *list_str = NULL;
char *nodes_str = bitmap2node_name(valid_arg->job_node_bitmap);
job_features_set2str(features_list, &list_str);
log_flag(NODE_FEATURES, "Check if the features %s are valid on nodes %s",
list_str, nodes_str);
xfree(list_str);
xfree(nodes_str);
}
if (list_for_each(features_list, _build_valid_feature_set,
&build_arg) < 0) {
rc = 0; /* Continue to next list */
} else {
list_for_each(final_list, _list_make_str,
&valid_arg->final_feature_str);
rc = -1; /* Got a valid feature list; stop iterating */
}
FREE_NULL_LIST(final_list);
return rc;
}
static char *_xlate_job_features(char *job_features,
list_t *job_feature_list,
bitstr_t *job_node_bitmap)
{
list_t *feature_sets;
valid_feature_args_t valid_arg = {
.final_feature_str = NULL,
.job_node_bitmap = job_node_bitmap
};
if (slurm_conf.debug_flags & DEBUG_FLAG_NODE_FEATURES) {
char *tmp_str = bitmap2node_name(job_node_bitmap);
log_flag(NODE_FEATURES, "Find a valid feature combination for %s on nodes %s",
job_features, tmp_str);
xfree(tmp_str);
}
feature_sets = job_features_list2feature_sets(job_features,
job_feature_list,
true);
/*
* Find the first feature set that works for this job and turn it into a
* comma-separated list of char* of only the changeable features.
*/
if (list_for_each(feature_sets, _reconcile_job_features,
&valid_arg) < 0) {
/*
* Found a valid feature set. It is possible for this string to
* be NULL if the chosen feature set contained only static
* features.
*/
log_flag(NODE_FEATURES, "final_feature_str=%s",
valid_arg.final_feature_str);
} else {
char *job_nodes = bitmap2node_name(job_node_bitmap);
/*
* Here the list_for_each iterated through all features sets and
* did not find a valid feature set. This should not happen
* and means there is a mismatch in handling features in this
* plugin and in the scheduler.
*/
error("Failed to translate feature request '%s' into features that match with the job's nodes '%s'",
job_features, job_nodes);
xfree(job_nodes);
}
FREE_NULL_LIST(feature_sets);
return valid_arg.final_feature_str;
}
extern int init(void)
{
return _read_config_file();
}
extern void fini(void)
{
FREE_NULL_LIST(helper_features);
FREE_NULL_LIST(helper_exclusives);
xfree(allowed_uid);
allowed_uid_cnt = 0;
}
extern bool node_features_p_changeable_feature(char *input)
{
plugin_feature_t *feature = NULL;
feature = list_find_first(helper_features, _cmp_features, input);
if (!feature)
return false;
return true;
}
typedef struct {
char *job_features;
unsigned int count;
} excl_count_t;
static int _get_list_excl_count(void *x, void *y)
{
char *feature = (char *) x;
char *job_features = ((excl_count_t *) y)->job_features;
char *ptr = xstrstr(job_features, feature);
unsigned int len = strlen(feature);
/* check for every matching pattern */
while (ptr) {
/* check word+1 to verify exact match */
if (isalnum(ptr[len]) || ptr[len] == '-' || ptr[len] == '.' ||
ptr[len] == '_' || ptr[len] == '=') {
ptr = xstrstr(&ptr[len], feature);
continue;
}
/* check word-1 to verify exact match */
if ((ptr != job_features) && isalnum(ptr[-1])) {
ptr = xstrstr(&ptr[len], feature);
continue;
}
((excl_count_t *) y)->count++;
ptr = xstrstr(&ptr[len], feature);
}
return 0;
}
static int _count_exclusivity(void *x, void *y)
{
list_t *exclusive_list = x;
excl_count_t args = {
.job_features = (char *)y,
.count = 0
};
list_for_each(exclusive_list, _get_list_excl_count, &args);
if (args.count > 1)
return -1;
else
return 0;
}
static int _foreach_feature(void *x, void *y)
{
char *job_features = (char *)y;
plugin_feature_t *feature = (plugin_feature_t *)x;
if (xstrstr(job_features, feature->name) != NULL) {
return -1;
}
return 0;
}
static int _has_exclusive_features(void *x, void *arg)
{
list_t *feature_list = x;
char *str = NULL;
int rc = 0;
job_features_set2str(feature_list, &str);
log_flag(NODE_FEATURES, "Testing if feature list %s has exclusive features",
str);
if (list_count(feature_list) > 1)
rc = list_for_each(helper_exclusives, _count_exclusivity, str);
xfree(str);
return rc;
}
extern int node_features_p_job_valid(char *job_features, list_t *feature_list)
{
list_t *feature_sets;
int rc;
if (!job_features)
return SLURM_SUCCESS;
if (list_for_each(helper_features, _foreach_feature,
job_features) >= 0) {
/* No helpers features requested */
return SLURM_SUCCESS;
}
/* Check the mutually exclusive lists */
feature_sets = job_features_list2feature_sets(job_features,
feature_list, true);
rc = list_for_each(feature_sets, _has_exclusive_features, NULL);
FREE_NULL_LIST(feature_sets);
if (rc < 0) {
error("job requests mutually exclusive features");
return ESLURM_INVALID_FEATURE;
}
/* Check for unsupported constraint operators in constraint expression */
if (!strpbrk(job_features, "[]*"))
return SLURM_SUCCESS;
/* If an unsupported operator was used, the constraint is valid only if
* the expression doesn't contain a feature handled by this plugin. */
if (list_for_each(helper_features, _foreach_feature, job_features) < 0) {
error("operator(s) \"[]*\" not allowed in constraint \"%s\" when using changeable features",
job_features);
return ESLURM_INVALID_FEATURE;
}
return SLURM_SUCCESS;
}
extern int node_features_p_node_set(char *active_features, bool *need_reboot)
{
char *tmp, *saveptr;
char *input = NULL;
const plugin_feature_t *feature = NULL;
bool reboot = false;
int rc = SLURM_ERROR;
input = xstrdup(active_features);
for (tmp = strtok_r(input, ",", &saveptr); tmp;
tmp = strtok_r(NULL, ",", &saveptr)) {
feature = list_find_first(helper_features, _cmp_features, tmp);
if (!feature) {
info("skipping unregistered feature \"%s\"", tmp);
continue;
}
if (!(feature->flags & FEATURE_FLAG_NO_REBOOT))
reboot = true;
if (_feature_set_state(feature) != SLURM_SUCCESS) {
active_features[0] = '\0';
goto fini;
}
}
*need_reboot = reboot;
rc = SLURM_SUCCESS;
fini:
xfree(input);
return rc;
}
static int _foreach_filter_modes(void *x, void *y)
{
char *feature = (char *)x;
list_t *filtered = y;
/* Verify this mode is legitimate to filter out garbage */
if (list_find_first(helper_features, _cmp_features, feature))
list_append(filtered, xstrdup(feature));
return 0;
}
static int _foreach_check_duplicates(void *x, void *y)
{
char *feature = (char *)x;
list_t *filtered = y;
if (!list_find_first(filtered, _cmp_str, feature))
list_append(filtered, xstrdup(feature));
return 0;
}
typedef struct {
char **avail_modes;
list_t *all_current;
} _foreach_modes_t;
static int _foreach_helper_get_modes(void *x, void *y)
{
char **avail_modes = ((_foreach_modes_t *)y)->avail_modes;
list_t *all_current = ((_foreach_modes_t *)y)->all_current;
plugin_feature_t *feature = (plugin_feature_t *)x;
list_t *current = _feature_get_state(feature);
xstrfmtcat(*avail_modes, "%s%s", (*avail_modes ? "," : ""), feature->name);
if (!current || list_is_empty(current)) {
FREE_NULL_LIST(current);
return 0;
}
/* filter out duplicates */
list_for_each(current, _foreach_check_duplicates, all_current);
FREE_NULL_LIST(current);
return 0;
}
extern void node_features_p_node_state(char **avail_modes, char **current_mode)
{
list_t *all_current = NULL;
list_t *filtered_modes = NULL;
_foreach_modes_t args;
if (!avail_modes || !current_mode)
return;
log_flag(NODE_FEATURES, "original: avail=%s current=%s",
*avail_modes, *current_mode);
all_current = list_create(xfree_ptr);
args.all_current = all_current;
args.avail_modes = avail_modes;
/*
* Call every helper with no args to get list of active features
*/
list_for_each(helper_features, _foreach_helper_get_modes, &args);
filtered_modes = list_create(xfree_ptr);
/* Filter out garbage */
list_for_each(all_current, _foreach_filter_modes, filtered_modes);
list_for_each(filtered_modes, _list_make_str, current_mode);
FREE_NULL_LIST(all_current);
FREE_NULL_LIST(filtered_modes);
log_flag(NODE_FEATURES, "new: avail=%s current=%s",
*avail_modes, *current_mode);
}
extern char *node_features_p_node_xlate(char *new_features, char *orig_features,
char *avail_features, int node_inx)
{
list_t *features = NULL;
char *feature = NULL;
char *input = NULL;
char *merged = NULL;
char *saveptr = NULL;
log_flag(NODE_FEATURES, "new_features: %s", new_features);
log_flag(NODE_FEATURES, "orig_features: %s", orig_features);
log_flag(NODE_FEATURES, "avail_features: %s", avail_features);
if (!new_features || new_features[0] == '\0')
return xstrdup(orig_features);
if (!orig_features || orig_features[0] == '\0')
return xstrdup(new_features);
/* Compute: merged = new_features U (orig_features - changeable_features) */
features = list_create(xfree_ptr);
/* Add all features in "new_features" */
input = xstrdup(new_features);
for (feature = strtok_r(input, ",", &saveptr); feature;
feature = strtok_r(NULL, ",", &saveptr)) {
list_append(features, xstrdup(feature));
}
xfree(input);
input = xstrdup(orig_features);
for (feature = strtok_r(input, ",", &saveptr); feature;
feature = strtok_r(NULL, ",", &saveptr)) {
/* orig_features - plugin_changeable_features */
if (node_features_p_changeable_feature(feature))
continue;
/* new_features U (orig_features - plugin_changeable_features) */
if (list_find_first(features, _cmp_str, feature) != NULL)
continue;
list_append(features, xstrdup(feature));
}
xfree(input);
list_for_each(features, _list_make_str, &merged);
FREE_NULL_LIST(features);
log_flag(NODE_FEATURES, "merged features: %s", merged);
return merged;
}
extern char *node_features_p_job_xlate(char *job_features,
list_t *feature_list,
bitstr_t *job_node_bitmap)
{
if (!job_features)
return NULL;
if (strpbrk(job_features, "[]*")) {
info("an unsupported constraint operator was used in \"%s\", clearing job constraint",
job_features);
return NULL;
}
return _xlate_job_features(job_features, feature_list, job_node_bitmap);
}
/* Return true if the plugin requires PowerSave mode for booting nodes */
extern bool node_features_p_node_power(void)
{
return false;
}
static char *_make_helper_str(const plugin_feature_t *feature)
{
char *str = NULL;
/* Format: "Name Helper=<path>" */
xstrfmtcat(str, "%s Helper=%s", feature->name, feature->helper);
return str;
}
static char *_make_exclusive_str(list_t *exclusive)
{
char *str = NULL;
list_for_each(exclusive, _list_make_str, &str);
return str;
}
static char *_make_uid_str(uid_t *uid_array, int uid_cnt)
{
char *sep = "", *tmp_str = NULL, *uid_str = NULL;
int i;
if (allowed_uid_cnt == 0) {
uid_str = xstrdup("ALL");
return uid_str;
}
for (i = 0; i < uid_cnt; i++) {
tmp_str = uid_to_string(uid_array[i]);
xstrfmtcat(uid_str, "%s%s(%d)", sep, tmp_str, uid_array[i]);
xfree(tmp_str);
sep = ",";
}
return uid_str;
}
static int _make_features_config(void *x, void *y)
{
plugin_feature_t *feature = (plugin_feature_t *)x;
list_t *data = y;
add_key_pair_own(data, "Feature", _make_helper_str(feature));
return 0;
}
static int _make_exclusive_config(void *x, void *y)
{
list_t *exclusive = x;
list_t *data = y;
add_key_pair_own(data, "MutuallyExclusive",
_make_exclusive_str(exclusive));
return 0;
}
/* Get node features plugin configuration */
extern void node_features_p_get_config(config_plugin_params_t *p)
{
list_t *data;
xassert(p);
xstrcat(p->name, plugin_type);
data = p->key_pairs;
list_for_each(helper_features, _make_features_config, data);
list_for_each(helper_exclusives, _make_exclusive_config, data);
add_key_pair_own(data, "AllowUserBoot",
_make_uid_str(allowed_uid, allowed_uid_cnt));
add_key_pair(data, "BootTime", "%u", boot_time);
add_key_pair(data, "ExecTime", "%u", exec_time);
}
extern bitstr_t *node_features_p_get_node_bitmap(void)
{
return node_conf_get_active_bitmap();
}
extern char *node_features_p_node_xlate2(char *new_features)
{
return xstrdup(new_features);
}
extern uint32_t node_features_p_boot_time(void)
{
return boot_time;
}
extern bool node_features_p_user_update(uid_t uid)
{
/* Default is ALL users allowed to update */
if (allowed_uid_cnt == 0)
return true;
for (int i = 0; i < allowed_uid_cnt; i++) {
if (allowed_uid[i] == uid)
return true;
}
log_flag(NODE_FEATURES, "UID %u is not allowed to update node features",
uid);
return false;
}
extern void node_features_p_step_config(bool mem_sort, bitstr_t *numa_bitmap)
{
return;
}
extern int node_features_p_overlap(bitstr_t *active_bitmap)
{
/* Executed on slurmctld and not used by this plugin */
return bit_set_count(active_bitmap);
}
extern int node_features_p_get_node(char *node_list)
{
/* Executed on slurmctld and not used by this plugin */
return SLURM_SUCCESS;
}
extern int node_features_p_node_update(char *active_features,
bitstr_t *node_bitmap)
{
/* Executed on slurmctld and not used by this plugin */
return SLURM_SUCCESS;
}
extern bool node_features_p_node_update_valid(void *node_ptr,
update_node_msg_t *update_node_msg)
{
/* Executed on slurmctld and not used by this plugin */
return true;
}