blob: d9f9465febf51883c56ba37542314c5920defbf2 [file] [log] [blame]
/*****************************************************************************\
* openapi.c - OpenAPI plugin handler
*****************************************************************************
* 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/data.h"
#include "src/common/plugrack.h"
#include "src/common/read_config.h"
#include "src/common/xassert.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "src/interfaces/serializer.h"
#include "src/slurmrestd/openapi.h"
#include "src/slurmrestd/operations.h"
#define OPENAPI_MAJOR_TYPE "openapi"
typedef struct {
int (*init)(void);
int (*fini)(void);
int (*get_paths)(const openapi_path_binding_t **paths_ptr,
const openapi_resp_meta_t **meta_ptr);
} funcs_t;
/*
* Must be synchronized with funcs_t above.
*/
static const char *syms[] = {
"slurm_openapi_p_init",
"slurm_openapi_p_fini",
"slurm_openapi_p_get_paths",
};
typedef enum {
OPENAPI_PATH_ENTRY_UNKNOWN = 0,
OPENAPI_PATH_ENTRY_MATCH_STRING,
OPENAPI_PATH_ENTRY_MATCH_PARAMETER,
OPENAPI_PATH_ENTRY_MAX
} entry_type_t;
/*
* This is a simplified entry since OAS allows combos of
* parameters but we will only honor having a single parameter
* as an dir entry for now
*/
typedef struct {
char *entry;
char *name;
entry_type_t type;
openapi_type_t parameter;
} entry_t;
typedef struct {
const openapi_path_binding_method_t *bound;
entry_t *entries;
http_request_method_t method;
} entry_method_t;
#define MAGIC_PATH 0x0a0b09fd
typedef struct {
int magic; /* MAGIC_PATH */
char *path; /* path as string */
const openapi_path_binding_t *bound;
data_parser_t *parser;
entry_method_t *methods;
int tag;
} path_t;
typedef struct {
const data_t *dpath;
path_t *path;
data_t *params;
http_request_method_t method;
entry_t *entry;
int tag;
} match_path_from_data_t;
#define MAGIC_MERGE_PATH 0x22b2ae44
typedef struct {
int magic; /* MAGIC_MERGE_PATH */
data_t *paths;
data_t *server_path;
} merge_path_t;
#define MAGIC_MERGE_ID_PATH 0x22b2aeae
typedef struct {
int magic; /* MAGIC_MERGE_ID_PATH */
data_t *server_path;
char *operation;
char *at;
char *path;
merge_path_t *merge_args;
} id_merge_path_t;
#define MAGIC_OAS 0x1218eeee
typedef struct {
int magic; /* MAGIC_OAS */
const char **mime_types;
data_t *spec;
data_t *tags;
data_t *paths;
data_t *components;
data_t *components_schemas;
data_t *security_schemas;
data_t *info;
data_t *contact;
data_t *license;
data_t *servers;
data_t *security;
data_t *x_slurm;
/* tracked references per data_parser */
void **references;
} openapi_spec_t;
static list_t *paths = NULL;
static int path_tag_counter = 0;
static plugins_t *plugins = NULL;
static data_parser_t **parsers = NULL; /* symlink to parser array */
static const struct {
char *openapi_version;
struct {
char *title;
char *desc;
char *tos;
struct {
char *name;
char *url;
char *email;
} contact;
struct {
char *name;
char *url;
} license;
} info;
struct {
char *name;
char *desc;
} tags[3];
struct {
char *url;
} servers[1];
/* security is too complex for here */
struct {
struct security_scheme_s {
char *key;
char *type;
char *desc;
char *name;
char *in;
char *scheme;
char *bearer_format;
} security_schemes[3];
} components;
} openapi_spec = {
.openapi_version = "3.0.3",
.info = {
.title = "Slurm REST API",
.desc = "API to access and control Slurm",
.tos = "https://github.com/SchedMD/slurm/blob/master/DISCLAIMER",
.contact = {
.name = "SchedMD LLC",
.url = "https://www.schedmd.com/",
.email = "sales@schedmd.com",
},
.license = {
.name = "Apache 2.0",
.url = "https://www.apache.org/licenses/LICENSE-2.0.html",
},
},
.tags = {
{
.name = "slurm",
.desc = "methods that query slurmctld",
},
{
.name = "slurmdb",
.desc = "methods that query slurmdbd",
},
{
.name = "openapi",
.desc = "methods that query for generated OpenAPI specifications",
},
},
.servers = {
{
.url = "/",
},
},
.components = {
.security_schemes = {
{
#define SEC_SCHEME_USER_INDEX 0
.key = "user",
.type = "apiKey",
.desc = "User name",
.name = "X-SLURM-USER-NAME",
.in = "header",
},
{
#define SEC_SCHEME_TOKEN_INDEX 1
.key = "token",
.type = "apiKey",
.desc = "User access token",
.name = "X-SLURM-USER-TOKEN",
.in = "header",
},
{
#define SEC_SCHEME_BEARER_INDEX 2
.key = "bearerAuth",
.type = "http",
.desc = "Bearer Authentication",
.scheme = "bearer",
.bearer_format = "JWT",
},
},
},
};
static const openapi_path_binding_method_t openapi_methods[] = {
{
.method = HTTP_REQUEST_GET,
.tags = (const char*[]) { "openapi", NULL },
.summary = "Retrieve OpenAPI Specification",
.response = {
.type = DATA_PARSER_OPENAPI_SHARES_RESP,
.description = "OpenAPI Specification",
},
.parameters = DATA_PARSER_SHARES_REQ_MSG,
},
{0}
};
static int _op_handler_openapi(openapi_ctxt_t *ctxt);
#define OP_FLAGS (OP_BIND_HIDDEN_OAS | OP_BIND_NO_SLURMDBD)
/*
* Paths to generate OpenAPI specification
*/
static const openapi_path_binding_t openapi_paths[] = {
{
.path = "/openapi.json",
.callback = _op_handler_openapi,
.methods = openapi_methods,
.flags = OP_FLAGS,
},
{
.path = "/openapi.yaml",
.callback = _op_handler_openapi,
.methods = openapi_methods,
.flags = OP_FLAGS,
},
{
.path = "/openapi",
.callback = _op_handler_openapi,
.methods = openapi_methods,
.flags = OP_FLAGS,
},
{
.path = "/openapi/v3",
.callback = _op_handler_openapi,
.methods = openapi_methods,
.flags = OP_FLAGS,
},
{0}
};
static const http_status_code_t *response_status_codes = NULL;
/*
* Default to "default" and 200 as openapi generator breaks with only "default"
* response code.
*/
static const http_status_code_t default_response_status_codes[] = {
HTTP_STATUS_CODE_SUCCESS_OK,
HTTP_STATUS_CODE_DEFAULT,
HTTP_STATUS_NONE
};
static char *_entry_to_string(entry_t *entry);
static const char *_get_entry_type_string(entry_type_t type)
{
switch (type) {
case OPENAPI_PATH_ENTRY_MATCH_STRING:
return "string";
case OPENAPI_PATH_ENTRY_MATCH_PARAMETER:
return "parameter";
default:
return "invalid";
}
}
static int _resolve_parser_index(data_parser_t *parser)
{
for (int i = 0; parsers[i]; i++)
if (parsers[i] == parser)
return i;
fatal_abort("Unable to find parser. This should never happen!");
}
static void _free_entry_list(entry_t *entry, int tag,
entry_method_t *method)
{
entry_t *itr = entry;
if (!entry)
return;
while (itr->type) {
debug5("%s: remove path tag:%d method:%s entry:%s name:%s",
__func__, tag,
(method ? get_http_method_string(method->method) :
"N/A"),
itr->entry, itr->name);
xfree(itr->entry);
xfree(itr->name);
itr++;
}
xfree(entry);
}
static void _list_delete_path_t(void *x)
{
entry_method_t *em;
if (!x)
return;
path_t *path = x;
xassert(path->magic == MAGIC_PATH);
xassert(path->tag != -1);
em = path->methods;
while (em->entries) {
debug5("%s: remove path tag:%d method:%s", __func__, path->tag,
get_http_method_string(em->method));
_free_entry_list(em->entries, path->tag, em);
em->entries = NULL;
em++;
}
xfree(path->methods);
xfree(path->path);
path->magic = ~MAGIC_PATH;
xfree(path);
}
static entry_t *_parse_openapi_path(const char *str_path, int *count_ptr)
{
char *save_ptr = NULL;
char *buffer = xstrdup(str_path);
char *token = strtok_r(buffer, "/", &save_ptr);
entry_t *entries = NULL;
entry_t *entry = NULL;
int count = 0;
/* find max bound on number of entries */
for (const char *i = str_path; *i; i++)
if (*i == '/')
count++;
if (count > 1024)
fatal_abort("%s: url %s is way too long", str_path, __func__);
entry = entries = xcalloc((count + 1), sizeof(entry_t));
while (token) {
const size_t slen = strlen(token);
/* ignore // entries */
if (slen <= 0)
goto again;
entry->entry = xstrdup(token);
if (!xstrcmp(token, ".") || !xstrcmp(token, "..")) {
/*
* there should not be a .. or . in a path
* definition, it just doesn't make any sense
*/
error("%s: invalid %s at entry",
__func__, token);
goto fail;
} else if (slen > 3 && token[0] == '{' &&
token[slen - 1] == '}') {
entry->type = OPENAPI_PATH_ENTRY_MATCH_PARAMETER;
entry->name = xstrndup(token + 1, slen - 2);
debug5("%s: parameter %s at entry %s",
__func__, entry->name, token);
} else { /* not a variable */
entry->type = OPENAPI_PATH_ENTRY_MATCH_STRING;
entry->name = NULL;
debug5("%s: string match entry %s",
__func__, token);
}
entry++;
xassert(entry <= entries + count);
again:
token = strtok_r(NULL, "/", &save_ptr);
}
/* last is always NULL */
xassert(!entry->type);
xfree(buffer);
if (count_ptr)
*count_ptr = count;
return entries;
fail:
_free_entry_list(entries, -1, NULL);
xfree(buffer);
if (count_ptr)
*count_ptr = -1;
return NULL;
}
static int _print_path_tag_methods(void *x, void *arg)
{
path_t *path = (path_t *) x;
int *tag = (int *) arg;
xassert(path->magic == MAGIC_PATH);
if (path->tag != *tag)
return 0;
if (!path->methods->entries)
debug4("%s: no methods found in path tag %d",
__func__, path->tag);
for (entry_method_t *em = path->methods; em->entries; em++) {
char *path_str = _entry_to_string(em->entries);
debug4("%s: path tag %d entry: %s %s",
__func__, path->tag, get_http_method_string(em->method),
path_str);
xfree(path_str);
}
/*
* We found the (unique) tag, so return -1 to exit early. The item's
* index returned by list_for_each_ro() will be negative.
*/
return -1;
}
extern void print_path_tag_methods(int tag)
{
if (get_log_level() < LOG_LEVEL_DEBUG4)
return;
if (list_for_each_ro(paths, _print_path_tag_methods, &tag) >= 0)
error("%s: Tag %d not found in paths", __func__, tag);
}
static void _clone_entries(entry_t **dst_ptr, entry_t *src, int count)
{
entry_t *dst = xcalloc((count + 1), sizeof(*dst));
xassert(!*dst_ptr);
xassert(count > 0);
*dst_ptr = dst;
for (; src->type; src++, dst++) {
dst->entry = xstrdup(src->entry);
dst->name = xstrdup(src->name);
dst->type = src->type;
dst->parameter = src->parameter;
}
}
static void _check_openapi_path_binding(const openapi_path_binding_t *op_path)
{
#ifndef NDEBUG
xassert(op_path->path);
xassert(op_path->callback);
xassert((op_path->flags == OP_BIND_NONE) ||
((op_path->flags > OP_BIND_NONE) &&
(op_path->flags < OP_BIND_INVALID_MAX)));
for (int i = 0;; i++) {
const openapi_path_binding_method_t *method =
&op_path->methods[i];
if (method->method == HTTP_REQUEST_INVALID)
break;
xassert(method->summary && method->summary[0]);
xassert(method->response.description &&
method->response.description[0]);
xassert(method->method > HTTP_REQUEST_INVALID);
xassert(method->method < HTTP_REQUEST_MAX);
xassert(method->tags && method->tags[0]);
xassert(method->response.type > DATA_PARSER_TYPE_INVALID);
xassert(method->response.type < DATA_PARSER_TYPE_MAX);
xassert(method->parameters >= DATA_PARSER_TYPE_INVALID);
xassert(method->parameters < DATA_PARSER_TYPE_MAX);
xassert(method->query >= DATA_PARSER_TYPE_INVALID);
xassert(method->query < DATA_PARSER_TYPE_MAX);
xassert(method->body.type >= DATA_PARSER_TYPE_INVALID);
xassert(method->body.type < DATA_PARSER_TYPE_MAX);
}
#endif /* !NDEBUG */
}
static bool _data_parser_supports_type(data_parser_t *parser,
data_parser_type_t type)
{
openapi_type_t oapi_type;
oapi_type = data_parser_g_resolve_openapi_type(parser, type, NULL);
xassert(oapi_type >= OPENAPI_TYPE_INVALID);
xassert(oapi_type < OPENAPI_TYPE_MAX);
return (oapi_type != OPENAPI_TYPE_INVALID);
}
static bool _data_parser_supports_method(data_parser_t *parser,
const openapi_path_binding_method_t *m)
{
/* check that parser supports each possible type if set */
if ((m->response.type != DATA_PARSER_TYPE_INVALID) &&
!_data_parser_supports_type(parser, m->response.type))
return false;
if ((m->parameters != DATA_PARSER_TYPE_INVALID) &&
!_data_parser_supports_type(parser, m->parameters))
return false;
if ((m->query != DATA_PARSER_TYPE_INVALID) &&
!_data_parser_supports_type(parser, m->query))
return false;
if ((m->body.type != DATA_PARSER_TYPE_INVALID) &&
!_data_parser_supports_type(parser, m->body.type))
return false;
return true;
}
extern int register_path_binding(const char *in_path,
const openapi_path_binding_t *op_path,
const openapi_resp_meta_t *meta,
data_parser_t *parser, int *tag_ptr)
{
entry_t *entries = NULL;
int tag = -1, methods_count = 0, entries_count = 0;
path_t *p = NULL;
const char *path = (in_path ? in_path : op_path->path);
debug4("%s: attempting to bind %s with %s",
__func__, (parser ? data_parser_get_plugin_version(parser) :
"data_parser/none"), path);
xassert(!!in_path == !!(op_path->flags & OP_BIND_DATA_PARSER));
_check_openapi_path_binding(op_path);
if (!(entries = _parse_openapi_path(path, &entries_count)))
fatal("%s: parse_openapi_path(%s) failed", __func__, path);
for (int i = 0; op_path->methods[i].method != HTTP_REQUEST_INVALID;
i++) {
if (!_data_parser_supports_method(parser, &op_path->methods[i]))
continue;
methods_count++;
}
if (!methods_count) {
debug5("%s: skip binding %s with %s",
__func__, path, data_parser_get_plugin(parser));
_free_entry_list(entries, -1, NULL);
return ESLURM_NOT_SUPPORTED;
}
tag = path_tag_counter++;
p = xmalloc(sizeof(*p));
p->magic = MAGIC_PATH;
p->methods = xcalloc((methods_count + 1), sizeof(*p->methods));
p->tag = tag;
p->bound = op_path;
p->parser = parser;
p->path = xstrdup(path);
for (int i = 0, mi = 0;; i++) {
const openapi_path_binding_method_t *m = &op_path->methods[i];
entry_method_t *t = &p->methods[mi];
entry_t *e;
if (m->method == HTTP_REQUEST_INVALID)
break;
/*
* Skip method if data_parser does not support any of the in/out
* types which would just cause slurmrestd to abort() later.
*/
if (!_data_parser_supports_method(parser, m)) {
debug5("%s: skip binding \"%s %s\" with %s",
__func__, get_http_method_string(m->method),
path, data_parser_get_plugin(parser));
continue;
}
t->method = m->method;
t->bound = m;
if (i != 0) {
_clone_entries(&t->entries, entries, entries_count);
e = t->entries;
} else {
p->methods[0].entries = e = entries;
}
for (; e->type; e++) {
if (e->type == OPENAPI_PATH_ENTRY_MATCH_PARAMETER)
e->parameter =
data_parser_g_resolve_openapi_type(
parser, m->parameters, e->name);
debug5("%s: add binded path %s entry: method=%s tag=%d entry=%s name=%s parameter=%s entry_type=%s",
__func__, path,
get_http_method_string(m->method), tag, e->entry,
e->name, openapi_type_to_string(e->parameter),
_get_entry_type_string(e->type));
}
/* only move to next method if populated */
mi++;
}
list_append(paths, p);
*tag_ptr = tag;
return SLURM_SUCCESS;
}
/*
* Check if the entry matches based on the OAS type
* and if it does, then add that matched parameter
*/
static bool _match_param(const data_t *data, match_path_from_data_t *args)
{
bool matched = false;
entry_t *entry = args->entry;
data_t *params = args->params;
data_t *match = data_new();
data_copy(match, data);
switch (entry->parameter) {
case OPENAPI_TYPE_NUMBER:
{
if (data_convert_type(match, DATA_TYPE_FLOAT) ==
DATA_TYPE_FLOAT) {
data_set_float(data_key_set(params, entry->name),
data_get_float(match));
matched = true;
}
break;
}
case OPENAPI_TYPE_INTEGER:
{
if (data_convert_type(match, DATA_TYPE_INT_64) ==
DATA_TYPE_INT_64) {
data_set_int(data_key_set(params, entry->name),
data_get_int(match));
matched = true;
}
break;
}
default: /* assume string */
debug("%s: unknown parameter type %s",
__func__, openapi_type_to_string(entry->parameter));
/* fall through */
case OPENAPI_TYPE_STRING:
{
if (data_convert_type(match, DATA_TYPE_STRING) ==
DATA_TYPE_STRING) {
data_set_string(data_key_set(params, entry->name),
data_get_string(match));
matched = true;
}
break;
}
}
if (get_log_level() >= LOG_LEVEL_DEBUG5) {
char *str = NULL;
data_get_string_converted(data, &str);
debug5("%s: parameter %s[%s]->%s[%s] result=%s",
__func__, entry->name,
openapi_type_to_string(entry->parameter),
str, data_get_type_string(data),
(matched ? "matched" : "failed"));
xfree(str);
}
FREE_NULL_DATA(match);
return matched;
}
static data_for_each_cmd_t _match_path(const data_t *data, void *y)
{
match_path_from_data_t *args = y;
entry_t *entry = args->entry;
if (!entry->type)
return DATA_FOR_EACH_FAIL;
if (entry->type == OPENAPI_PATH_ENTRY_MATCH_STRING) {
bool match;
if (data_get_type(data) != DATA_TYPE_STRING)
return DATA_FOR_EACH_FAIL;
match = !xstrcmp(data_get_string(data), entry->entry);
debug5("%s: string attempt match %s to %s: %s",
__func__, entry->entry, data_get_string(data),
(match ? "SUCCESS" : "FAILURE"));
if (!match)
return DATA_FOR_EACH_FAIL;
} else if (entry->type == OPENAPI_PATH_ENTRY_MATCH_PARAMETER) {
if (!_match_param(data, args))
return DATA_FOR_EACH_FAIL;
} else
fatal_abort("%s: unknown OAS path entry match type",
__func__);
args->entry++;
return DATA_FOR_EACH_CONT;
}
static char *_entry_to_string(entry_t *entry)
{
char *path = NULL;
data_t *d = data_set_list(data_new());
for (; entry->type; entry++) {
switch (entry->type) {
case OPENAPI_PATH_ENTRY_MATCH_STRING:
data_set_string(data_list_append(d), entry->entry);
break;
case OPENAPI_PATH_ENTRY_MATCH_PARAMETER:
data_set_string_fmt(data_list_append(d), "{%s}",
entry->name);
break;
case OPENAPI_PATH_ENTRY_UNKNOWN:
case OPENAPI_PATH_ENTRY_MAX:
fatal_abort("invalid entry type");
}
}
serialize_g_data_to_string(&path, NULL, d, MIME_TYPE_JSON,
SER_FLAGS_COMPACT);
FREE_NULL_DATA(d);
return path;
}
static int _match_path_from_data(void *x, void *key)
{
char *dst_path = NULL, *src_path = NULL;
match_path_from_data_t *args = key;
path_t *path = x;
entry_method_t *method;
bool matched = false;
xassert(path->magic == MAGIC_PATH);
if (get_log_level() >= LOG_LEVEL_DEBUG5) {
serialize_g_data_to_string(&dst_path, NULL, args->dpath,
MIME_TYPE_JSON, SER_FLAGS_COMPACT);
}
args->path = path;
for (method = path->methods; method->entries; method++) {
int entries = 0;
if (get_log_level() >= LOG_LEVEL_DEBUG5) {
xfree(src_path);
src_path = _entry_to_string(method->entries);
}
if (args->method != method->method) {
debug5("%s: method skip for %s(%d, %s != %s) to %s(0x%"PRIXPTR")",
__func__, src_path, args->path->tag,
get_http_method_string(args->method),
get_http_method_string(method->method),
dst_path, (uintptr_t) args->dpath);
continue;
}
for (args->entry = method->entries; args->entry->type;
entries++, args->entry++)
/* do nothing */;
if (data_get_list_length(args->dpath) != entries) {
debug5("%s: skip non-matching subdirectories: registered=%u requested=%zu ",
__func__, entries,
data_get_list_length(args->dpath));
continue;
}
args->entry = method->entries;
if (data_list_for_each_const(args->dpath, _match_path,
args) < 0) {
debug5("%s: match failed %s",
__func__, args->entry->entry);
continue;
}
/*
* The list is NULL terminated, so if entry->type is not NULL
* we didn't match the whole list, but we already don't have
* anything to compare in request.
*/
if (!args->entry->type) {
args->tag = path->tag;
matched = true;
break;
}
}
debug5("%s: match %s for %s(%d, %s) to %s(0x%"PRIXPTR")",
__func__,
matched ? "successful" : "failed",
src_path,
args->path->tag,
get_http_method_string(args->method),
dst_path,
(uintptr_t) args->dpath);
xfree(src_path);
xfree(dst_path);
return matched;
}
extern int find_path_tag(const data_t *dpath, data_t *params,
http_request_method_t method)
{
match_path_from_data_t args = {
.params = params,
.dpath = dpath,
.method = method,
.tag = -1,
};
xassert(data_get_type(params) == DATA_TYPE_DICT);
(void) list_find_first(paths, _match_path_from_data, &args);
return args.tag;
}
static int _bind_paths(const openapi_path_binding_t *paths,
const openapi_resp_meta_t *meta)
{
int rc = SLURM_SUCCESS;
for (int i = 0; paths[i].path; i++) {
const openapi_path_binding_t *op_path = &paths[i];
if ((rc = bind_operation_path(op_path, meta)))
break;
}
return rc;
}
extern int init_openapi(const char *plugin_list, plugrack_foreach_t listf,
data_parser_t **parsers_ptr,
const http_status_code_t *resp_status_codes)
{
int rc;
if (paths)
fatal("%s called twice", __func__);
if (resp_status_codes)
response_status_codes = resp_status_codes;
else
response_status_codes = default_response_status_codes;
paths = list_create(_list_delete_path_t);
/* must have JSON plugin to parse the openapi.json */
serializer_required(MIME_TYPE_JSON);
if ((rc = _bind_paths(openapi_paths, NULL)))
fatal("Unable to bind openapi specification paths: %s",
slurm_strerror(rc));
rc = load_plugins(&plugins, OPENAPI_MAJOR_TYPE, plugin_list, listf,
syms, ARRAY_SIZE(syms));
if (!xstrcasecmp("list", plugin_list))
return SLURM_SUCCESS;
if (rc)
fatal("Loading OpenAPI plugins failed: %s", slurm_strerror(rc));
if ((rc = load_plugins(&plugins, OPENAPI_MAJOR_TYPE, plugin_list, listf,
syms, ARRAY_SIZE(syms))))
fatal("Loading OpenAPI plugins failed: %s", slurm_strerror(rc));
if (!plugins->count)
fatal("No OpenAPI plugins loaded.");
parsers = parsers_ptr;
for (size_t i = 0; i < plugins->count; i++) {
const funcs_t *funcs = plugins->functions[i];
const openapi_path_binding_t *paths;
const openapi_resp_meta_t *meta;
if ((rc = funcs->get_paths(&paths, &meta)))
fatal("Failure loading plugin path bindings: %s",
slurm_strerror(rc));
if ((rc = _bind_paths(paths, meta)))
fatal("Unable to bind openapi specification paths: %s",
slurm_strerror(rc));
}
/* Call init() after all plugins are fully loaded */
for (size_t i = 0; i < plugins->count; i++) {
const funcs_t *funcs = plugins->functions[i];
funcs->init();
}
return rc;
}
extern void destroy_openapi(void)
{
if (!paths)
return;
for (size_t i = 0; i < plugins->count; i++) {
const funcs_t *funcs = plugins->functions[i];
funcs->fini();
}
FREE_NULL_PLUGINS(plugins);
FREE_NULL_LIST(paths);
}
static data_for_each_cmd_t _merge_operationId_strings(data_t *data, void *arg)
{
id_merge_path_t *args = arg;
char *p;
xassert(args->magic == MAGIC_MERGE_ID_PATH);
xassert(args->merge_args->magic == MAGIC_MERGE_PATH);
if (data_convert_type(data, DATA_TYPE_STRING) != DATA_TYPE_STRING)
return DATA_FOR_EACH_FAIL;
p = xstrdup(data_get_string(data));
/* sub out '.' for '_' to avoid breaking compilers */
for (int s = strlen(p), i = 0; i < s; i++)
if ((p[i] == '.') || (p[i] == '{') || (p[i] == '}'))
p[i] = '_';
xstrfmtcatat(args->operation, &args->at, "%s%s",
(args->operation ? "_" : ""), data_get_string(data));
xfree(p);
return DATA_FOR_EACH_CONT;
}
static data_for_each_cmd_t _foreach_strip_params(data_t *data, void *arg)
{
const char *item = data_get_string(data);
data_t **last_ptr = arg;
int len = strlen(item);
xassert(item);
if (!item || (item[0] != '{')) {
char *dst = xstrdup(item);
char *last = dst;
/* strip out '.' */
for (int i = 0; i < len; i++) {
if (item[i] == '.')
continue;
*last = item[i];
last++;
}
*last = '\0';
data_set_string_own(data, dst);
*last_ptr = data;
return DATA_FOR_EACH_CONT;
}
xassert(len > 2);
xassert(item[len - 1] == '}');
if (*last_ptr &&
!xstrncmp(data_get_string(*last_ptr), (item + 1), (len - 2))) {
/*
* Last item is the same as the parameter name which means that
* the item is uncountable and we need to set last as single.
*/
data_set_string(data, data_get_string(*last_ptr));
data_set_string(*last_ptr, "single");
return DATA_FOR_EACH_CONT;
}
return DATA_FOR_EACH_DELETE;
}
/* Caller must xfree() returned string */
static char *_get_method_operationId(openapi_spec_t *spec, path_t *path,
const openapi_path_binding_method_t
*method)
{
data_t *merge[10] = {0}, *dpath, *merged = NULL, *last = NULL;
const char *method_str = get_http_method_string_lc(method->method);
int i = 0;
merge_path_t merge_args = {
.magic = MAGIC_MERGE_PATH,
.paths = spec->paths,
};
id_merge_path_t merge_id_args = {
.magic = MAGIC_MERGE_ID_PATH,
.merge_args = &merge_args,
};
dpath = parse_url_path(path->path, false, true);
xassert((data_get_list_length(dpath) + 1) < (ARRAY_SIZE(merge) - 1));
(void) data_list_for_each(dpath, _foreach_strip_params, &last);
if (data_get_list_length(dpath) < 3) {
/* unversioned paths */
merge[i++] = data_set_string(data_new(), method_str);
} else {
merge[i++] = data_list_dequeue(dpath); /* slurm vs slurmdb */
merge[i++] = data_list_dequeue(dpath); /* v0.0.XX */
merge[i++] = data_set_string(data_new(), method_str);
}
while (data_get_list_length(dpath) && (i < (ARRAY_SIZE(merge) - 1)))
merge[i++] = data_list_dequeue(dpath);
merged = data_list_join((const data_t **) merge, true);
if (data_list_for_each(merged, _merge_operationId_strings,
&merge_id_args) < 0)
fatal_abort("_merge_operationId_strings() failed which should never happen");
for (i = 0; i < ARRAY_SIZE(merge); i++)
FREE_NULL_DATA(merge[i]);
FREE_NULL_DATA(merged);
FREE_NULL_DATA(dpath);
debug5("%s: [%s %s] setting OperationId: %s",
__func__, method_str, path->path, merge_id_args.operation);
return merge_id_args.operation;
}
static int _populate_method(path_t *path, openapi_spec_t *spec, data_t *dpath,
const openapi_path_binding_method_t *method)
{
const char **mime_types = spec->mime_types;
void *refs = &spec->references[_resolve_parser_index(path->parser)];
data_t *dmethod = data_set_dict(data_key_set(dpath,
get_http_method_string_lc(method->method)));
data_t *dtags = data_set_list(data_key_set(dmethod, "tags"));
for (int i = 0; method->tags[i]; i++)
data_set_string(data_list_append(dtags), method->tags[i]);
if (method->summary)
data_set_string(data_key_set(dmethod, "summary"),
method->summary);
if (method->description)
data_set_string(data_key_set(dmethod, "description"),
method->description);
if (data_parser_g_is_deprecated(path->parser))
data_set_bool(data_key_set(dmethod, "deprecated"), true);
{
char *opid = _get_method_operationId(spec, path, method);
data_set_string_own(data_key_set(dmethod, "operationId"), opid);
}
if (method->parameters || method->query) {
/*
* Use existing replacements
*/
data_t *dst = data_key_set(dmethod, "parameters");
if (data_parser_g_populate_parameters(path->parser,
method->parameters,
method->query, refs, dst,
spec->components_schemas))
fatal_abort("data_parser_g_populate_parameters() failed");
}
if (method->response.type) {
data_t *dresp = data_set_dict(data_key_set(dmethod, "responses"));
data_t *resp_code = data_set_dict(data_new());
data_t *cnt = data_set_dict(data_key_set(resp_code, "content"));
if (method->response.description)
data_set_string(data_set_dict(data_key_set(resp_code,
"description")), method->response.description);
for (int i = 0; mime_types[i]; i++) {
data_t *dtype, *dschema;
/*
* Never return URL encoded mimetype as it is only for
* HTTP query
*/
if (!xstrcmp(mime_types[i], MIME_TYPE_URL_ENCODED))
continue;
dtype = data_set_dict(data_key_set(cnt, mime_types[i]));
dschema = data_set_dict(data_key_set(dtype, "schema"));
if (data_parser_g_populate_schema(path->parser,
method->response.type, refs, dschema,
spec->components_schemas))
fatal_abort("data_parser_g_populate_schema() failed");
}
for (int i = 0; response_status_codes[i]; i++) {
const http_status_code_t code =
response_status_codes[i];
char str[64];
if (code == HTTP_STATUS_CODE_DEFAULT)
snprintf(str, sizeof(str), "%s",
get_http_status_code_string(code));
else
snprintf(str, sizeof(str), "%u", code);
data_copy(data_key_set(dresp, str), resp_code);
}
FREE_NULL_DATA(resp_code);
}
if (method->body.type) {
data_t *dbody = data_set_dict(data_key_set(dmethod,
"requestBody"));
data_t *cnt = data_set_dict(data_key_set(dbody, "content"));
if (method->body.description)
data_set_string(data_set_dict(data_key_set(dbody,
"description")), method->body.description);
for (int i = 0; mime_types[i]; i++) {
data_t *dtype, *dschema;
/*
* Never return URL encoded mimetype as it is only for
* HTTP query
*/
if (!xstrcmp(mime_types[i], MIME_TYPE_URL_ENCODED))
continue;
dtype = data_set_dict(data_key_set(cnt, mime_types[i]));
dschema = data_set_dict(data_key_set(dtype, "schema"));
if (data_parser_g_populate_schema(path->parser,
method->body.type, refs, dschema,
spec->components_schemas))
fatal_abort("data_parser_g_populate_schema() failed");
}
}
return SLURM_SUCCESS;
}
static int _foreach_add_path(void *x, void *arg)
{
int rc = SLURM_SUCCESS;
path_t *path = x;
openapi_spec_t *spec = arg;
const openapi_path_binding_t *bound = path->bound;
data_t *dpath;
xassert(spec->magic == MAGIC_OAS);
xassert(path->magic == MAGIC_PATH);
if (!bound)
return SLURM_SUCCESS;
if (bound->flags & OP_BIND_HIDDEN_OAS)
return SLURM_SUCCESS;
xassert(!data_key_get(spec->paths, path->path));
dpath = data_set_dict(data_key_set(spec->paths, path->path));
for (int i = 0; !rc && path->methods[i].method; i++)
rc = _populate_method(path, spec, dpath,
path->methods[i].bound);
return rc;
}
static int _foreach_count_path(void *x, void *arg)
{
path_t *path = x;
openapi_spec_t *spec = arg;
const char **mime_types = spec->mime_types;
const openapi_path_binding_t *bound = path->bound;
void *refs;
xassert(spec->magic == MAGIC_OAS);
xassert(path->magic == MAGIC_PATH);
if (!bound)
return SLURM_SUCCESS;
refs = &spec->references[_resolve_parser_index(path->parser)];
for (int i = 0; path->methods[i].method; i++) {
const openapi_path_binding_method_t *method =
path->methods[i].bound;
if (method->parameters &&
data_parser_g_increment_reference(path->parser,
method->parameters, refs))
fatal_abort("data_parser_g_increment_reference() failed");
if (method->query &&
data_parser_g_increment_reference(path->parser,
method->query, refs))
fatal_abort("data_parser_g_increment_reference() failed");
if (method->body.type) {
/*
* Need to add 1 reference per mime type that will get
* dumped
*/
for (int i = 0; mime_types[i]; i++)
if (data_parser_g_increment_reference(
path->parser, method->body.type, refs))
fatal_abort("data_parser_g_increment_reference() failed");
}
if (method->response.type) {
/*
* Need to add 1 reference per mime type that will get
* dumped
*/
for (int i = 0; mime_types[i]; i++)
if (data_parser_g_increment_reference(
path->parser, method->response.type,
refs))
fatal_abort("data_parser_g_increment_reference() failed");
}
}
return SLURM_SUCCESS;
}
extern int generate_spec(data_t *dst, const char **mime_types)
{
openapi_spec_t spec = {
.magic = MAGIC_OAS,
.spec = dst,
.mime_types = mime_types,
};
data_t *security1, *security2, *security3;
data_t *openapi_plugins, *data_parsers, *slurm_version;
char *version = xstrdup_printf("Slurm-%s", SLURM_VERSION_STRING);
int parsers_count;
/* count the parsers present to allocate refs counts */
for (parsers_count = 0; parsers[parsers_count]; parsers_count++);
spec.references = xcalloc(parsers_count, sizeof(*spec.references));
data_set_dict(spec.spec);
spec.tags = data_set_list(data_key_set(spec.spec, "tags"));
spec.paths = data_set_dict(data_key_set(spec.spec, "paths"));
spec.components = data_set_dict(data_key_set(spec.spec, "components"));
spec.components_schemas = data_set_dict(data_key_set(spec.components,
"schemas"));
spec.security_schemas = data_set_dict(data_key_set(spec.components,
"securitySchemes"));
spec.info = data_set_dict(data_key_set(spec.spec, "info"));
spec.contact = data_set_dict(data_key_set(spec.info, "contact"));
spec.license = data_set_dict(data_key_set(spec.info, "license"));
spec.x_slurm = data_set_dict(data_key_set(spec.info, "x-slurm"));
spec.servers = data_set_list(data_key_set(spec.spec, "servers"));
spec.security = data_set_list(data_key_set(spec.spec, "security"));
security1 = data_set_dict(data_list_append(spec.security));
security2 = data_set_dict(data_list_append(spec.security));
security3 = data_set_dict(data_list_append(spec.security));
data_parsers =
data_set_list(data_key_set(spec.x_slurm, "data_parsers"));
openapi_plugins = data_set_list(data_key_set(spec.x_slurm, "openapi"));
slurm_version = data_set_dict(data_key_set(spec.x_slurm, "version"));
data_set_string(data_key_set(spec.spec, "openapi"),
openapi_spec.openapi_version);
data_set_string(data_key_set(spec.info, "title"), openapi_spec.info.title);
data_set_string(data_key_set(spec.info, "description"),
openapi_spec.info.desc);
data_set_string(data_key_set(spec.info, "termsOfService"),
openapi_spec.info.tos);
data_set_string_own(data_key_set(spec.info, "version"), version);
/* Populate info spec extension x-slurm with data_parsers and flags */
for (int i = 0; i < parsers_count; i++) {
data_t *tmp_data = data_list_append(data_parsers);
data_set_dict(tmp_data);
data_set_string(data_key_set(tmp_data, "plugin"),
data_parser_get_plugin_version(parsers[i]));
if (data_parser_g_dump_flags(parsers[i],
data_key_set(tmp_data, "flags")))
fatal_abort("data_parser_g_dump_flags() failed");
}
/* Populate info spec extension x-slurm with openapi plugins */
for (int i = 0; i < plugins->count; i++)
data_set_string(data_list_append(openapi_plugins),
plugins->types[i]);
data_set_string(data_key_set(slurm_version, "major"), SLURM_MAJOR);
data_set_string(data_key_set(slurm_version, "micro"), SLURM_MICRO);
data_set_string(data_key_set(slurm_version, "minor"), SLURM_MINOR);
data_set_string(data_key_set(spec.x_slurm, "release"),
SLURM_VERSION_STRING);
data_set_string(data_key_set(spec.contact, "name"),
openapi_spec.info.contact.name);
data_set_string(data_key_set(spec.contact, "url"),
openapi_spec.info.contact.url);
data_set_string(data_key_set(spec.contact, "email"),
openapi_spec.info.contact.email);
data_set_string(data_key_set(spec.license, "name"),
openapi_spec.info.license.name);
data_set_string(data_key_set(spec.license, "url"),
openapi_spec.info.license.url);
for (int i = 0; i < ARRAY_SIZE(openapi_spec.tags); i++) {
data_t *tag = data_set_dict(data_list_append(spec.tags));
data_set_string(data_key_set(tag, "name"),
openapi_spec.tags[i].name);
data_set_string(data_key_set(tag, "description"),
openapi_spec.tags[i].desc);
}
for (int i = 0; i < ARRAY_SIZE(openapi_spec.servers); i++) {
data_t *server = data_set_dict(data_list_append(spec.servers));
data_set_string(data_key_set(server, "url"),
openapi_spec.servers[i].url);
}
/* Add default of no auth required */
data_set_dict(data_list_append(spec.security));
/* Add user and token auth */
data_set_list(data_key_set(
security1,
openapi_spec.components.security_schemes[SEC_SCHEME_USER_INDEX]
.key));
data_set_list(data_key_set(
security1,
openapi_spec.components.security_schemes[SEC_SCHEME_TOKEN_INDEX]
.key));
/* Add only token auth */
data_set_list(data_key_set(
security2,
openapi_spec.components.security_schemes[SEC_SCHEME_TOKEN_INDEX]
.key));
/* Add only bearer */
data_set_list(data_key_set(
security3, openapi_spec.components
.security_schemes[SEC_SCHEME_BEARER_INDEX]
.key));
for (int i = 0;
i < ARRAY_SIZE(openapi_spec.components.security_schemes); i++) {
const struct security_scheme_s *s =
&openapi_spec.components.security_schemes[i];
data_t *schema =
data_set_dict(data_key_set(spec.security_schemas,
s->key));
data_set_string(data_key_set(schema, "type"), s->type);
data_set_string(data_key_set(schema, "description"), s->desc);
if (s->name)
data_set_string(data_key_set(schema, "name"), s->name);
if (s->in)
data_set_string(data_key_set(schema, "in"), s->in);
if (s->scheme)
data_set_string(data_key_set(schema, "scheme"),
s->scheme);
if (s->bearer_format)
data_set_string(data_key_set(schema, "bearerFormat"),
s->bearer_format);
}
(void) list_for_each(paths, _foreach_count_path, &spec);
/* Add generated paths */
(void) list_for_each(paths, _foreach_add_path, &spec);
for (int i = 0; parsers[i]; i++)
if (spec.references[i])
data_parser_g_release_references(parsers[i],
&spec.references[i]);
xfree(spec.references);
/*
* We currently fatal instead of returning failure since openapi are
* compile time static and we should not be failing to serve the specs
* out
*/
return SLURM_SUCCESS;
}
static int _op_handler_openapi(openapi_ctxt_t *ctxt)
{
return generate_spec(ctxt->resp, get_mime_type_array());
}
static bool _on_error(void *arg, data_parser_type_t type, int error_code,
const char *source, const char *why, ...)
{
va_list ap;
char *str;
openapi_ctxt_t *ctxt = arg;
va_start(ap, why);
str = vxstrfmt(why, ap);
va_end(ap);
openapi_resp_error(ctxt, error_code, source, "%s", str);
xfree(str);
return false;
}
static void _on_warn(void *arg, data_parser_type_t type, const char *source,
const char *why, ...)
{
va_list ap;
char *str;
openapi_ctxt_t *ctxt = arg;
va_start(ap, why);
str = vxstrfmt(why, ap);
va_end(ap);
openapi_resp_warn(ctxt, source, "%s", str);
xfree(str);
}
extern int openapi_resp_error(openapi_ctxt_t *ctxt, int error_code,
const char *source, const char *why, ...)
{
openapi_resp_error_t *e;
xassert(ctxt->errors);
if (!ctxt->errors)
return error_code;
e = xmalloc(sizeof(*e));
if (why) {
va_list ap;
char *str;
va_start(ap, why);
str = vxstrfmt(why, ap);
va_end(ap);
error("%s: [%s] parser=%s rc[%d]=%s -> %s",
(source ? source : __func__), ctxt->id,
data_parser_get_plugin(ctxt->parser), error_code,
slurm_strerror(error_code), str);
e->description = str;
}
if (error_code) {
e->num = error_code;
if (!ctxt->rc)
ctxt->rc = error_code;
}
if (source)
e->source = xstrdup(source);
list_append(ctxt->errors, e);
return error_code;
}
extern void openapi_resp_warn(openapi_ctxt_t *ctxt, const char *source,
const char *why, ...)
{
openapi_resp_warning_t *w;
xassert(ctxt->warnings);
if (!ctxt->warnings)
return;
w = xmalloc(sizeof(*w));
if (why) {
va_list ap;
char *str;
va_start(ap, why);
str = vxstrfmt(why, ap);
va_end(ap);
debug("%s: [%s] parser=%s WARNING: %s",
(source ? source : __func__), ctxt->id,
data_parser_get_plugin(ctxt->parser), str);
w->description = str;
}
if (source)
w->source = xstrdup(source);
list_append(ctxt->warnings, w);
}
static void _populate_openapi_results(openapi_ctxt_t *ctxt,
openapi_resp_meta_t *query_meta)
{
data_t *errors, *warnings, *meta;
int rc;
/* need to populate meta, errors and warnings */
errors = data_key_set(ctxt->resp,
XSTRINGIFY(OPENAPI_RESP_STRUCT_ERRORS_FIELD_NAME));
warnings = data_key_set(ctxt->resp,
XSTRINGIFY(OPENAPI_RESP_STRUCT_WARNINGS_FIELD_NAME));
meta = data_key_set(ctxt->resp,
XSTRINGIFY(OPENAPI_RESP_STRUCT_META_FIELD_NAME));
if (data_get_type(meta) == DATA_TYPE_NULL)
DATA_DUMP(ctxt->parser, OPENAPI_META_PTR, query_meta, meta);
if (data_get_type(errors) == DATA_TYPE_LIST) {
if (!data_get_list_length(errors))
data_set_null(errors);
else
xassert(list_is_empty(ctxt->errors));
}
if ((data_get_type(errors) == DATA_TYPE_NULL) &&
((rc = DATA_DUMP(ctxt->parser, OPENAPI_ERRORS, ctxt->errors,
errors)))) {
/* data_parser doesn't support OPENAPI_ERRORS parser */
data_t *e =
data_set_dict(data_list_append(data_set_list(errors)));
data_set_string(data_key_set(e, "description"),
"Requested data_parser plugin does not support OpenAPI plugin");
data_set_int(data_key_set(e, "error_number"),
ESLURM_NOT_SUPPORTED);
data_set_string(data_key_set(e, "error"),
slurm_strerror(ESLURM_NOT_SUPPORTED));
}
if (data_get_type(warnings) == DATA_TYPE_LIST) {
if (!data_get_list_length(warnings))
data_set_null(warnings);
else
xassert(list_is_empty(ctxt->warnings));
}
if (data_get_type(warnings) == DATA_TYPE_NULL)
DATA_DUMP(ctxt->parser, OPENAPI_WARNINGS, ctxt->warnings,
warnings);
}
extern int wrap_openapi_ctxt_callback(const char *context_id,
http_request_method_t method,
data_t *parameters, data_t *query,
int tag, data_t *resp, void *auth,
data_parser_t *parser,
const openapi_path_binding_t *op_path,
const openapi_resp_meta_t *plugin_meta)
{
int rc = SLURM_SUCCESS;
openapi_ctxt_t ctxt = {
.id = context_id,
.method = method,
.parameters = parameters,
.query = query,
.resp = resp,
.tag = tag,
};
openapi_resp_meta_t query_meta = {{0}};
openapi_ctxt_handler_t callback = op_path->callback;
if (plugin_meta)
query_meta = *plugin_meta;
query_meta.plugin.data_parser = (char *) data_parser_get_plugin(parser);
query_meta.plugin.accounting_storage =
(char *) slurm_conf.accounting_storage_type;
query_meta.client.source = (char *) context_id;
query_meta.slurm.cluster = slurm_conf.cluster_name;
ctxt.parent_path = data_set_list(data_new());
ctxt.errors = list_create(free_openapi_resp_error);
ctxt.warnings = list_create(free_openapi_resp_warning);
ctxt.parser = data_parser_g_new(_on_error, _on_error, _on_error, &ctxt,
_on_warn, _on_warn, _on_warn, &ctxt,
data_parser_get_plugin(parser), NULL,
true);
debug("%s: [%s] %s using %s",
__func__, context_id, get_http_method_string(method),
data_parser_get_plugin(ctxt.parser));
if (op_path->flags & OP_BIND_NO_SLURMDBD) {
; /* Do not attempt to open a connection to slurmdbd */
} else if (slurm_conf.accounting_storage_type &&
!(ctxt.db_conn = openapi_get_db_conn(auth))) {
char *auth_error_msg = "";
if (errno == SLURM_PROTOCOL_AUTHENTICATION_ERROR)
auth_error_msg = ", authentication error";
if (op_path->flags & OP_BIND_REQUIRE_SLURMDBD)
openapi_resp_error(&ctxt, (rc = ESLURM_DB_CONNECTION),
XSTRINGIFY(openapi_get_db_conn),
"Failed to open slurmdbd connection%s",
auth_error_msg);
else
openapi_resp_warn(&ctxt,
XSTRINGIFY(openapi_get_db_conn),
"Failed to open connection to slurmdbd%s. Response fields may not be fully populated or empty.",
auth_error_msg);
} else {
rc = data_parser_g_assign(ctxt.parser,
DATA_PARSER_ATTR_DBCONN_PTR,
ctxt.db_conn);
}
if (!rc)
rc = callback(&ctxt);
if (data_get_type(ctxt.resp) == DATA_TYPE_NULL)
data_set_dict(ctxt.resp);
if (op_path->flags & OP_BIND_OPENAPI_RESP_FMT)
_populate_openapi_results(&ctxt, &query_meta);
if (!rc)
rc = ctxt.rc;
FREE_NULL_LIST(ctxt.errors);
FREE_NULL_LIST(ctxt.warnings);
FREE_NULL_DATA_PARSER(ctxt.parser);
FREE_NULL_DATA(ctxt.parent_path);
return rc;
}
extern bool is_spec_generation_only(bool set)
{
static bool is_spec_only = false;
if (set)
is_spec_only = true;
return is_spec_only;
}