blob: 7a98911826f225366eb83ef4934ffaca15ebe6c2 [file] [log] [blame]
/*****************************************************************************\
* as_mysql_resv.c - functions dealing with reservations.
*****************************************************************************
* Copyright (C) 2004-2007 The Regents of the University of California.
* Copyright (C) 2008-2010 Lawrence Livermore National Security.
* Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
* Written by Danny Auble <da@llnl.gov>
*
* 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 "as_mysql_resv.h"
#include "as_mysql_jobacct_process.h"
static int _setup_resv_limits(slurmdb_reservation_rec_t *resv,
char **cols, char **vals,
char **extra)
{
/* strip off the action item from the flags */
if (resv->assocs) {
int start = 0;
int len = strlen(resv->assocs)-1;
if (strchr(resv->assocs, '-')) {
int i = 0, i2 = 0;
char *assocs = xmalloc(len);
/* We will remove the negative's here. This
is here so if we only have negatives in the
reservation we don't want to keep track of
every other id so don't keep track of any
since everyone except a few can use it.
These id's are only used to divide up idle
time so it isn't that important.
*/
while (i < len) {
if (resv->assocs[i] == ',' &&
resv->assocs[i+1] == '-') {
i+=2;
while (i < len) {
i++;
if (resv->assocs[i] == ',')
break;
}
continue;
}
assocs[i2++] = resv->assocs[i++];
}
xfree(resv->assocs);
len = i2-1;
resv->assocs = assocs;
assocs = NULL;
}
/* strip off extra ,'s */
if (resv->assocs[0] == ',')
start = 1;
if (resv->assocs[len] == ',')
resv->assocs[len] = '\0';
xstrcat(*cols, ", assoclist");
xstrfmtcat(*vals, ", '%s'", resv->assocs+start);
xstrfmtcat(*extra, ", assoclist='%s'", resv->assocs+start);
}
if (resv->flags != NO_VAL64) {
xstrcat(*cols, ", flags");
xstrfmtcat(*vals, ", %"PRIu64, resv->flags);
xstrfmtcat(*extra, ", flags=%"PRIu64, resv->flags);
}
if (resv->name) {
xstrcat(*cols, ", resv_name");
xstrfmtcat(*vals, ", '%s'", resv->name);
xstrfmtcat(*extra, ", resv_name='%s'", resv->name);
}
if (resv->nodes) {
xstrcat(*cols, ", nodelist");
xstrfmtcat(*vals, ", '%s'", resv->nodes);
xstrfmtcat(*extra, ", nodelist='%s'", resv->nodes);
}
if (resv->node_inx) {
xstrcat(*cols, ", node_inx");
xstrfmtcat(*vals, ", '%s'", resv->node_inx);
xstrfmtcat(*extra, ", node_inx='%s'", resv->node_inx);
}
if (resv->time_end) {
xstrcat(*cols, ", time_end");
xstrfmtcat(*vals, ", %ld", resv->time_end);
xstrfmtcat(*extra, ", time_end=%ld", resv->time_end);
}
if (resv->time_force) {
xstrcat(*cols, ", time_force");
xstrfmtcat(*vals, ", %ld", resv->time_force);
xstrfmtcat(*extra, ", time_force=%ld", resv->time_force);
}
if (resv->time_start) {
xstrcat(*cols, ", time_start");
xstrfmtcat(*vals, ", %ld", resv->time_start);
xstrfmtcat(*extra, ", time_start=%ld", resv->time_start);
}
if (resv->tres_str) {
xstrcat(*cols, ", tres");
xstrfmtcat(*vals, ", '%s'", resv->tres_str);
xstrfmtcat(*extra, ", tres='%s'", resv->tres_str);
}
if (resv->comment) {
xstrcat(*cols, ", comment");
xstrfmtcat(*vals, ", '%s'", resv->comment);
xstrfmtcat(*extra, ", comment='%s'", resv->comment);
}
return SLURM_SUCCESS;
}
static int _setup_resv_cond_limits(slurmdb_reservation_cond_t *resv_cond,
char **extra)
{
int set = 0;
list_itr_t *itr = NULL;
char *object = NULL;
char *prefix = "t1";
time_t now = time(NULL);
if (!resv_cond)
return 0;
if (resv_cond->id_list && list_count(resv_cond->id_list)) {
set = 0;
if (*extra)
xstrcat(*extra, " && (");
else
xstrcat(*extra, " where (");
itr = list_iterator_create(resv_cond->id_list);
while ((object = list_next(itr))) {
if (set)
xstrcat(*extra, " || ");
xstrfmtcat(*extra, "%s.id_resv=%s", prefix, object);
set = 1;
}
list_iterator_destroy(itr);
xstrcat(*extra, ")");
}
if (resv_cond->name_list && list_count(resv_cond->name_list)) {
set = 0;
if (*extra)
xstrcat(*extra, " && (");
else
xstrcat(*extra, " where (");
itr = list_iterator_create(resv_cond->name_list);
while ((object = list_next(itr))) {
if (set)
xstrcat(*extra, " || ");
xstrfmtcat(*extra, "%s.resv_name='%s'",
prefix, object);
set = 1;
}
list_iterator_destroy(itr);
xstrcat(*extra, ")");
}
if (resv_cond->time_start) {
if (!resv_cond->time_end)
resv_cond->time_end = now;
if (*extra)
xstrcat(*extra, " && (");
else
xstrcat(*extra, " where (");
xstrfmtcat(*extra,
"(t1.time_start < %ld "
"&& (t1.time_end >= %ld || t1.time_end = 0)))",
resv_cond->time_end, resv_cond->time_start);
} else if (resv_cond->time_end) {
if (*extra)
xstrcat(*extra, " && (");
else
xstrcat(*extra, " where (");
xstrfmtcat(*extra,
"(t1.time_start < %ld))", resv_cond->time_end);
}
return set;
}
static int _add_usage_to_resv(void *object, void *arg)
{
slurmdb_job_rec_t *job = (slurmdb_job_rec_t *)object;
slurmdb_reservation_rec_t *resv = (slurmdb_reservation_rec_t *)arg;
int start = job->start;
int end = job->end;
int elapsed = 0;
/*
* Sanity check we are dealing with the reservation we requested.
*/
if (resv->id != job->resvid) {
error("We got a job %u and it doesn't match the reservation we requested. We requested %d but got %d. This should never happen.",
job->jobid, resv->id, job->resvid);
return SLURM_SUCCESS;
}
if (start < resv->time_start)
start = resv->time_start;
if (!end || end > resv->time_end)
end = resv->time_end;
if ((elapsed = (end - start)) < 1)
return SLURM_SUCCESS;
slurmdb_transfer_tres_time(
&resv->tres_list, job->tres_alloc_str,
elapsed);
return SLURM_SUCCESS;
}
static void _get_usage_for_resv(mysql_conn_t *mysql_conn, uid_t uid,
slurmdb_reservation_rec_t *resv,
char *resv_id)
{
list_t *job_list;
slurmdb_job_cond_t job_cond;
memset(&job_cond, 0, sizeof(job_cond));
job_cond.db_flags = SLURMDB_JOB_FLAG_NOTSET;
job_cond.usage_start = resv->time_start;
job_cond.usage_end = resv->time_end;
job_cond.cluster_list = list_create(NULL);
list_append(job_cond.cluster_list, resv->cluster);
job_cond.resvid_list = list_create(NULL);
list_append(job_cond.resvid_list, resv_id);
job_list = as_mysql_jobacct_process_get_jobs(
mysql_conn, uid, &job_cond);
if (job_list && list_count(job_list))
list_for_each(job_list, _add_usage_to_resv, resv);
FREE_NULL_LIST(job_cond.cluster_list);
FREE_NULL_LIST(job_cond.resvid_list);
FREE_NULL_LIST(job_list);
}
extern int as_mysql_add_resv(mysql_conn_t *mysql_conn,
slurmdb_reservation_rec_t *resv)
{
int rc = SLURM_SUCCESS;
char *cols = NULL, *vals = NULL, *extra = NULL,
*query = NULL;
if (!resv) {
error("No reservation was given to add.");
return SLURM_ERROR;
}
if (!resv->id) {
error("We need an id to add a reservation.");
return SLURM_ERROR;
}
if (!resv->time_start) {
error("We need a start time to add a reservation.");
return SLURM_ERROR;
}
if (!resv->cluster || !resv->cluster[0]) {
error("We need a cluster name to add a reservation.");
return SLURM_ERROR;
}
_setup_resv_limits(resv, &cols, &vals, &extra);
xstrfmtcat(query,
"insert into \"%s_%s\" (id_resv%s) values (%u%s) "
"on duplicate key update deleted=0%s;",
resv->cluster, resv_table, cols, resv->id, vals, extra);
DB_DEBUG(DB_RESV, mysql_conn->conn, "query\n%s", query);
rc = mysql_db_query(mysql_conn, query);
xfree(query);
xfree(cols);
xfree(vals);
xfree(extra);
return rc;
}
extern int as_mysql_modify_resv(mysql_conn_t *mysql_conn,
slurmdb_reservation_rec_t *resv)
{
MYSQL_RES *result = NULL;
MYSQL_ROW row, row2;
int rc = SLURM_SUCCESS;
char *cols = NULL, *vals = NULL, *extra = NULL,
*query = NULL;
time_t start = 0, now = time(NULL);
int i;
int set = 0;
char *resv_req_inx[] = {
"assoclist",
"deleted",
"time_start",
"time_end",
"resv_name",
"nodelist",
"node_inx",
"flags",
"tres",
"comment",
};
enum {
RESV_ASSOCS,
RESV_DELETED,
RESV_START,
RESV_END,
RESV_NAME,
RESV_NODES,
RESV_NODE_INX,
RESV_FLAGS,
RESV_TRES,
RESV_COMMENT,
RESV_COUNT
};
if (!resv) {
error("No reservation was given to edit");
return SLURM_ERROR;
}
if (!resv->id) {
error("We need an id to edit a reservation.");
return SLURM_ERROR;
}
if (!resv->time_start) {
error("We need a start time to edit a reservation.");
return SLURM_ERROR;
}
if (!resv->cluster || !resv->cluster[0]) {
error("We need a cluster name to edit a reservation.");
return SLURM_ERROR;
}
if (!resv->time_start_prev) {
error("We need a time to check for last "
"start of reservation.");
return SLURM_ERROR;
}
xstrfmtcat(cols, "%s", resv_req_inx[0]);
for (i=1; i<RESV_COUNT; i++) {
xstrfmtcat(cols, ", %s", resv_req_inx[i]);
}
/* Get the last record of this reservation */
query = xstrdup_printf("select %s from \"%s_%s\" where id_resv=%u "
"and time_start >= %ld "
"order by time_start desc "
"FOR UPDATE;",
cols, resv->cluster, resv_table, resv->id,
MIN(resv->time_start, resv->time_start_prev));
debug4("%d(%s:%d) query\n%s",
mysql_conn->conn, THIS_FILE, __LINE__, query);
if (!(result = mysql_db_query_ret(
mysql_conn, query, 0))) {
rc = SLURM_ERROR;
goto end_it;
}
/* Get the first one that isn't deleted */
do {
if (!(row = mysql_fetch_row(result))) {
mysql_free_result(result);
error("%s: There is no reservation by id %u, time_start %ld, and cluster '%s', creating it",
__func__, resv->id, resv->time_start_prev,
resv->cluster);
/*
* Don't set the time_start to time_start_prev as we
* have no idea what the reservation looked like at that
* time. Doing so will also mess up future updates.
*/
/* resv->time_start = resv->time_start_prev; */
rc = as_mysql_add_resv(mysql_conn, resv);
goto end_it;
}
} while (slurm_atoul(row[RESV_DELETED]));
start = slurm_atoul(row[RESV_START]);
xfree(query);
xfree(cols);
/*
* Check to see if the start is after the time we are looking for and
* before now to make sure we are the latest update. If we aren't throw
* this one away. This should rarely if ever happen.
*/
if ((start > resv->time_start) && (start <= now)) {
error("There is newer record for reservation with id %u, drop modification request:",
resv->id);
error("assocs:'%s', cluster:'%s', flags:%"PRIu64", id:%u, name:'%s', nodes:'%s', nodes_inx:'%s', time_end:%ld, time_start:%ld, time_start_prev:%ld, tres_str:'%s', unused_wall:%f",
resv->assocs, resv->cluster, resv->flags, resv->id,
resv->name, resv->nodes, resv->node_inx, resv->time_end,
resv->time_start, resv->time_start_prev, resv->tres_str,
resv->unused_wall);
mysql_free_result(result);
rc = SLURM_SUCCESS;
goto end_it;
}
/*
* Here we are making sure we don't get a potential duplicate entry in
* the database. If we find one then we will delete it. This should
* never happen in practice but is more a sanity check.
*/
while ((row2 = mysql_fetch_row(result))) {
if (resv->time_start != slurm_atoul(row2[RESV_START]))
continue;
query = xstrdup_printf("delete from \"%s_%s\" where "
"id_resv=%u and time_start=%ld;",
resv->cluster, resv_table,
resv->id, resv->time_start);
info("When trying to update a reservation an already existing row that would create a duplicate entry was found. Replacing this old row with the current request. This should rarely if ever happen.");
rc = mysql_db_query(mysql_conn, query);
if (rc != SLURM_SUCCESS) {
error("problem with update query");
mysql_free_result(result);
goto end_it;
}
xfree(query);
}
/* check differences here */
if (!resv->name
&& row[RESV_NAME] && row[RESV_NAME][0])
// if this changes we just update the
// record, no need to create a new one since
// this doesn't really effect the
// reservation accounting wise
resv->name = slurm_add_slash_to_quotes(row[RESV_NAME]);
if (xstrcmp(resv->assocs, row[RESV_ASSOCS]) ||
(resv->flags != slurm_atoul(row[RESV_FLAGS])) ||
xstrcmp(resv->nodes, row[RESV_NODE_INX]) ||
xstrcmp(resv->tres_str, row[RESV_TRES]) ||
xstrcmp(resv->comment, row[RESV_COMMENT]))
set = 1;
if (!resv->time_end)
resv->time_end = slurm_atoul(row[RESV_END]);
mysql_free_result(result);
_setup_resv_limits(resv, &cols, &vals, &extra);
/* use start below instead of resv->time_start_prev
* just in case we have a different one from being out
* of sync
*/
if ((start > now) || !set) {
/* we haven't started the reservation yet, or
we are changing the associations or end
time which we can just update it */
query = xstrdup_printf("update \"%s_%s\" set deleted=0%s "
"where deleted=0 and id_resv=%u "
"and time_start=%ld;",
resv->cluster, resv_table,
extra, resv->id, start);
} else {
if (start != resv->time_start)
/* time_start is already done above and we
* changed something that is in need on a new
* entry. */
query = xstrdup_printf(
"update \"%s_%s\" set time_end=%ld "
"where deleted=0 && id_resv=%u "
"and time_start=%ld;",
resv->cluster, resv_table,
resv->time_start,
resv->id, start);
xstrfmtcat(query,
"insert into \"%s_%s\" (id_resv%s) "
"values (%u%s) "
"on duplicate key update deleted=0%s;",
resv->cluster, resv_table, cols, resv->id,
vals, extra);
}
DB_DEBUG(DB_RESV, mysql_conn->conn, "query\n%s", query);
rc = mysql_db_query(mysql_conn, query);
end_it:
xfree(query);
xfree(cols);
xfree(vals);
xfree(extra);
return rc;
}
extern int as_mysql_remove_resv(mysql_conn_t *mysql_conn,
slurmdb_reservation_rec_t *resv)
{
int rc = SLURM_SUCCESS;
char *query = NULL;
if (!resv) {
error("No reservation was given to remove");
return SLURM_ERROR;
}
if (!resv->id) {
error("An id is needed to remove a reservation.");
return SLURM_ERROR;
}
if (!resv->time_start) {
error("A start time is needed to remove a reservation.");
return SLURM_ERROR;
}
if (!resv->cluster || !resv->cluster[0]) {
error("A cluster name is needed to remove a reservation.");
return SLURM_ERROR;
}
/* first delete the resv that hasn't happened yet. */
query = xstrdup_printf("delete from \"%s_%s\" where time_start > %ld "
"and id_resv=%u and time_start=%ld;",
resv->cluster, resv_table, resv->time_start_prev,
resv->id,
resv->time_start);
/* then update the remaining ones with a deleted flag and end
* time of the time_start_prev which is set to when the
* command was issued */
xstrfmtcat(query,
"update \"%s_%s\" set time_end=%ld, "
"deleted=1 where deleted=0 and "
"id_resv=%u and time_start=%ld;",
resv->cluster, resv_table, resv->time_start_prev,
resv->id, resv->time_start);
DB_DEBUG(DB_RESV, mysql_conn->conn, "query\n%s", query);
rc = mysql_db_query(mysql_conn, query);
xfree(query);
return rc;
}
extern list_t *as_mysql_get_resvs(mysql_conn_t *mysql_conn, uid_t uid,
slurmdb_reservation_cond_t *resv_cond)
{
//DEF_TIMERS;
char *query = NULL;
char *extra = NULL;
char *tmp = NULL;
list_t *resv_list = NULL;
int i=0, is_admin=1;
MYSQL_RES *result = NULL;
MYSQL_ROW row;
void *curr_cluster = NULL;
list_t *local_cluster_list = NULL;
list_t *use_cluster_list = NULL;
list_itr_t *itr = NULL;
char *cluster_name = NULL;
/* needed if we don't have an resv_cond */
uint16_t with_usage = 0;
bool locked = false;
/* if this changes you will need to edit the corresponding enum */
char *resv_req_inx[] = {
"id_resv",
"assoclist",
"flags",
"nodelist",
"node_inx",
"resv_name",
"time_start",
"time_end",
"time_force",
"tres",
"unused_wall",
"comment",
};
enum {
RESV_REQ_ID,
RESV_REQ_ASSOCS,
RESV_REQ_FLAGS,
RESV_REQ_NODES,
RESV_REQ_NODE_INX,
RESV_REQ_NAME,
RESV_REQ_START,
RESV_REQ_END,
RESV_REQ_FORCE,
RESV_REQ_TRES,
RESV_REQ_UNUSED,
RESV_REQ_COMMENT,
RESV_REQ_COUNT
};
if (!resv_cond) {
xstrcat(extra, " where deleted=0");
goto empty;
}
if (check_connection(mysql_conn) != SLURM_SUCCESS)
return NULL;
if (slurm_conf.private_data & PRIVATE_DATA_RESERVATIONS) {
if (!(is_admin = is_user_min_admin_level(
mysql_conn, uid, SLURMDB_ADMIN_OPERATOR))) {
error("Only admins can look at reservations");
errno = ESLURM_ACCESS_DENIED;
return NULL;
}
}
with_usage = resv_cond->with_usage;
if (resv_cond->nodes) {
slurmdb_job_cond_t job_cond;
memset(&job_cond, 0, sizeof(slurmdb_job_cond_t));
job_cond.db_flags = SLURMDB_JOB_FLAG_NOTSET;
job_cond.usage_start = resv_cond->time_start;
job_cond.usage_end = resv_cond->time_end;
job_cond.used_nodes = resv_cond->nodes;
if (!resv_cond->cluster_list)
resv_cond->cluster_list = list_create(xfree_ptr);
/*
* If they didn't specify a cluster, give them the one they are
* calling from.
*/
if (!list_count(resv_cond->cluster_list))
list_append(resv_cond->cluster_list,
xstrdup(mysql_conn->cluster_name));
job_cond.cluster_list = resv_cond->cluster_list;
local_cluster_list = setup_cluster_list_with_inx(
mysql_conn, &job_cond, (void **)&curr_cluster);
}
(void) _setup_resv_cond_limits(resv_cond, &extra);
empty:
xfree(tmp);
xstrfmtcat(tmp, "t1.%s", resv_req_inx[i]);
for(i=1; i<RESV_REQ_COUNT; i++) {
xstrfmtcat(tmp, ", t1.%s", resv_req_inx[i]);
}
if (resv_cond && resv_cond->cluster_list &&
list_count(resv_cond->cluster_list)) {
use_cluster_list = resv_cond->cluster_list;
} else {
slurm_rwlock_rdlock(&as_mysql_cluster_list_lock);
use_cluster_list = as_mysql_cluster_list;
locked = true;
}
itr = list_iterator_create(use_cluster_list);
while ((cluster_name = list_next(itr))) {
if (query)
xstrcat(query, " union ");
//START_TIMER;
xstrfmtcat(query, "select distinct %s,'%s' as cluster "
"from \"%s_%s\" as t1%s",
tmp, cluster_name, cluster_name, resv_table,
extra ? extra : "");
}
list_iterator_destroy(itr);
if (locked)
slurm_rwlock_unlock(&as_mysql_cluster_list_lock);
if (query)
xstrcat(query, " order by cluster, time_start, resv_name;");
xfree(tmp);
xfree(extra);
DB_DEBUG(DB_RESV, mysql_conn->conn, "query\n%s", query);
if (!(result = mysql_db_query_ret(mysql_conn, query, 0))) {
xfree(query);
FREE_NULL_LIST(local_cluster_list);
return NULL;
}
xfree(query);
resv_list = list_create(slurmdb_destroy_reservation_rec);
while ((row = mysql_fetch_row(result))) {
slurmdb_reservation_rec_t *resv;
int start = slurm_atoul(row[RESV_REQ_START]);
if (!good_nodes_from_inx(local_cluster_list, &curr_cluster,
row[RESV_REQ_NODE_INX], start))
continue;
resv = xmalloc(sizeof(slurmdb_reservation_rec_t));
list_append(resv_list, resv);
resv->id = slurm_atoul(row[RESV_REQ_ID]);
resv->name = xstrdup(row[RESV_REQ_NAME]);
resv->node_inx = xstrdup(row[RESV_REQ_NODE_INX]);
resv->cluster = xstrdup(row[RESV_REQ_COUNT]);
resv->assocs = xstrdup(row[RESV_REQ_ASSOCS]);
resv->nodes = xstrdup(row[RESV_REQ_NODES]);
resv->time_start = start;
resv->time_end = slurm_atoul(row[RESV_REQ_END]);
resv->time_force = slurm_atoul(row[RESV_REQ_FORCE]);
resv->flags = slurm_atoull(row[RESV_REQ_FLAGS]);
resv->tres_str = xstrdup(row[RESV_REQ_TRES]);
resv->unused_wall = atof(row[RESV_REQ_UNUSED]);
resv->comment = xstrdup(row[RESV_REQ_COMMENT]);
if (with_usage)
_get_usage_for_resv(
mysql_conn, uid, resv, row[RESV_REQ_ID]);
}
FREE_NULL_LIST(local_cluster_list);
/* free result after we use the list with resv id's in it. */
mysql_free_result(result);
//END_TIMER2("get_resvs");
return resv_list;
}