blob: 0983608b58d6b8669aa5bfd26d6dca628ecd4055 [file] [edit]
/*****************************************************************************\
* http.c - Implementation for handling HTTP requests
*****************************************************************************
* Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
* 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 "slurm/slurm_errno.h"
#include "slurm/slurmdb.h"
#include "src/common/assoc_mgr.h"
#include "src/common/http.h"
#include "src/common/http_con.h"
#include "src/common/http_mime.h"
#include "src/common/http_request.h"
#include "src/common/http_router.h"
#include "src/common/openapi.h"
#include "src/common/pack.h"
#include "src/common/probes.h"
#include "src/common/read_config.h"
#include "src/common/slurm_protocol_defs.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "src/conmgr/conmgr.h"
#include "src/interfaces/data_parser.h"
#include "src/interfaces/http_auth.h"
#include "src/slurmdbd/http.h"
#include "src/slurmdbd/proc_req.h"
#include "src/slurmdbd/read_config.h"
#include "src/slurmdbd/slurmdbd.h"
static data_parser_t **parsers = NULL;
static int _reply_error(http_con_t *hcon, const char *name,
const http_con_request_t *request, int err,
bool close_header)
{
char *body = NULL, *at = NULL;
int rc = EINVAL;
xstrfmtcatat(body, &at, "slurmdbd HTTP server request for '%s %s':\n",
get_http_method_string(request->method),
request->url.path);
if (err)
xstrfmtcatat(body, &at, "Failed: %s\n", slurm_strerror(err));
rc = http_con_send_response(hcon, http_status_from_error(err), NULL,
close_header,
&SHADOW_BUF_INITIALIZER(body, strlen(body)),
MIME_TYPE_TEXT);
xfree(body);
return rc;
}
static int _auth(http_con_t *hcon, const char *name,
const http_con_request_t *request, void *uid_ptr)
{
int rc = EINVAL;
if ((rc = http_auth_g_authenticate(HTTP_AUTH_PLUGIN_ANY, uid_ptr, hcon,
name, request))) {
(void) _reply_error(hcon, name, request, rc, true);
return rc;
}
return SLURM_SUCCESS;
}
static int _req_not_found(http_con_t *hcon, const char *name,
const http_con_request_t *request, void *arg,
void *path_arg)
{
return _reply_error(hcon, name, request, ESLURM_URL_INVALID_PATH,
false);
}
static int _req_root(http_con_t *hcon, const char *name,
const http_con_request_t *request, void *arg,
void *path_arg)
{
static const char body[] =
"slurmdbd index of endpoints:\n"
" '/{data_parser}/conf': dump slurmdbd configuration\n"
" ({data_parser} is the version)\n"
" '/{data_parser}/diag': dump slurmdbd RPC statistics\n"
" '/readyz': check slurmdbd is servicing requests\n"
" '/livez': check slurmdbd is running\n"
" '/healthz': check slurmdbd is running\n";
return http_con_send_response(hcon,
http_status_from_error(SLURM_SUCCESS),
NULL, false,
&SHADOW_BUF_INITIALIZER(body,
strlen(body)),
MIME_TYPE_TEXT);
}
static int _req_readyz(http_con_t *hcon, const char *name,
const http_con_request_t *request, void *arg,
void *path_arg)
{
static const char ready[] = "READY";
const buf_t ready_buf = SHADOW_BUF_INITIALIZER(ready, strlen(ready));
http_status_code_t status = HTTP_STATUS_CODE_SRVERR_INTERNAL;
const buf_t *reply = NULL;
buf_t *body = NULL;
int rc = EINVAL;
if (!xstrcasecmp(request->url.query, "verbose")) {
uid_t uid = SLURM_AUTH_NOBODY;
/* Authenticate request and close it on any failure */
if (_auth(hcon, name, request, &uid))
return SLURM_SUCCESS;
/* Only root and SlurmUser are allowed to view verbose */
if (!validate_slurm_user(uid))
return _reply_error(hcon, name, request, EPERM, true);
body = init_buf(INFINITE);
}
if (probe_run(body, NULL, body, __func__) >= PROBE_RC_READY) {
status = HTTP_STATUS_CODE_SUCCESS_OK;
if (!body || !get_buf_offset(body))
reply = &ready_buf;
}
rc = http_con_send_response(hcon, status, NULL, false,
(reply ? reply : body), MIME_TYPE_TEXT);
FREE_NULL_BUFFER(body);
return rc;
}
static int _req_livez(http_con_t *hcon, const char *name,
const http_con_request_t *request, void *arg,
void *path_arg)
{
http_status_code_t status = HTTP_STATUS_CODE_SRVERR_INTERNAL;
log_flag(NET, "%s: [%s] %s %s",
__func__, name, get_http_method_string(request->method),
request->url.path);
if (probe_run(false, NULL, NULL, __func__) >= PROBE_RC_ONLINE) {
static const char live[] = "LIVE";
return http_con_send_response(
hcon, HTTP_STATUS_CODE_SUCCESS_OK, NULL, false,
&SHADOW_BUF_INITIALIZER(live, strlen(live)),
MIME_TYPE_TEXT);
}
return http_con_send_response(hcon, status, NULL, false, NULL, NULL);
}
static int _req_healthz(http_con_t *hcon, const char *name,
const http_con_request_t *request, void *arg,
void *path_arg)
{
http_status_code_t status = HTTP_STATUS_CODE_SRVERR_INTERNAL;
log_flag(NET, "%s: [%s] %s %s",
__func__, name, get_http_method_string(request->method),
request->url.path);
if (probe_run(false, NULL, NULL, __func__) >= PROBE_RC_ONLINE) {
static const char healthy[] = "HEALTHY";
return http_con_send_response(
hcon, HTTP_STATUS_CODE_SUCCESS_OK, NULL, false,
&SHADOW_BUF_INITIALIZER(healthy, strlen(healthy)),
MIME_TYPE_TEXT);
}
return http_con_send_response(hcon, status, NULL, false, NULL, NULL);
}
static int _req_failed(http_request_event_t *event, http_con_t *hcon,
const char *name, const http_con_request_t *request,
int err)
{
return _reply_error(hcon, name, request, err, true);
}
static int _req_conf(http_request_event_t *event, http_con_t *hcon,
const char *name, const uid_t uid,
http_request_method_t method,
const http_con_request_t *request, void *arg)
{
int rc = EINVAL;
slurmdbd_conf_t conf_shallow_copy = { 0 };
openapi_resp_single_t resp = {
.response = &conf_shallow_copy,
};
/* User must be authenticated */
if (uid == SLURM_AUTH_NOBODY)
return _req_failed(event, hcon, name, request, EPERM);
/* slurmdbd_conf may be rebuilt by reconfig(), which holds the lock */
slurm_rwlock_rdlock(&reconfig_lock);
conf_shallow_copy = *slurmdbd_conf;
/* Never send the storage fields, matching DBD_GOT_CONFIG */
conf_shallow_copy.storage_loc = NULL;
conf_shallow_copy.storage_pass_script = NULL;
conf_shallow_copy.storage_user = NULL;
rc = http_request_reply(event, SLURM_SUCCESS, NULL, false, &resp,
sizeof(resp));
slurm_rwlock_unlock(&reconfig_lock);
return rc;
}
static int _req_diag(http_request_event_t *event, http_con_t *hcon,
const char *name, const uid_t uid,
http_request_method_t method,
const http_con_request_t *request, void *arg)
{
int rc = EINVAL;
openapi_resp_single_t resp = { 0 };
slurmdb_stats_rec_t *stats = NULL;
slurm_msg_auth_t auth = { .uid = uid };
/* User must be authenticated */
if (uid == SLURM_AUTH_NOBODY)
return _req_failed(event, hcon, name, request, EPERM);
slurm_rwlock_rdlock(&reconfig_lock);
if (validate_slurm_user(uid))
auth.level = AUTH_LEVEL_INTERNAL;
else
assoc_mgr_complete_msg_auth(&auth);
slurm_rwlock_unlock(&reconfig_lock);
if (auth.level < AUTH_LEVEL_ADMIN)
return _req_failed(event, hcon, name, request, EPERM);
if ((rc = proc_req_get_stats_copy(&stats)))
return _req_failed(event, hcon, name, request, rc);
resp.response = stats;
rc = http_request_reply(event, SLURM_SUCCESS, NULL, false, &resp,
sizeof(resp));
slurmdb_destroy_stats_rec(stats);
return rc;
}
extern void http_init(void)
{
int rc = EINVAL;
if ((rc = http_auth_g_init(slurmdbd_conf->http_auth_params, NULL,
NULL)))
fatal("http authentication plugins failed to load: %s",
slurm_strerror(rc));
if (!(parsers = data_parser_g_new_array(NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL,
false)))
fatal("Unable to initialize data_parser plugins");
http_router_init(_req_not_found);
http_router_bind(HTTP_REQUEST_GET, "/", _req_root, NULL, NULL);
http_router_bind(HTTP_REQUEST_GET, "/readyz", _req_readyz, NULL, NULL);
http_router_bind(HTTP_REQUEST_GET, "/livez", _req_livez, NULL, NULL);
http_router_bind(HTTP_REQUEST_GET, "/healthz", _req_healthz, NULL,
NULL);
http_request_bind(parsers, HTTP_REQUEST_GET,
"/" OPENAPI_DATA_PARSER_PARAM "/conf", _req_conf,
_req_failed, DATA_PARSER_OPENAPI_SLURMDBD_CONF_RESP,
NULL);
http_request_bind(parsers, HTTP_REQUEST_GET,
"/" OPENAPI_DATA_PARSER_PARAM "/diag", _req_diag,
_req_failed, DATA_PARSER_OPENAPI_SLURMDBD_STATS_RESP,
NULL);
}
extern void http_fini(void)
{
http_router_fini();
http_auth_g_fini();
FREE_NULL_DATA_PARSER_ARRAY(parsers, false);
}
extern int on_http_connection(conmgr_fd_t *con)
{
static const http_con_server_events_t events = {
.on_request = http_router_on_request,
};
int rc = EINVAL;
conmgr_fd_ref_t *ref = conmgr_fd_new_ref(con);
rc = http_con_assign_server(ref, NULL, &events, NULL);
CONMGR_CON_UNLINK(ref);
return rc;
}