blob: 7ba89878212e307dc06e86c37458a2fd4dc4695a [file] [log] [blame]
/*****************************************************************************\
* job_features.c
*****************************************************************************
* Copyright (C) SchedMD LLC.
*
* 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 "src/common/job_features.h"
#include "src/common/read_config.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
typedef struct {
bool debug_flag;
list_t *distribute_lists;
list_t *feature_set;
list_t *new_feature_sets;
} distribute_arg_t;
typedef struct {
bool debug_flag;
int last_paren_cnt;
int last_op;
int last_paren_op;
list_t *paren_lists;
list_t *curr_feature_sets;
list_t *final_feature_sets;
list_t *tmp_feature_list;
list_t *working_list;
} evalute_feature_arg_t;
typedef struct {
bool first;
char *pos;
char *str;
} job_feature2str_arg_t;
static int _cmp_job_feature(void *x, void *key)
{
job_feature_t *f1 = x;
job_feature_t *f2 = key;
return !xstrcmp(f1->name, f2->name);
}
static int _foreach_job_feature2str(void *x, void *arg)
{
job_feature_t *job_feat_ptr = x;
job_feature2str_arg_t *args = arg;
if (args->first) {
xstrfmtcatat(args->str, &args->pos, "%s", job_feat_ptr->name);
args->first = false;
} else {
xstrfmtcatat(args->str, &args->pos, ",%s", job_feat_ptr->name);
}
return 0;
}
static int _copy_job_feature_ptr_unique(void *x, void *arg)
{
job_feature_t *job_feat_ptr = x;
list_t *feature_set = arg;
if (!list_find_first_ro(feature_set, _cmp_job_feature, job_feat_ptr))
list_append(feature_set, job_feat_ptr);
return 0;
}
/*
* Merge unique items in feature_set and distribute_set into a new list.
* Append the new list to new_feature_sets.
*/
static int _distribute_one_list(void *x, void *arg)
{
list_t *distribute_set = x;
distribute_arg_t *dist_args = arg;
list_t *new_feature_set;
/*
* NOTE: list_shallow_copy just copies pointers and does not copy the
* destructor function. This is okay because the destructor function is
* already NULL - these are just pointers to job_feature_t, not full
* copies.
*/
new_feature_set = list_shallow_copy(dist_args->feature_set);
list_for_each(distribute_set, _copy_job_feature_ptr_unique,
new_feature_set);
list_append(dist_args->new_feature_sets, new_feature_set);
if (dist_args->debug_flag) {
char *dist_str = NULL;
char *old_str = NULL;
char *new_str = NULL;
job_features_set2str(dist_args->feature_set, &old_str);
job_features_set2str(distribute_set, &dist_str);
job_features_set2str(new_feature_set, &new_str);
log_flag(NODE_FEATURES,
"%s: Copy %s to %s: result list=%s",
__func__, dist_str, old_str, new_str);
xfree(dist_str);
xfree(old_str);
xfree(new_str);
}
return 0;
}
static int _foreach_distribute_lists(void *x, void *arg)
{
list_t *possible_list = x;
distribute_arg_t *distribute_args = arg;
distribute_arg_t distribute_args2 = {
.debug_flag = distribute_args->debug_flag,
.feature_set = possible_list,
.new_feature_sets = distribute_args->new_feature_sets,
};
list_for_each(distribute_args->distribute_lists,
_distribute_one_list,
&distribute_args2);
return 0;
}
/*
*
* Distribute each list in distribute_lists to each list in feature_sets (like a
* multiply). If feature_sets is empty, just transfer distribute_lists into
* feature_sets.
*
* Example:
*
* Job feature string: "a&(b|c)"
*
* When we get to the closing paren:
*
* feature_sets = {[a]}
* distribute_lists = {[b],[c]}
*
* After _distribute_lists, we want feature_sets to be:
*
* {[a,b],[a,c]}
*
*
* Another example:
*
* job feature string: "(a|b)&(c|d)
*
* When we get to the first closing paren "b)":
*
* feature_sets = {}
* distribute_lists = {[a],[b]}
*
* Just transfer distribute_lists into feature_sets:
*
* feature_sets = {[a],[b]}
* distribute_lists = {}
*
* When we get to the second closing paren "d)":
*
* feature_sets = {[a],[b]}
* distribute_lists = {[c],[d]}
*
* Copy [c] and [d] to each of [a] and [b]:
*
* feature_sets = {[a,c],[a,d],[b,c],[b,d]}
*/
static void _distribute_lists(list_t **feature_sets,
list_t *distribute_lists,
bool debug_flag)
{
list_t *new_feature_sets;
xassert(feature_sets);
xassert(*feature_sets);
/*
* Build a new list which will have the distributed features.
* Iterate over the original list.
*/
new_feature_sets = list_create((ListDelF) list_destroy);
if (list_is_empty(*feature_sets)) {
list_transfer(new_feature_sets, distribute_lists);
} else {
distribute_arg_t distribute_args = {
.debug_flag = debug_flag,
.distribute_lists = distribute_lists,
.new_feature_sets = new_feature_sets,
};
if (debug_flag) {
char *feature_sets_str = NULL;
char *paren_lists_str = NULL;
list_for_each(*feature_sets, job_features_set2str,
&feature_sets_str);
list_for_each(distribute_lists, job_features_set2str,
&paren_lists_str);
log_flag(NODE_FEATURES, "%s: Distribute %s to %s",
__func__, feature_sets_str, paren_lists_str);
xfree(feature_sets_str);
xfree(paren_lists_str);
}
list_for_each(*feature_sets, _foreach_distribute_lists,
&distribute_args);
}
FREE_NULL_LIST(*feature_sets);
*feature_sets = new_feature_sets;
}
static int _evaluate_job_feature(void *x, void *arg)
{
job_feature_t *job_feat_ptr = x;
evalute_feature_arg_t *args = arg;
if (args->last_paren_cnt < job_feat_ptr->paren) {
/*
* Start of expression in parentheses.
* Create a list of possible features for the expression
* in the parentheses.
*/
args->last_paren_op = args->last_op;
args->last_op = FEATURE_OP_AND;
args->paren_lists = list_create((ListDelF) list_destroy);
args->working_list = args->paren_lists;
args->tmp_feature_list = NULL;
}
/* Always do matching OR logic */
if ((args->last_op == FEATURE_OP_OR) ||
(args->last_op == FEATURE_OP_MOR)) {
/* New list */
/* Each list-node is a pointer - nothing to free */
args->tmp_feature_list = list_create(NULL);
list_append(args->tmp_feature_list, job_feat_ptr);
list_append(args->working_list, args->tmp_feature_list);
} else { /* FEATURE_OP_AND, other ops not supported */
/*
* - If we're in a paren, append to the current feature list.
* - Otherwise, distribute to all of the current lists.
*/
if (args->paren_lists) {
if (!args->tmp_feature_list) {
args->tmp_feature_list = list_create(NULL);
list_append(args->paren_lists,
args->tmp_feature_list);
}
if (!list_find_first_ro(args->tmp_feature_list,
_cmp_job_feature,
job_feat_ptr)) {
list_append(args->tmp_feature_list,
job_feat_ptr);
}
} else {
list_t *tmp = list_create((ListDelF) list_destroy);
list_t *features = list_create(NULL);
list_append(features, job_feat_ptr);
list_append(tmp, features);
_distribute_lists(&args->curr_feature_sets, tmp,
args->debug_flag);
/* Update working_list to the new list */
args->working_list = args->curr_feature_sets;
FREE_NULL_LIST(tmp);
}
}
if (args->last_paren_cnt > job_feat_ptr->paren) {
/*
* End of expression in parenthesis
* OR: Transfer paren_lists to curr_feature_sets.
*/
if ((args->last_paren_op == FEATURE_OP_OR) ||
(args->last_paren_op == FEATURE_OP_MOR)) {
list_transfer(args->curr_feature_sets,
args->paren_lists);
} else { /* FEATURE_OP_AND, other ops not supported */
/* Distribute to the current feature sets */
_distribute_lists(&args->curr_feature_sets,
args->paren_lists,
args->debug_flag);
}
FREE_NULL_LIST(args->paren_lists);
args->tmp_feature_list = NULL;
/* Update working_list to the new list */
args->working_list = args->curr_feature_sets;
}
if (!args->paren_lists &&
((job_feat_ptr->op_code == FEATURE_OP_OR) ||
(job_feat_ptr->op_code == FEATURE_OP_MOR))) {
/*
* If not inside parentheses, move feature sets in the
* current list to the final list, and start fresh with
* the current list.
*/
list_transfer(args->final_feature_sets,
args->curr_feature_sets);
}
if (args->debug_flag) {
char *curr_feature_sets_str = NULL;
char *final_feature_sets_str = NULL;
char *paren_lists_str = NULL;
if (args->curr_feature_sets)
list_for_each(args->curr_feature_sets,
job_features_set2str,
&curr_feature_sets_str);
if (args->final_feature_sets)
list_for_each(args->final_feature_sets,
job_features_set2str,
&final_feature_sets_str);
if (args->paren_lists)
list_for_each(args->paren_lists,
job_features_set2str,
&paren_lists_str);
log_flag(NODE_FEATURES, "%s: After evaluating feature %s: final feature sets: %s; curr feature sets: %s; paren lists: %s",
__func__, job_feat_ptr->name, final_feature_sets_str,
curr_feature_sets_str, paren_lists_str);
xfree(curr_feature_sets_str);
xfree(final_feature_sets_str);
xfree(paren_lists_str);
}
args->last_op = job_feat_ptr->op_code;
args->last_paren_cnt = job_feat_ptr->paren;
return 0;
}
extern list_t *job_features_list2feature_sets(char *job_features,
list_t *job_feature_list,
bool suppress_log_flag)
{
evalute_feature_arg_t feature_sets_arg = {
.last_paren_cnt = 0,
.last_op = FEATURE_OP_AND,
.last_paren_op = FEATURE_OP_AND,
.paren_lists = NULL,
.curr_feature_sets = NULL,
.final_feature_sets = NULL,
.tmp_feature_list = NULL,
.working_list = NULL,
};
feature_sets_arg.debug_flag =
suppress_log_flag ? false :
(slurm_conf.debug_flags & DEBUG_FLAG_NODE_FEATURES);
feature_sets_arg.curr_feature_sets =
list_create((ListDelF) list_destroy);
feature_sets_arg.final_feature_sets =
list_create((ListDelF) list_destroy);
feature_sets_arg.working_list = feature_sets_arg.curr_feature_sets;
if (feature_sets_arg.debug_flag)
log_flag(NODE_FEATURES, "%s: Convert %s to a matching OR expression",
__func__, job_features);
list_for_each(job_feature_list, _evaluate_job_feature,
&feature_sets_arg);
list_transfer(feature_sets_arg.final_feature_sets,
feature_sets_arg.curr_feature_sets);
FREE_NULL_LIST(feature_sets_arg.curr_feature_sets);
FREE_NULL_LIST(feature_sets_arg.paren_lists);
return feature_sets_arg.final_feature_sets;
}
extern int job_features_set2str(void *x, void *arg)
{
list_t *feature_list = x;
char **str_ptr = arg;
job_feature2str_arg_t feature2str_args = {
.first = true,
.pos = NULL,
.str = *str_ptr,
};
/*
* If a list is already in *str_ptr, then separate this new list
* with a bar |.
*/
if (xstrchr(feature2str_args.str, ')'))
xstrfmtcatat(feature2str_args.str, &feature2str_args.pos, "|(");
else
xstrfmtcatat(feature2str_args.str, &feature2str_args.pos, "(");
list_for_each(feature_list, _foreach_job_feature2str,
&feature2str_args);
xstrfmtcatat(feature2str_args.str, &feature2str_args.pos, ")");
*str_ptr = feature2str_args.str;
return 0;
}