/*****************************************************************************\
 *  as_mysql_cluster.c - functions dealing with clusters.
 *****************************************************************************
 *  Copyright (C) 2004-2007 The Regents of the University of California.
 *  Copyright (C) 2008-2011 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_tres.h"
#include "as_mysql_assoc.h"
#include "as_mysql_cluster.h"
#include "as_mysql_federation.h"
#include "as_mysql_usage.h"
#include "as_mysql_wckey.h"

#include "src/interfaces/select.h"
#include "src/common/sluid.h"
#include "src/common/slurm_time.h"

/* if this changes you will need to edit the corresponding enum */
static char *event_req_inx[] = {
	"cluster_nodes",
	"extra",
	"instance_id",
	"instance_type",
	"node_name",
	"state",
	"time_start",
	"time_end",
	"reason",
	"reason_uid",
	"tres",
};

enum {
	EVENT_REQ_CNODES,
	EVENT_REQ_EXTRA,
	EVENT_REQ_INSTANCE_ID,
	EVENT_REQ_INSTANCE_TYPE,
	EVENT_REQ_NODE,
	EVENT_REQ_STATE,
	EVENT_REQ_START,
	EVENT_REQ_END,
	EVENT_REQ_REASON,
	EVENT_REQ_REASON_UID,
	EVENT_REQ_TRES,
	EVENT_REQ_COUNT
};

extern int as_mysql_get_fed_cluster_id(mysql_conn_t *mysql_conn,
				       const char *cluster,
				       const char *federation,
				       int last_id, int *ret_id)
{
	/* find id for cluster in federation.
	 * don't do anything if cluster is already part of federation
	 * get list of clusters that are part of the federration.
	 * loop through each cluster and find the first id available.
	 * report error if all are full in 63 slots. */

	int        id     = 1;
	char      *query  = NULL;
	MYSQL_ROW  row;
	MYSQL_RES *result = NULL;

	xassert(cluster);
	xassert(federation);
	xassert(ret_id);

	/* See if cluster is already part of federation */
	xstrfmtcat(query,
		   "SELECT name, fed_id FROM %s "
		   "WHERE deleted=0 AND name='%s' AND federation='%s';",
		   cluster_table, cluster, federation);
	DB_DEBUG(FEDR, mysql_conn->conn, "query\n%s", query);
	if (!(result = mysql_db_query_ret(mysql_conn, query, 0))) {
		xfree(query);
		error("no result given for %s", query);
		return SLURM_ERROR;
	}
	xfree(query);
	while ((row = mysql_fetch_row(result))) {
		int tmp_id = slurm_atoul(row[1]);
		log_flag(FEDR, "cluster '%s' already part of federation '%s', using existing id %d",
			 cluster, federation, tmp_id);
		mysql_free_result(result);
		*ret_id = tmp_id;
		return SLURM_SUCCESS;
	}
	mysql_free_result(result);

	/* Get all other clusters in the federation and find an open id. */
	xstrfmtcat(query,
		   "SELECT name, federation, fed_id FROM %s "
		   "WHERE name!='%s' AND federation='%s' "
		   "AND fed_id > %d AND deleted=0 ORDER BY fed_id;",
		   cluster_table, cluster, federation, last_id);
	DB_DEBUG(FEDR, mysql_conn->conn, "query\n%s", query);
	if (!(result = mysql_db_query_ret(mysql_conn, query, 0))) {
		xfree(query);
		error("no result given for %s", query);
		return SLURM_ERROR;
	}
	xfree(query);

	if (last_id >= id)
		id = last_id + 1;
	while ((row = mysql_fetch_row(result))) {
		if (id != slurm_atoul(row[2]))
			break;
		id++;
	}
	mysql_free_result(result);

	if (id > MAX_FED_CLUSTERS) {
		error("Too many clusters in this federation.");
		errno = ESLURM_FED_CLUSTER_MAX_CNT;
		return  ESLURM_FED_CLUSTER_MAX_CNT;
	}

	*ret_id = id;
	return SLURM_SUCCESS;
}

static int _setup_cluster_cond_limits(slurmdb_cluster_cond_t *cluster_cond,
				      char **extra)
{
	int set = 0;
	list_itr_t *itr = NULL;
	char *object = NULL;

	if (!cluster_cond)
		return 0;

	if (cluster_cond->with_deleted)
		xstrcat(*extra, " where (deleted=0 || deleted=1)");
	else
		xstrcat(*extra, " where deleted=0");

	if (cluster_cond->cluster_list
	    && list_count(cluster_cond->cluster_list)) {
		set = 0;
		xstrcat(*extra, " && (");
		itr = list_iterator_create(cluster_cond->cluster_list);
		while ((object = list_next(itr))) {
			if (set)
				xstrcat(*extra, " || ");
			xstrfmtcat(*extra, "name='%s'", object);
			set = 1;
		}
		list_iterator_destroy(itr);
		xstrcat(*extra, ")");
	}

	if (cluster_cond->federation_list
	    && list_count(cluster_cond->federation_list)) {
		set = 0;
		xstrcat(*extra, " && (");
		itr = list_iterator_create(cluster_cond->federation_list);
		while ((object = list_next(itr))) {
			if (set)
				xstrcat(*extra, " || ");
			xstrfmtcat(*extra, "federation='%s'", object);
			set = 1;
		}
		list_iterator_destroy(itr);
		xstrcat(*extra, ")");
	}

	if (cluster_cond->rpc_version_list
	    && list_count(cluster_cond->rpc_version_list)) {
		set = 0;
		xstrcat(*extra, " && (");
		itr = list_iterator_create(cluster_cond->rpc_version_list);
		while ((object = list_next(itr))) {
			if (set)
				xstrcat(*extra, " || ");
			xstrfmtcat(*extra, "rpc_version='%s'", object);
			set = 1;
		}
		list_iterator_destroy(itr);
		xstrcat(*extra, ")");
	}

	if (cluster_cond->classification) {
		xstrfmtcat(*extra, " && (classification & %u)",
			   cluster_cond->classification);
	}

	if (cluster_cond->flags != NO_VAL) {
		xstrfmtcat(*extra, " && (flags & %u)",
			   cluster_cond->flags);
	}

	return set;
}

extern uint16_t as_mysql_cluster_get_unique_id(mysql_conn_t *mysql_conn,
					       char *cluster_name,
					       uint16_t requested_id)
{
	MYSQL_RES *result = NULL;
	MYSQL_ROW row;
	uint16_t id = 0;
	char *query = NULL;
	int num_rows;

	/* First check to see if we are already here with an id. */
	query = xstrdup_printf("select id from %s where name='%s';",
			       cluster_table, cluster_name);
	result = mysql_db_query_ret(mysql_conn, query, 0);
	xfree(query);

	if (!result)
		fatal("No cluster table?");

	if ((row = mysql_fetch_row(result)) && row[0])
		id = slurm_atoul(row[0]);

	mysql_free_result(result);

	while (!id) {
		/* try the requested id first */
		if (requested_id > 0) {
			id = requested_id;
		} else {
			requested_id = 0;
			id = generate_cluster_id();
		}

		/* Test to make sure we aren't a duplicate */
		query = xstrdup_printf("select id from %s where id=%u;",
				       cluster_table, id);
		result = mysql_db_query_ret(mysql_conn, query, 0);
		xfree(query);
		num_rows = mysql_num_rows(result);
		mysql_free_result(result);

		/* Unique */
		if (!num_rows)
			break;
		id = 0;
		requested_id = 0;
	}

	return id;
}

extern int as_mysql_add_clusters(mysql_conn_t *mysql_conn, uint32_t uid,
				 list_t *cluster_list)
{
	list_itr_t *itr = NULL;
	int rc = SLURM_SUCCESS;
	slurmdb_cluster_rec_t *object = NULL;
	char *extra = NULL,
		*query = NULL, *tmp_extra = NULL;
	time_t now = time(NULL);
	char *user_name = NULL;
	int affect_rows = 0;
	int added = 0;
	bool has_feds = false;
	list_t *assoc_list = NULL;
	slurmdb_assoc_rec_t *assoc = NULL;
	bool external_cluster = false;

	if (check_connection(mysql_conn) != SLURM_SUCCESS)
		return ESLURM_DB_CONNECTION;

	if (!is_user_min_admin_level(mysql_conn, uid, SLURMDB_ADMIN_SUPER_USER))
		return ESLURM_ACCESS_DENIED;

	if (!cluster_list || !list_count(cluster_list)) {
		error("%s: Trying to add empty cluster list", __func__);
		return ESLURM_EMPTY_LIST;
	}

	assoc_list = list_create(slurmdb_destroy_assoc_rec);

	user_name = uid_to_string((uid_t) uid);
	/* Since adding tables make it so you can't roll back, if
	   there is an error there is no way to easily remove entries
	   in the database, so we will create the tables first and
	   then after that works out then add them to the mix.
	*/
	itr = list_iterator_create(cluster_list);
	while ((object = list_next(itr))) {
		if (!object->name || !object->name[0]) {
			error("We need a cluster name to add.");
			rc = SLURM_ERROR;
			list_remove(itr);
			continue;
		}
		if ((object->flags != NO_VAL) &&
		    (object->flags & CLUSTER_FLAG_EXT))
			external_cluster = true;
		if ((rc = create_cluster_tables(mysql_conn,
						object->name))
		    != SLURM_SUCCESS) {
			added = 0;
			if (mysql_errno(mysql_conn->db_conn)
			    == ER_WRONG_TABLE_NAME)
				rc = ESLURM_BAD_NAME;
			goto end_it;
		}
	}

	/* Now that all the tables were created successfully lets go
	   ahead and add it to the system.
	*/
	list_iterator_reset(itr);
	while ((object = list_next(itr))) {
		int fed_id = 0;
		uint16_t fed_state = CLUSTER_FED_STATE_NA;
		char *features = NULL;
		uint16_t id;

		if (object->fed.name) {
			has_feds = 1;
			rc = as_mysql_get_fed_cluster_id(mysql_conn,
							 object->name,
							 object->fed.name, -1,
							 &fed_id);
			if (rc) {
				error("failed to get cluster id for "
				      "federation");
				added=0;
				goto end_it;
			}

			if (object->fed.state != NO_VAL)
				fed_state = object->fed.state;
			else
				fed_state = CLUSTER_FED_STATE_ACTIVE;
		}

		if (object->fed.feature_list) {
			features =
				slurm_char_list_to_xstr(
					object->fed.feature_list);
			has_feds = 1;
		}

		id = as_mysql_cluster_get_unique_id(mysql_conn, object->name,
						    object->id);

		xstrfmtcat(query,
			   "insert into %s (creation_time, mod_time, "
			   "name, id, classification, federation, fed_id, "
			   "fed_state, features) "
			   "values (%ld, %ld, '%s', %d, %u, '%s', %d, %u, '%s') "
			   "on duplicate key update deleted=0, mod_time=%ld, "
			   "classification=%u, flags=0, federation='%s', "
			   "fed_id=%d, fed_state=%u, features='%s', id=%u",
			   cluster_table,
			   now, now, object->name, id, object->classification,
			   (object->fed.name) ? object->fed.name : "",
			   fed_id, fed_state, (features) ? features : "",
			   now, object->classification,
			   (object->fed.name) ? object->fed.name : "",
			   fed_id, fed_state, (features) ? features : "", id);
		DB_DEBUG(DB_ASSOC, mysql_conn->conn, "query\n%s", query);
		rc = mysql_db_query(mysql_conn, query);
		xfree(query);
		if (rc != SLURM_SUCCESS) {
			error("Couldn't add cluster %s", object->name);
			xfree(features);
			added=0;
			break;
		}

		affect_rows = last_affected_rows(mysql_conn);

		if (!affect_rows) {
			debug2("nothing changed %d", affect_rows);
			xfree(features);
			continue;
		}

		if (!external_cluster) {
			char *cols = NULL, *vals = NULL;
			xstrcat(cols, "creation_time, mod_time, acct");
			xstrfmtcat(vals, "%ld, %ld, 'root'", now, now);
			xstrfmtcat(extra, ", mod_time=%ld", now);
			if (!object->root_assoc) {
				object->root_assoc =
					xmalloc(sizeof(*object->root_assoc));
				slurmdb_init_assoc_rec(object->root_assoc, 0);
			}

			rc = setup_assoc_limits(object->root_assoc,
						&cols, &vals, &extra,
						QOS_LEVEL_SET, 1);
			if (rc) {
				xfree(extra);
				xfree(cols);
				xfree(vals);
				added=0;
				error("%s: Failed, setup_assoc_limits functions returned error",
				      __func__);
				break;
			}

			/* Add root account */
			xstrfmtcat(query,
				   "insert into \"%s_%s\" (%s, lineage) "
				   "values (%s, '/') "
				   "on duplicate key update deleted=0, "
				   "id_assoc=LAST_INSERT_ID(id_assoc), lineage=VALUES(lineage)%s;",
				   object->name, assoc_table, cols,
				   vals,
				   extra);
			xfree(cols);
			xfree(vals);
			DB_DEBUG(DB_ASSOC, mysql_conn->conn, "query\n%s", query);

			rc = mysql_db_query(mysql_conn, query);
			xfree(query);

			if (rc != SLURM_SUCCESS) {
				error("Couldn't add cluster root assoc");
				xfree(extra);
				xfree(features);
				added=0;
				break;
			}

			/*
			 * Add this base association to the update
			 * list. This cannot be done with
			 * as_mysql_add_assocs() as it uses this assoc
			 * to set things up. Since it isn't there yet it
			 * won't work.
			 */
			if (object->root_assoc->def_qos_id == INFINITE)
				object->root_assoc->def_qos_id = 0;
			xfree(object->root_assoc->cluster);
			object->root_assoc->cluster = xstrdup(object->name);
			xfree(object->root_assoc->acct);
			object->root_assoc->acct = xstrdup("root");
			xfree(object->root_assoc->user);
			object->root_assoc->id =
				mysql_insert_id(mysql_conn->db_conn);
			xfree(object->root_assoc->lineage);
			object->root_assoc->lineage = xstrdup("/");
			if (addto_update_list(mysql_conn->update_list,
					      SLURMDB_ADD_ASSOC,
					      object->root_assoc) !=
			    SLURM_SUCCESS) {
				xfree(extra);
				xfree(features);
				added=0;
				break;
			}
			object->root_assoc = NULL;
		}

		/* Build up extra with cluster specific values for txn table */
		xstrfmtcat(extra,
			   ", federation='%s', fed_id=%d, fed_state=%u, "
			   "features='%s'",
			   (object->fed.name) ? object->fed.name : "",
			   fed_id, fed_state, (features) ? features : "");
		xfree(features);

		/* we always have a ', ' as the first 2 chars */
		tmp_extra = slurm_add_slash_to_quotes(extra+2);

		xstrfmtcat(query,
			   "insert into %s "
			   "(timestamp, action, name, actor, info) "
			   "values (%ld, %u, '%s', '%s', '%s');",
			   txn_table, now, DBD_ADD_CLUSTERS,
			   object->name, user_name, tmp_extra);
		xfree(tmp_extra);
		xfree(extra);
		debug4("%d(%s:%d) query\n%s",
		       mysql_conn->conn, THIS_FILE, __LINE__, query);

		rc = mysql_db_query(mysql_conn, query);
		xfree(query);
		if (rc != SLURM_SUCCESS) {
			error("Couldn't add txn");
		} else {
			list_itr_t *check_itr;
			char *tmp_name;

			added++;
			/* add it to the list and sort */
			slurm_rwlock_wrlock(&as_mysql_cluster_list_lock);
			check_itr = list_iterator_create(as_mysql_cluster_list);
			while ((tmp_name = list_next(check_itr))) {
				if (!xstrcmp(tmp_name, object->name))
					break;
			}
			list_iterator_destroy(check_itr);
			if (!tmp_name) {
				list_append(as_mysql_cluster_list,
					    xstrdup(object->name));
				list_sort(as_mysql_cluster_list,
					  (ListCmpF)slurm_sort_char_list_asc);
			} else
				error("Cluster %s(%s) appears to already be in "
				      "our cache list, not adding.", tmp_name,
				      object->name);
			slurm_rwlock_unlock(&as_mysql_cluster_list_lock);
		}

		if (!external_cluster) {
			/* Add user root by default to run from the root
			 * association.  This gets popped off so we need to
			 * read it every time here.
			 */
			assoc = xmalloc(sizeof(slurmdb_assoc_rec_t));
			slurmdb_init_assoc_rec(assoc, 0);
			list_append(assoc_list, assoc);

			assoc->cluster = xstrdup(object->name);
			assoc->user = xstrdup("root");
			assoc->acct = xstrdup("root");
			assoc->is_def = 1;
			assoc->flags = ASSOC_FLAG_BLOCK_ADD;

			/*
			 * If the cluster is registering then don't add to the
			 * update_list.
			 */
			if (object->flags & CLUSTER_FLAG_REGISTER)
				assoc->flags |= ASSOC_FLAG_NO_UPDATE;
			if (as_mysql_add_assocs(mysql_conn, uid, assoc_list)
			    == SLURM_ERROR) {
				error("Problem adding root user association");
				rc = SLURM_ERROR;
			}
		}
	}
end_it:
	list_iterator_destroy(itr);
	xfree(user_name);

	FREE_NULL_LIST(assoc_list);

	if (!added)
		reset_mysql_conn(mysql_conn);
	else if (has_feds)
		as_mysql_add_feds_to_update_list(mysql_conn);

	return rc;
}

static int _reconcile_existing_features(void *object, void *arg)
{
	char *new_feature = (char *)object;
	list_t *existing_features = arg;

	if (new_feature[0] == '-')
		list_delete_all(existing_features, slurm_find_char_in_list,
				new_feature + 1);
	else if (new_feature[0] == '+')
		list_append(existing_features, xstrdup(new_feature + 1));
	else
		list_append(existing_features, xstrdup(new_feature));

	return SLURM_SUCCESS;
}

extern list_t *as_mysql_modify_clusters(mysql_conn_t *mysql_conn, uint32_t uid,
					slurmdb_cluster_cond_t *cluster_cond,
					slurmdb_cluster_rec_t *cluster)
{
	list_t *ret_list = NULL;
	int rc = SLURM_SUCCESS;
	char *object = NULL;
	char *vals = NULL, *extra = NULL, *query = NULL, *name_char = NULL;
	time_t now = time(NULL);
	char *user_name = NULL;
	int set = 0;
	MYSQL_RES *result = NULL;
	MYSQL_ROW row;
	bool clust_reg = false, fed_update = false;

	/* If you need to alter the default values of the cluster use
	 * modify_assocs since this is used only for registering
	 * the controller when it loads
	 */

	if (!cluster_cond || !cluster) {
		error("we need something to change");
		return NULL;
	}

	if (check_connection(mysql_conn) != SLURM_SUCCESS)
		return NULL;

	if (!is_user_min_admin_level(mysql_conn, uid,
				     SLURMDB_ADMIN_SUPER_USER)) {
		errno = ESLURM_ACCESS_DENIED;
		return NULL;
	}

	/* force to only do non-deleted clusters */
	cluster_cond->with_deleted = 0;
	_setup_cluster_cond_limits(cluster_cond, &extra);

	set = 0;
	if (cluster->control_host) {
		xstrfmtcat(vals, ", control_host='%s'", cluster->control_host);
		set++;
		clust_reg = true;
		fed_update = true;
	}

	if (cluster->control_port) {
		xstrfmtcat(vals, ", control_port=%u, last_port=%u",
			   cluster->control_port, cluster->control_port);
		set++;
		clust_reg = true;
		fed_update = true;
	}

	if (cluster->rpc_version) {
		xstrfmtcat(vals, ", rpc_version=%u", cluster->rpc_version);
		set++;
		clust_reg = true;
		fed_update = true;
	}

	if (cluster->dimensions) {
		xstrfmtcat(vals, ", dimensions=%u", cluster->dimensions);
		clust_reg = true;
	}

	if (cluster->flags != NO_VAL) {
		xstrfmtcat(vals, ", flags=%u", cluster->flags);
		clust_reg = true;
	}

	if (cluster->classification) {
		xstrfmtcat(vals, ", classification=%u",
			   cluster->classification);
	}

	if (cluster->fed.name) {
		xstrfmtcat(vals, ", federation='%s'", cluster->fed.name);
		fed_update = true;
	}

	if (cluster->fed.state != NO_VAL) {
		xstrfmtcat(vals, ", fed_state=%u", cluster->fed.state);
		fed_update = true;
	}

	if (!vals && !cluster->fed.feature_list) {
		xfree(extra);
		errno = SLURM_NO_CHANGE_IN_DATA;
		error("Nothing to change");
		return NULL;
	} else if (clust_reg && (set != 3)) {
		xfree(vals);
		xfree(extra);
		errno = EFAULT;
		error("Need control host, port and rpc version "
		      "to register a cluster");
		return NULL;
	}

	xstrfmtcat(query, "select name, control_port, federation, features, id from %s%s;",
		   cluster_table, extra);

	DB_DEBUG(DB_ASSOC, mysql_conn->conn, "query\n%s", query);
	if (!(result = mysql_db_query_ret(
		      mysql_conn, query, 0))) {
		xfree(query);
		xfree(vals);
		error("no result given for %s", extra);
		xfree(extra);
		return NULL;
	}
	xfree(extra);

	ret_list = list_create(xfree_ptr);
	user_name = uid_to_string((uid_t) uid);
	while ((row = mysql_fetch_row(result))) {
		char *tmp_vals = xstrdup(vals);

		object = xstrdup(row[0]);
		if (clust_reg)
			cluster->id = slurm_atoul(row[4]);

		if (cluster->fed.name) {
			int id = 0;
			char *curr_fed = NULL;
			uint32_t set_state = NO_VAL;

			if (cluster->fed.name[0] != '\0') {
				rc = as_mysql_get_fed_cluster_id(
					mysql_conn, object,
					cluster->fed.name, -1,
					&id);
				if (rc) {
					error("failed to get cluster id for "
					      "federation");
					xfree(tmp_vals);
					xfree(object);
					FREE_NULL_LIST(ret_list);
					mysql_free_result(result);
					goto end_it;
				}
			}
			/* will set fed_id=0 if being removed from fed. */
			xstrfmtcat(tmp_vals, ", fed_id=%d", id);

			curr_fed = xstrdup(row[2]);
			if (cluster->fed.name[0] == '\0')
				/* clear fed_state when leaving federation */
				set_state = CLUSTER_FED_STATE_NA;
			else if (cluster->fed.state != NO_VAL) {
				/* NOOP: fed_state already set in vals */
			} else if (xstrcmp(curr_fed, cluster->fed.name))
				/* set state to active when joining fed * */
				set_state = CLUSTER_FED_STATE_ACTIVE;
			/* else use existing state */

			if (set_state != NO_VAL)
				xstrfmtcat(tmp_vals, ", fed_state=%u",
					   set_state);

			xfree(curr_fed);
		}

		if (cluster->fed.feature_list) {
			if (!list_count(cluster->fed.feature_list)) {
				/* clear all existing features */
				xstrfmtcat(tmp_vals, ", features=''");
			} else {
				char *features = NULL, *feature = NULL;
				list_t *existing_features = list_create(xfree_ptr);

				if ((feature =
				     list_peek(cluster->fed.feature_list)) &&
				    (feature[0] == '+' || feature[0] == '-'))
					slurm_addto_char_list(existing_features,
							      row[3]);

				list_for_each(cluster->fed.feature_list,
					      _reconcile_existing_features,
					      existing_features);

				features =
					slurm_char_list_to_xstr(
						existing_features);
				xstrfmtcat(tmp_vals, ", features='%s'",
					   features ? features : "");

				xfree(features);
				FREE_NULL_LIST(existing_features);
			}

			fed_update = true;
		}

		list_append(ret_list, object);
		xstrfmtcat(name_char, "name='%s'", object);

		rc = modify_common(mysql_conn, DBD_MODIFY_CLUSTERS, now,
				   user_name, cluster_table,
				   name_char, tmp_vals, NULL);
		xfree(name_char);
		xfree(tmp_vals);
		if (rc == SLURM_ERROR) {
			error("Couldn't modify cluster 1");
			FREE_NULL_LIST(ret_list);
			mysql_free_result(result);
			goto end_it;
		}
	}
	mysql_free_result(result);
	xfree(user_name);

	if (!list_count(ret_list)) {
		errno = SLURM_NO_CHANGE_IN_DATA;
		DB_DEBUG(DB_ASSOC, mysql_conn->conn,
		         "didn't affect anything\n%s", query);
		xfree(name_char);
		xfree(vals);
		xfree(query);
		return ret_list;
	}

	if (fed_update)
		as_mysql_add_feds_to_update_list(mysql_conn);

end_it:
	xfree(query);
	xfree(vals);
	xfree(user_name);

	return ret_list;
}

extern list_t *as_mysql_remove_clusters(mysql_conn_t *mysql_conn, uint32_t uid,
					slurmdb_cluster_cond_t *cluster_cond)
{
	list_itr_t *itr = NULL;
	list_t *tmp_list = NULL;
	int rc = SLURM_SUCCESS;
	char *object = NULL;
	char *extra = NULL, *query = NULL, *cluster_name = NULL;
	time_t now = time(NULL);
	slurmdb_wckey_cond_t wckey_cond;
	MYSQL_RES *result = NULL;
	MYSQL_ROW row;
	bool fed_update = false;

	remove_common_args_t args = {
		.jobs_running = false,
		.mysql_conn = mysql_conn,
		.now = now,
		.table = cluster_table,
		.type = DBD_REMOVE_CLUSTERS,
	};

	if (!cluster_cond) {
		error("we need something to change");
		return NULL;
	}

	if (check_connection(mysql_conn) != SLURM_SUCCESS)
		return NULL;

	if (!is_user_min_admin_level(
		    mysql_conn, uid, SLURMDB_ADMIN_SUPER_USER)) {
		errno = ESLURM_ACCESS_DENIED;
		return NULL;
	}

	/* force to only do non-deleted clusters */
	cluster_cond->with_deleted = 0;
	_setup_cluster_cond_limits(cluster_cond, &extra);

	if (!extra) {
		error("Nothing to remove");
		return NULL;
	}

	query = xstrdup_printf("select name,federation from %s%s;",
			       cluster_table, extra);
	xfree(extra);
	if (!(result = mysql_db_query_ret(
		      mysql_conn, query, 0))) {
		xfree(query);
		return NULL;
	}
	rc = 0;
	args.ret_list = list_create(xfree_ptr);

	if (!mysql_num_rows(result)) {
		mysql_free_result(result);
		errno = SLURM_NO_CHANGE_IN_DATA;
		DB_DEBUG(DB_ASSOC, mysql_conn->conn,
		         "didn't affect anything\n%s", query);
		xfree(query);
		return args.ret_list;
	}
	xfree(query);

	args.assoc_char = xstrdup_printf("t2.lineage like '/%%'");
	args.user_name = uid_to_string((uid_t) uid);

	while ((row = mysql_fetch_row(result))) {
		char *object = xstrdup(row[0]);
		if (!args.jobs_running) {
			/* strdup the cluster name because ret_list will be
			 * flushed if there are running jobs. This will cause an
			 * invalid read because _check_jobs_before_remove() will
			 * still try to access "cluster_name" which was
			 * "object". */
			list_append(args.ret_list, xstrdup(object));
		}

		if (row[1] && (*row[1] != '\0'))
			fed_update = true;

		xfree(args.name_char);
		xstrfmtcat(args.name_char, "name='%s'", object);

		args.cluster_name = object;
		rc = remove_common(&args);

		xfree(object);
		if (rc != SLURM_SUCCESS)
			break;
	}
	mysql_free_result(result);
	xfree(args.user_name);
	xfree(args.name_char);
	xfree(args.assoc_char);

	if (rc != SLURM_SUCCESS) {
		FREE_NULL_LIST(args.ret_list);
		return NULL;
	}
	if (!args.jobs_running) {
		/* We need to remove these clusters from the wckey table */
		memset(&wckey_cond, 0, sizeof(slurmdb_wckey_cond_t));
		wckey_cond.cluster_list = args.ret_list;
		tmp_list = as_mysql_remove_wckeys(mysql_conn, uid, &wckey_cond);
		FREE_NULL_LIST(tmp_list);

		itr = list_iterator_create(args.ret_list);
		while ((object = list_next(itr))) {
			if ((rc = remove_cluster_tables(mysql_conn, object))
			    != SLURM_SUCCESS)
				break;
			cluster_name = xstrdup(object);
			if (addto_update_list(mysql_conn->update_list,
					      SLURMDB_REMOVE_CLUSTER,
					      cluster_name) != SLURM_SUCCESS)
				xfree(cluster_name);
		}
		list_iterator_destroy(itr);

		if (rc != SLURM_SUCCESS) {
			reset_mysql_conn(mysql_conn);
			FREE_NULL_LIST(args.ret_list);
			errno = rc;
			return NULL;
		}

		if (fed_update)
			as_mysql_add_feds_to_update_list(mysql_conn);

		errno = SLURM_SUCCESS;
	} else
		errno = ESLURM_JOBS_RUNNING_ON_ASSOC;

	xfree(query);

	return args.ret_list;
}

extern list_t *as_mysql_get_clusters(mysql_conn_t *mysql_conn, uid_t uid,
				     slurmdb_cluster_cond_t *cluster_cond)
{
	char *query = NULL;
	char *extra = NULL;
	char *tmp = NULL;
	list_t *cluster_list = NULL;
	list_itr_t *itr = NULL;
	int i=0;
	MYSQL_RES *result = NULL;
	MYSQL_ROW row;
	slurmdb_assoc_cond_t assoc_cond;
	list_itr_t *assoc_itr = NULL;
	slurmdb_cluster_rec_t *cluster = NULL;
	slurmdb_assoc_rec_t *assoc = NULL;
	list_t *assoc_list = NULL;

	/* if this changes you will need to edit the corresponding enum */
	char *cluster_req_inx[] = {
		"name",
		"id",
		"classification",
		"control_host",
		"control_port",
		"deleted",
		"features",
		"federation",
		"fed_id",
		"fed_state",
		"rpc_version",
		"dimensions",
		"flags",
	};
	enum {
		CLUSTER_REQ_NAME,
		CLUSTER_REQ_ID,
		CLUSTER_REQ_CLASS,
		CLUSTER_REQ_CH,
		CLUSTER_REQ_CP,
		CLUSTER_REQ_DELETED,
		CLUSTER_REQ_FEATURES,
		CLUSTER_REQ_FEDR,
		CLUSTER_REQ_FEDID,
		CLUSTER_REQ_FEDSTATE,
		CLUSTER_REQ_VERSION,
		CLUSTER_REQ_DIMS,
		CLUSTER_REQ_FLAGS,
		CLUSTER_REQ_COUNT
	};

	if (check_connection(mysql_conn) != SLURM_SUCCESS)
		return NULL;


	if (!cluster_cond) {
		xstrcat(extra, " where deleted=0");
		goto empty;
	}

	_setup_cluster_cond_limits(cluster_cond, &extra);

empty:

	xfree(tmp);
	i=0;
	xstrfmtcat(tmp, "%s", cluster_req_inx[i]);
	for(i=1; i<CLUSTER_REQ_COUNT; i++) {
		xstrfmtcat(tmp, ", %s", cluster_req_inx[i]);
	}

	query = xstrdup_printf("select %s from %s%s",
			       tmp, cluster_table, extra);
	xfree(tmp);
	xfree(extra);

	DB_DEBUG(DB_ASSOC, mysql_conn->conn, "query\n%s", query);
	if (!(result = mysql_db_query_ret(
		      mysql_conn, query, 0))) {
		xfree(query);
		return NULL;
	}
	xfree(query);

	cluster_list = list_create(slurmdb_destroy_cluster_rec);

	memset(&assoc_cond, 0, sizeof(slurmdb_assoc_cond_t));

	if (cluster_cond) {
		/* I don't think we want the with_usage flag here.
		 * We do need the with_deleted though. */
		//assoc_cond.with_usage = cluster_cond->with_usage;
		if (cluster_cond->with_deleted)
			assoc_cond.flags |= ASSOC_COND_FLAG_WITH_DELETED;
	}
	assoc_cond.cluster_list = list_create(NULL);

	while ((row = mysql_fetch_row(result))) {
		MYSQL_RES *result2 = NULL;
		MYSQL_ROW row2;
		char *features = NULL;
		cluster = xmalloc(sizeof(slurmdb_cluster_rec_t));
		slurmdb_init_cluster_rec(cluster, 0);
		list_append(cluster_list, cluster);

		cluster->name = xstrdup(row[CLUSTER_REQ_NAME]);
		cluster->id = slurm_atoul(row[CLUSTER_REQ_ID]);

		list_append(assoc_cond.cluster_list, cluster->name);

		cluster->classification = slurm_atoul(row[CLUSTER_REQ_CLASS]);
		cluster->control_host = xstrdup(row[CLUSTER_REQ_CH]);
		cluster->control_port = slurm_atoul(row[CLUSTER_REQ_CP]);
		cluster->fed.name     = xstrdup(row[CLUSTER_REQ_FEDR]);
		features              = row[CLUSTER_REQ_FEATURES];
		if (features && *features) {
			cluster->fed.feature_list = list_create(xfree_ptr);
			slurm_addto_char_list(cluster->fed.feature_list,
					      features);
		}
		cluster->fed.id       = slurm_atoul(row[CLUSTER_REQ_FEDID]);
		cluster->fed.state    = slurm_atoul(row[CLUSTER_REQ_FEDSTATE]);
		cluster->rpc_version = slurm_atoul(row[CLUSTER_REQ_VERSION]);
		cluster->dimensions = slurm_atoul(row[CLUSTER_REQ_DIMS]);
		cluster->flags = slurm_atoul(row[CLUSTER_REQ_FLAGS]);

		if (row[CLUSTER_REQ_DELETED] &&
		    (row[CLUSTER_REQ_DELETED][0] == '1'))
			cluster->flags |= CLUSTER_FLAG_DELETED;

		query = xstrdup_printf(
			"select tres, cluster_nodes from "
			"\"%s_%s\" where time_end=0 and node_name='' limit 1",
			cluster->name, event_table);
		DB_DEBUG(DB_TRES, mysql_conn->conn, "query\n%s", query);
		if (!(result2 = mysql_db_query_ret(mysql_conn, query, 0))) {
			xfree(query);
			continue;
		}
		xfree(query);
		if ((row2 = mysql_fetch_row(result2))) {
			cluster->tres_str = xstrdup(row2[0]);
			if (row2[1] && row2[1][0])
				cluster->nodes = xstrdup(row2[1]);
		}
		mysql_free_result(result2);

		/* get the usage if requested */
		if (cluster_cond && cluster_cond->with_usage) {
			as_mysql_get_usage(
				mysql_conn, uid, cluster,
				DBD_GET_CLUSTER_USAGE,
				cluster_cond->usage_start,
				cluster_cond->usage_end);
		}

	}
	mysql_free_result(result);

	if (!list_count(assoc_cond.cluster_list)) {
		FREE_NULL_LIST(assoc_cond.cluster_list);
		return cluster_list;
	}

	assoc_cond.acct_list = list_create(NULL);
	list_append(assoc_cond.acct_list, "root");

	assoc_cond.user_list = list_create(NULL);
	list_append(assoc_cond.user_list, "");

	assoc_list = as_mysql_get_assocs(mysql_conn, uid, &assoc_cond);
	FREE_NULL_LIST(assoc_cond.cluster_list);
	FREE_NULL_LIST(assoc_cond.acct_list);
	FREE_NULL_LIST(assoc_cond.user_list);

	if (!assoc_list)
		return cluster_list;

	itr = list_iterator_create(cluster_list);
	assoc_itr = list_iterator_create(assoc_list);
	while ((cluster = list_next(itr))) {
		while ((assoc = list_next(assoc_itr))) {
			if (xstrcmp(assoc->cluster, cluster->name))
				continue;

			if (cluster->root_assoc) {
				debug("This cluster %s already has "
				      "an association.", cluster->name);
				continue;
			}
			cluster->root_assoc = assoc;
			list_remove(assoc_itr);
		}
		list_iterator_reset(assoc_itr);
	}
	list_iterator_destroy(itr);
	list_iterator_destroy(assoc_itr);
	if (list_count(assoc_list))
		error("I have %d left over associations",
		      list_count(assoc_list));
	FREE_NULL_LIST(assoc_list);

	return cluster_list;
}

extern list_t *as_mysql_get_cluster_events(mysql_conn_t *mysql_conn, uint32_t uid,
					   slurmdb_event_cond_t *event_cond)
{
	char *query = NULL;
	char *extra = NULL;
	char *tmp = NULL;
	list_t *ret_list = NULL;
	list_itr_t *itr = NULL;
	char *object = NULL;
	int set = 0;
	int i=0;
	MYSQL_RES *result = NULL;
	MYSQL_ROW row;
	time_t now = time(NULL);
	list_t *use_cluster_list = NULL;
	slurmdb_user_rec_t user;
	bool locked = false;

	if (check_connection(mysql_conn) != SLURM_SUCCESS)
		return NULL;

	memset(&user, 0, sizeof(slurmdb_user_rec_t));
	user.uid = uid;

	if (slurm_conf.private_data & PRIVATE_DATA_EVENTS) {
		if (!is_user_min_admin_level(
			    mysql_conn, uid, SLURMDB_ADMIN_OPERATOR)) {
			error("UID %u tried to access events, only administrators can look at events",
			      uid);
			errno = ESLURM_ACCESS_DENIED;
			return NULL;
		}
	}

	if (!event_cond)
		goto empty;

	if (event_cond->cpus_min) {
		if (extra)
			xstrcat(extra, " && (");
		else
			xstrcat(extra, " where (");

		xstrfmtcat(extra,
			   "(CONVERT(SUBSTRING_INDEX(tres,'%d=',-1),"
			   "UNSIGNED INTEGER)",
			   TRES_CPU);

		if (event_cond->cpus_max) {
			xstrfmtcat(extra, " between %u and %u))",
				   event_cond->cpus_min, event_cond->cpus_max);

		} else {
			xstrfmtcat(extra, "='%u'))",
				   event_cond->cpus_min);

		}
	}

	switch(event_cond->event_type) {
	case SLURMDB_EVENT_ALL:
		break;
	case SLURMDB_EVENT_CLUSTER:
		if (extra)
			xstrcat(extra, " && (");
		else
			xstrcat(extra, " where (");
		xstrcat(extra, "node_name = '')");

		break;
	case SLURMDB_EVENT_NODE:
		if (extra)
			xstrcat(extra, " && (");
		else
			xstrcat(extra, " where (");
		xstrcat(extra, "node_name != '')");

		break;
	default:
		error("Unknown event %u doing all", event_cond->event_type);
		break;
	}

	if (event_cond->node_list) {
		int dims = 0;
		hostlist_t *temp_hl = NULL;

		if (get_cluster_dims(mysql_conn,
				     (char *)list_peek(event_cond->cluster_list),
				     &dims))
			return NULL;

		temp_hl = hostlist_create_dims(event_cond->node_list, dims);
		if (hostlist_count(temp_hl) <= 0) {
			error("we didn't get any real hosts to look for.");
			return NULL;
		}

		set = 0;
		if (extra)
			xstrcat(extra, " && (");
		else
			xstrcat(extra, " where (");

		while ((object = hostlist_shift(temp_hl))) {
			if (set)
				xstrcat(extra, " || ");
			xstrfmtcat(extra, "node_name='%s'", object);
			set = 1;
			free(object);
		}
		xstrcat(extra, ")");
		hostlist_destroy(temp_hl);
	}

	if (event_cond->period_start) {
		if (!event_cond->period_end)
			event_cond->period_end = now;

		if (extra)
			xstrcat(extra, " && (");
		else
			xstrcat(extra, " where (");

		if (event_cond->cond_flags & SLURMDB_EVENT_COND_OPEN)
			xstrfmtcat(extra,
				   "(time_start >= %ld) && (time_end = 0))",
				   event_cond->period_start);
		else
			xstrfmtcat(extra,
				   "(time_start < %ld) "
				   "&& (time_end >= %ld || time_end = 0))",
				   event_cond->period_end,
				   event_cond->period_start);

	} else if (event_cond->cond_flags & SLURMDB_EVENT_COND_OPEN) {
		if (extra)
			xstrcat(extra, " && (");
		else
			xstrcat(extra, " where (");

		xstrfmtcat(extra, "time_end = 0)");
	}

	if (event_cond->reason_list
	    && list_count(event_cond->reason_list)) {
		set = 0;
		if (extra)
			xstrcat(extra, " && (");
		else
			xstrcat(extra, " where (");
		itr = list_iterator_create(event_cond->reason_list);
		while ((object = list_next(itr))) {
			if (set)
				xstrcat(extra, " || ");
			xstrfmtcat(extra, "reason like '%%%s%%'", object);
			set = 1;
		}
		list_iterator_destroy(itr);
		xstrcat(extra, ")");
	}

	if (event_cond->reason_uid_list
	    && list_count(event_cond->reason_uid_list)) {
		set = 0;
		if (extra)
			xstrcat(extra, " && (");
		else
			xstrcat(extra, " where (");
		itr = list_iterator_create(event_cond->reason_uid_list);
		while ((object = list_next(itr))) {
			if (set)
				xstrcat(extra, " || ");
			xstrfmtcat(extra, "reason_uid='%s'", object);
			set = 1;
		}
		list_iterator_destroy(itr);
		xstrcat(extra, ")");
	}

	if (event_cond->state_list
	    && list_count(event_cond->state_list)) {
		set = 0;
		if (extra)
			xstrcat(extra, " && (");
		else
			xstrcat(extra, " where (");
		itr = list_iterator_create(event_cond->state_list);
		while ((object = list_next(itr))) {
			uint32_t tmp_state = strtol(object, NULL, 10);
			if (set)
				xstrcat(extra, " || ");
			if (tmp_state & NODE_STATE_BASE)
				xstrfmtcat(extra, "(state&%u)=%u",
					   NODE_STATE_BASE,
					   tmp_state & NODE_STATE_BASE);
			else
				xstrfmtcat(extra, "state&%u", tmp_state);
			set = 1;
		}
		list_iterator_destroy(itr);
		xstrcat(extra, ")");
	}

empty:
	xfree(tmp);
	xstrfmtcat(tmp, "%s", event_req_inx[0]);
	for(i=1; i<EVENT_REQ_COUNT; i++) {
		bool include = true;
		if (event_cond && event_cond->format_list)
			include = list_find_first(event_cond->format_list,
						  slurm_find_char_in_list,
						  event_req_inx[i]);
		xstrfmtcat(tmp, ", %s%s",
			   include ? "" : "'' as ", event_req_inx[i]);
	}

	if (event_cond && event_cond->cluster_list &&
	    list_count(event_cond->cluster_list)) {
		use_cluster_list = event_cond->cluster_list;
	} else {
		slurm_rwlock_rdlock(&as_mysql_cluster_list_lock);
		use_cluster_list = list_shallow_copy(as_mysql_cluster_list);
		locked = true;
	}

	ret_list = list_create(slurmdb_destroy_event_rec);

	itr = list_iterator_create(use_cluster_list);
	while ((object = list_next(itr))) {
		query = xstrdup_printf("select %s from \"%s_%s\"",
				       tmp, object, event_table);
		if (extra)
			xstrfmtcat(query, " %s", extra);

		DB_DEBUG(DB_ASSOC, mysql_conn->conn, "query\n%s", query);
		if (!(result = mysql_db_query_ret(
			      mysql_conn, query, 0))) {
			xfree(query);
			if (mysql_errno(mysql_conn->db_conn)
			    != ER_NO_SUCH_TABLE) {
				FREE_NULL_LIST(ret_list);
				ret_list = NULL;
			}
			break;
		}
		xfree(query);

		while ((row = mysql_fetch_row(result))) {
			slurmdb_event_rec_t *event =
				xmalloc(sizeof(slurmdb_event_rec_t));

			list_append(ret_list, event);

			event->cluster = xstrdup(object);

			if (row[EVENT_REQ_NODE] && row[EVENT_REQ_NODE][0]) {
				event->node_name = xstrdup(row[EVENT_REQ_NODE]);
				event->event_type = SLURMDB_EVENT_NODE;
			} else
				event->event_type = SLURMDB_EVENT_CLUSTER;

			event->state = slurm_atoul(row[EVENT_REQ_STATE]);
			event->period_start = slurm_atoul(row[EVENT_REQ_START]);
			event->period_end = slurm_atoul(row[EVENT_REQ_END]);

			if (row[EVENT_REQ_REASON] && row[EVENT_REQ_REASON][0])
				event->reason = xstrdup(row[EVENT_REQ_REASON]);
			event->reason_uid =
				slurm_atoul(row[EVENT_REQ_REASON_UID]);

			if (row[EVENT_REQ_CNODES] && row[EVENT_REQ_CNODES][0])
				event->cluster_nodes =
					xstrdup(row[EVENT_REQ_CNODES]);

			if (row[EVENT_REQ_TRES] && row[EVENT_REQ_TRES][0])
				event->tres_str = xstrdup(row[EVENT_REQ_TRES]);
		}
		mysql_free_result(result);
	}
	list_iterator_destroy(itr);
	xfree(tmp);
	xfree(extra);

	if (locked) {
		FREE_NULL_LIST(use_cluster_list);
		slurm_rwlock_unlock(&as_mysql_cluster_list_lock);
	}

	return ret_list;
}

/*
 * _create_instance_rec - create an instance record from two event table rows
 * IN row: the "chronologically older" row
 * IN prev_row: the "chronologically newer" row (may be NULL if row is the first
 *		row from the query result)
 * IN cluster: name of the cluster
 * RET: malloc'd instance
 *
 * Each instance runs "between" events, like the following:
 *
 * Rows from <cluster>_event_table
 * -------------+---------------+---------------+---------------+
 * | time_start	| time_end	| node_name	| instance_id	|
 * -------------+---------------+---------------+---------------+
 * | 00:01:00	| -		| n1		| -		| (prev_row)
 * | -		| 00:00:00	| n1		| 123		| (row)
 * -------------+---------------+---------------+---------------+
 *
 * Resulting slurmdb_instance_rec_t
 * -------------+---------------+---------------+---------------+
 * | time_start	| time_end	| node_name	| instance_id	|
 * -------------+---------------+---------------+---------------+
 * | 00:00:00	| 00:01:00	| n1		| 123		| (instance)
 * -------------+---------------+---------------+---------------+
 *
 * Other fields in slurmdb_instance_rec_t are filled similarly to instance_id.
 *
 */
static slurmdb_instance_rec_t *_create_instance_rec(MYSQL_ROW row,
						    MYSQL_ROW prev_row,
						    char *cluster)
{
	uint32_t instance_start = 0;
	uint32_t instance_end = 0;
	slurmdb_instance_rec_t *instance = NULL;

	if (!row)
		return NULL;

	if (row[EVENT_REQ_END])
		instance_start = slurm_atoul(row[EVENT_REQ_END]);

	if (!instance_start)
		return NULL;

	instance = xmalloc(sizeof(slurmdb_instance_rec_t));
	slurmdb_init_instance_rec(instance);

	instance->cluster = xstrdup(cluster);
	if (row[EVENT_REQ_NODE] && row[EVENT_REQ_NODE][0])
		instance->node_name = xstrdup(row[EVENT_REQ_NODE]);
	if (row[EVENT_REQ_EXTRA] && row[EVENT_REQ_EXTRA][0])
		instance->extra = xstrdup(row[EVENT_REQ_EXTRA]);
	if (row[EVENT_REQ_INSTANCE_ID] && row[EVENT_REQ_INSTANCE_ID][0])
		instance->instance_id = xstrdup(row[EVENT_REQ_INSTANCE_ID]);
	if (row[EVENT_REQ_INSTANCE_TYPE] && row[EVENT_REQ_INSTANCE_TYPE][0])
		instance->instance_type = xstrdup(row[EVENT_REQ_INSTANCE_TYPE]);

	if ((!prev_row) ||
	    ((row[EVENT_REQ_NODE] && prev_row[EVENT_REQ_NODE]) &&
	     xstrcmp(row[EVENT_REQ_NODE], prev_row[EVENT_REQ_NODE]))) {
		/*
		 * "row" is the most recent event record for the current node,
		 * and it does not have an end time. That means the instance is
		 * still running, so the instance's end time is set accordingly
		 */
		instance->time_start = instance_start;
		instance->time_end = 0;

		return instance;
	}

	if (prev_row[EVENT_REQ_START])
		instance_end = slurm_atoul(prev_row[EVENT_REQ_START]);

	instance->time_start = instance_start;
	instance->time_end = instance_end;

	return instance;
}

static void _add_char_list_to_where_clause(list_t *char_list,
					   const char *col_name,
					   char **where_clause)
{
	list_itr_t *itr = NULL;
	int set = 0;
	char *string = NULL;

	if (!where_clause)
		return;

	if (char_list && list_count(char_list)) {
		if (*where_clause)
			xstrcat(*where_clause, " AND (");
		else
			xstrcat(*where_clause, " where (");
		itr = list_iterator_create(char_list);
		while ((string = list_next(itr))) {
			if (set)
				xstrcat(*where_clause, " OR ");
			xstrfmtcat(*where_clause, "%s='%s'", col_name, string);
			set = 1;
		}
		list_iterator_destroy(itr);
		xstrcat(*where_clause, ")");
	}
}

extern list_t *as_mysql_get_instances(mysql_conn_t *mysql_conn, uint32_t uid,
				      slurmdb_instance_cond_t *instance_cond)
{
	bool locked = false;
	char *cluster = NULL;
	char *where_clause = NULL;
	char *object = NULL;
	char *query = NULL;
	char *tmp = NULL;
	int i = 0;
	int set = 0;
	list_t *ret_list = NULL;
	list_t *use_cluster_list = NULL;
	list_itr_t *itr = NULL;
	MYSQL_RES *result = NULL;
	MYSQL_ROW row, prev_row = NULL;
	slurmdb_user_rec_t user;
	time_t now = time(NULL);

	if (check_connection(mysql_conn) != SLURM_SUCCESS)
		return NULL;

	memset(&user, 0, sizeof(slurmdb_user_rec_t));
	user.uid = uid;

	if (slurm_conf.private_data & PRIVATE_DATA_EVENTS) {
		if (!is_user_min_admin_level(mysql_conn, uid,
					     SLURMDB_ADMIN_OPERATOR)) {
			error("UID %u tried to access events, only administrators can look at events",
			      uid);
			errno = ESLURM_ACCESS_DENIED;
			return NULL;
		}
	}


	/* determine cluster list */
	if (instance_cond && instance_cond->cluster_list &&
	    list_count(instance_cond->cluster_list)) {
		use_cluster_list = instance_cond->cluster_list;
	} else {
		slurm_rwlock_rdlock(&as_mysql_cluster_list_lock);
		use_cluster_list = list_shallow_copy(as_mysql_cluster_list);
		locked = true;
	}

	/* Only query node events for CLOUD & POWERED_DOWN nodes */
	where_clause = xstrdup_printf(" where (node_name!='') AND "
				      "(state & %"PRIu64")",
				      NODE_STATE_POWERED_DOWN);

	if (!instance_cond)
		goto empty;

	/* Set default time_start to the start of previous day */
	if (!instance_cond->time_start) {
		instance_cond->time_start = now;
		struct tm start_tm;

		if (!localtime_r(&instance_cond->time_start, &start_tm)) {
			error("Couldn't get localtime from %ld",
			      (long) instance_cond->time_start);
		} else {
			start_tm.tm_sec = 0;
			start_tm.tm_min = 0;
			start_tm.tm_hour = 0;
			start_tm.tm_mday--;
			instance_cond->time_start = slurm_mktime(&start_tm);
		}
	}

	if (instance_cond->time_start) {
		if (!instance_cond->time_end)
			instance_cond->time_end = now;

		xstrfmtcat(where_clause,
			   " AND ((time_start < %ld) AND (time_end >= %ld OR time_end = 0))",
			   instance_cond->time_end,
			   instance_cond->time_start);
	}

	_add_char_list_to_where_clause(instance_cond->extra_list, "extra",
				       &where_clause);
	_add_char_list_to_where_clause(instance_cond->instance_id_list,
				       "instance_id", &where_clause);
	_add_char_list_to_where_clause(instance_cond->instance_type_list,
				       "instance_type", &where_clause);

	if (instance_cond->node_list) {
		int dims = 0;
		hostlist_t *temp_hl = NULL;

		if (get_cluster_dims(
			    mysql_conn,
			    (char *) list_peek(use_cluster_list),
			    &dims)) {
			xfree(where_clause);
			return NULL;
		}

		temp_hl = hostlist_create_dims(instance_cond->node_list, dims);
		if (hostlist_count(temp_hl) <= 0) {
			xfree(where_clause);
			error("we didn't get any real hosts to look for.");
			return NULL;
		}

		set = 0;
		if (where_clause)
			xstrcat(where_clause, " AND (");
		else
			xstrcat(where_clause, " where (");

		while ((object = hostlist_shift(temp_hl))) {
			if (set)
				xstrcat(where_clause, " OR ");
			xstrfmtcat(where_clause, "node_name='%s'", object);
			set = 1;
			free(object);
		}
		xstrcat(where_clause, ")");
		hostlist_destroy(temp_hl);
	}

empty:
	xfree(tmp);
	xstrfmtcat(tmp, "%s", event_req_inx[0]);
	for (i = 1; i < EVENT_REQ_COUNT; i++) {
		bool include = true;
		xstrfmtcat(tmp, ", %s%s", include ? "" : "'' as ",
			   event_req_inx[i]);
	}

	ret_list = list_create(slurmdb_destroy_instance_rec);


	itr = list_iterator_create(use_cluster_list);
	while ((cluster = list_next(itr))) {
		query = xstrdup_printf("select %s from \"%s_%s\" %s "
				       "order by node_name,time_start desc",
				       tmp, cluster, event_table,
				       where_clause ? where_clause : "");

		DB_DEBUG(DB_EVENT, mysql_conn->conn, "query\n%s", query);
		if (!(result = mysql_db_query_ret(mysql_conn, query, 0))) {
			xfree(query);
			if (mysql_errno(mysql_conn->db_conn) !=
			    ER_NO_SUCH_TABLE) {
				FREE_NULL_LIST(ret_list);
				ret_list = NULL;
			}
			break;
		}
		xfree(query);

		while ((row = mysql_fetch_row(result))) {
			slurmdb_instance_rec_t *instance = _create_instance_rec(
				row, prev_row, cluster);

			if (instance) {
				list_append(ret_list, instance);
			}

			prev_row = row;
		}
		mysql_free_result(result);
	}
	list_iterator_destroy(itr);
	xfree(tmp);
	xfree(where_clause);

	if (locked) {
		FREE_NULL_LIST(use_cluster_list);
		slurm_rwlock_unlock(&as_mysql_cluster_list_lock);
	}

	return ret_list;
}

extern int as_mysql_node_down(mysql_conn_t *mysql_conn,
			      node_record_t *node_ptr,
			      time_t event_time, char *reason,
			      uint32_t reason_uid)
{
	int rc = SLURM_SUCCESS;
	char *query = NULL;
	char *my_reason;
	MYSQL_RES *result = NULL;
	MYSQL_ROW row;

	if (check_connection(mysql_conn) != SLURM_SUCCESS)
		return ESLURM_DB_CONNECTION;

	if (!mysql_conn->cluster_name) {
		error("%s:%d no cluster name", THIS_FILE, __LINE__);
		return SLURM_ERROR;
	}

	if (!node_ptr) {
		error("No node_ptr given!");
		return SLURM_ERROR;
	}

	if (!node_ptr->tres_str) {
		error("node ptr has no tres_list!");
		return SLURM_ERROR;
	}

	query = xstrdup_printf("select state, reason, time_start from \"%s_%s\" where "
			       "time_end=0 and node_name='%s';",
			       mysql_conn->cluster_name, event_table,
			       node_ptr->name);
	/* info("%d(%s:%d) query\n%s", */
	/*        mysql_conn->conn, THIS_FILE, __LINE__, query); */
	result = mysql_db_query_ret(mysql_conn, query, 0);
	xfree(query);

	if (!result)
		return SLURM_ERROR;

	if (reason)
		my_reason = reason;
	else
		my_reason = node_ptr->reason;

	if (!my_reason)
		my_reason = "";

	row = mysql_fetch_row(result);
	if (row && (node_ptr->node_state == slurm_atoul(row[0])) &&
	    !xstrcasecmp(my_reason, row[1])) {
		DB_DEBUG(DB_EVENT, mysql_conn->conn,
		         "no change to %s(%s) needed %u == %s and %s == %s",
		         node_ptr->name, mysql_conn->cluster_name,
		         node_ptr->node_state, row[0], my_reason, row[1]);
		mysql_free_result(result);
		return SLURM_SUCCESS;
	}

	if (row && (event_time == slurm_atoul(row[2]))) {
		/*
		 * If you are clean-restarting the controller over and over
		 * again you could get records that are duplicates in the
		 * database. If this is the case we will zero out the time_end
		 * we are just filled in. This will cause the last time to be
		 * erased from the last restart, but if you are restarting
		 * things this often the previous one didn't mean anything
		 * anyway. This way we only get one for the last time we let it
		 * run.
		 */
		query = xstrdup_printf(
			"update \"%s_%s\" set reason='%s' where "
			"time_start=%ld and node_name='%s';",
			mysql_conn->cluster_name, event_table,
			my_reason, event_time, node_ptr->name);
		DB_DEBUG(DB_EVENT, mysql_conn->conn, "query\n%s", query);
		rc = mysql_db_query(mysql_conn, query);
		xfree(query);

		mysql_free_result(result);
		return rc;
	}

	mysql_free_result(result);

	DB_DEBUG(DB_EVENT, mysql_conn->conn,
	         "inserting %s(%s) with tres of '%s'",
		 node_ptr->name, mysql_conn->cluster_name, node_ptr->tres_str);

	query = xstrdup_printf(
		"update \"%s_%s\" set time_end=%ld where "
		"time_end=0 and node_name='%s';",
		mysql_conn->cluster_name, event_table,
		event_time, node_ptr->name);

	/*
	 * Reason for "on duplicate": slurmctld will send a time_start based on
	 * the state of the "node_state" state file. If the the slurmctld is
	 * "killed" before updating the state file, the slurmctld can send the
	 * same time_start for the node and cause a "Duplicate entry" error.
	 * This can particularly happen when doing clean starts.
	 */
	xstrfmtcat(query,
		   "insert into \"%s_%s\" "
		   "(node_name, state, tres, time_start, "
		   "reason, reason_uid) "
		   "values ('%s', %u, '%s', %ld, '%s', %u) "
		   "on duplicate key update time_end=0;",
		   mysql_conn->cluster_name, event_table,
		   node_ptr->name, node_ptr->node_state,
		   node_ptr->tres_str, event_time, my_reason, reason_uid);
	DB_DEBUG(DB_EVENT, mysql_conn->conn, "query\n%s", query);
	rc = mysql_db_query(mysql_conn, query);
	xfree(query);

	return rc;
}

extern int as_mysql_node_up(mysql_conn_t *mysql_conn,
			    node_record_t *node_ptr,
			    time_t event_time)
{
	char* query;
	int rc = SLURM_SUCCESS;

	if (check_connection(mysql_conn) != SLURM_SUCCESS)
		return ESLURM_DB_CONNECTION;

	if (!mysql_conn->cluster_name) {
		error("%s:%d no cluster name", THIS_FILE, __LINE__);
		return SLURM_ERROR;
	}

	query = xstrdup_printf(
		"update \"%s_%s\" set time_end=%ld where "
		"time_end=0 and node_name='%s';",
		mysql_conn->cluster_name, event_table,
		event_time, node_ptr->name);
	DB_DEBUG(DB_EVENT, mysql_conn->conn, "query\n%s", query);
	rc = mysql_db_query(mysql_conn, query);
	xfree(query);
	return rc;
}

extern int as_mysql_node_update(mysql_conn_t *mysql_conn,
				node_record_t *node_ptr)
{
	char *query;
	char *values = NULL;
	int rc = SLURM_SUCCESS;
	MYSQL_RES *result = NULL;

	if (check_connection(mysql_conn) != SLURM_SUCCESS)
		return ESLURM_DB_CONNECTION;

	if (!mysql_conn->cluster_name) {
		error("%s:%d no cluster name", THIS_FILE, __LINE__);
		return SLURM_ERROR;
	}

	xstrfmtcat(values, "%sextra='%s'",
		   values ? ", " : "",
		   node_ptr->extra ? node_ptr->extra : "");
	xstrfmtcat(values, "%sinstance_id='%s'",
		   values ? ", " : "",
		   node_ptr->instance_id ? node_ptr->instance_id : "");
	xstrfmtcat(values, "%sinstance_type='%s'",
		   values ? ", " : "",
		   node_ptr->instance_type ? node_ptr->instance_type : "");

	query = xstrdup_printf("select time_start from \"%s_%s\" "
			       "where node_name='%s' AND (state & %"PRIu64") limit 1;",
			       mysql_conn->cluster_name, event_table,
			       node_ptr->name, NODE_STATE_POWERED_DOWN);
	DB_DEBUG(DB_EVENT, mysql_conn->conn, "check event table status for node '%s':\n%s",
		 node_ptr->name, query);
	result = mysql_db_query_ret(mysql_conn, query, 0);
	xfree(query);

	if (!result) {
		xfree(values);
		return SLURM_ERROR;
	}
	if (!mysql_fetch_row(result)) {
		/* create new event if no events for the node yet. */
		as_mysql_node_down(mysql_conn, node_ptr, time(NULL),
				   "node-update", slurm_conf.slurm_user_id);
		as_mysql_node_up(mysql_conn, node_ptr, time(NULL));
	}
	mysql_free_result(result);

	query = xstrdup_printf("update \"%s_%s\" "
			       "set %s "
			       "where node_name='%s' AND (state & %"PRIu64") "
			       "order by time_start desc "
			       "limit 1",
			       mysql_conn->cluster_name, event_table, values,
			       node_ptr->name, NODE_STATE_POWERED_DOWN);
	DB_DEBUG(DB_EVENT, mysql_conn->conn, "query\n%s", query);
	rc = mysql_db_query(mysql_conn, query);
	xfree(query);
	xfree(values);
	return rc;
}

/* This function is not used in the slurmdbd. */
extern int as_mysql_register_ctld(mysql_conn_t *mysql_conn,
				  char *cluster, uint16_t port)
{
	return SLURM_ERROR;
}

extern int as_mysql_fini_ctld(mysql_conn_t *mysql_conn,
			      slurmdb_cluster_rec_t *cluster_rec)
{
	int rc = SLURM_SUCCESS;
	time_t now = time(NULL);
	char *query = NULL;
	bool free_it = false;
	bool affected_rows = false;

	if (check_connection(mysql_conn) != SLURM_SUCCESS)
		return ESLURM_DB_CONNECTION;

	/* Here we need to check make sure we are updating the entry
	   correctly just in case the backup has already gained
	   control.  If we check the ip and port it is a pretty safe
	   bet we have the right ctld.
	*/
	query = xstrdup_printf(
		"update %s set mod_time=%ld, control_host='', "
		"control_port=0 where name='%s' && "
		"control_host='%s' && control_port=%u;",
		cluster_table, now, cluster_rec->name,
		cluster_rec->control_host, cluster_rec->control_port);
	DB_DEBUG(DB_EVENT, mysql_conn->conn, "query\n%s", query);
	rc = mysql_db_query(mysql_conn, query);
	xfree(query);

	if (rc != SLURM_SUCCESS)
		return SLURM_ERROR;

	affected_rows = last_affected_rows(mysql_conn);
	if (affected_rows)
		as_mysql_add_feds_to_update_list(mysql_conn);

	if (!affected_rows || !slurmdbd_conf->track_ctld ||
	    (cluster_rec->flags & CLUSTER_FLAG_EXT))
		return rc;

	/* If tres is NULL we can get the current number of tres by
	   sending NULL for the tres param in the as_mysql_cluster_tres
	   function.
	*/
	if (!cluster_rec->tres_str) {
		free_it = true;
		as_mysql_cluster_tres(
			mysql_conn, cluster_rec->control_host,
			&cluster_rec->tres_str, now,
			cluster_rec->rpc_version);
	}

	/* Since as_mysql_cluster_tres could change the
	   last_affected_rows we can't group this with the above
	   return.
	*/
	if (!cluster_rec->tres_str)
		return rc;

	/* If we affected things we need to now drain the nodes in the
	 * cluster.  This is to give better stats on accounting that
	 * the ctld was gone so no jobs were able to be scheduled.  We
	 * drain the nodes since the rollup functionality understands
	 * how to deal with that and running jobs so we don't get bad
	 * info.
	 */
	query = xstrdup_printf(
		"insert into \"%s_%s\" (tres, state, time_start, reason) "
		"values ('%s', %u, %ld, 'slurmctld disconnect');",
		cluster_rec->name, event_table,
		cluster_rec->tres_str, NODE_STATE_DOWN, (long)now);

	if (free_it)
		xfree(cluster_rec->tres_str);

	DB_DEBUG(DB_EVENT, mysql_conn->conn, "query\n%s", query);
	rc = mysql_db_query(mysql_conn, query);
	xfree(query);

	return rc;
}

extern int as_mysql_cluster_tres(mysql_conn_t *mysql_conn,
				 char *cluster_nodes, char **tres_str_in,
				 time_t event_time, uint16_t rpc_version)
{
	char* query;
	int rc = SLURM_SUCCESS;
	int response = 0;
	MYSQL_RES *result = NULL;
	MYSQL_ROW row;
	bool handle_disconnect = true;

	xassert(tres_str_in);

 	if (check_connection(mysql_conn) != SLURM_SUCCESS)
		return ESLURM_DB_CONNECTION;

	if (!mysql_conn->cluster_name) {
		error("%s:%d no cluster name", THIS_FILE, __LINE__);
		return SLURM_ERROR;
	}

	/* Record the processor count */
	query = xstrdup_printf(
		"select tres, cluster_nodes from \"%s_%s\" where "
		"time_end=0 and node_name='' and state=0 limit 1",
		mysql_conn->cluster_name, event_table);
	if (!(result = mysql_db_query_ret(mysql_conn, query, 0))) {
		xfree(query);
		if (mysql_errno(mysql_conn->db_conn) == ER_NO_SUCH_TABLE)
			rc = ESLURM_ACCESS_DENIED;
		else
			rc = SLURM_ERROR;
		return rc;
	}
	xfree(query);

	/* we only are checking the first one here */
	if (!(row = mysql_fetch_row(result))) {
		debug("We don't have an entry for this machine %s "
		      "most likely a first time running.",
		      mysql_conn->cluster_name);

		/* Get all nodes in a down state and jobs pending or running.
		 * This is for the first time a cluster registers
		 *
		 * We will return ACCOUNTING_FIRST_REG so this
		 * is taken care of since the message thread
		 * may not be up when we run this in the controller or
		 * in the slurmdbd.
		 */
		if (!*tres_str_in) {
			rc = 0;
			goto end_it;
		}

		response = ACCOUNTING_FIRST_REG;
		goto add_it;
	}

	/* If tres is NULL we want to return the tres for this cluster */
	if (!*tres_str_in) {
		*tres_str_in = xstrdup(row[0]);
		goto end_it;
	} else if (xstrcmp(*tres_str_in, row[0])) {
		debug("%s has changed tres from %s to %s",
		      mysql_conn->cluster_name,
		      row[0], *tres_str_in);

		/*
		 * Reset all the entries for this cluster since the tres changed
		 * some of the downed nodes may have gone away.
		 * Request them again with ACCOUNTING_NODES_CHANGE_DB
		 */

		if (xstrcmp(cluster_nodes, row[1])) {
			DB_DEBUG(DB_EVENT, mysql_conn->conn,
			         "Nodes on the cluster have changed.");
			response = ACCOUNTING_NODES_CHANGE_DB;
		} else
			response = ACCOUNTING_TRES_CHANGE_DB;
	} else if (xstrcmp(cluster_nodes, row[1])) {
		DB_DEBUG(DB_EVENT, mysql_conn->conn,
		         "Node names on the cluster have changed.");
		response = ACCOUNTING_NODES_CHANGE_DB;
	} else {
		DB_DEBUG(DB_EVENT, mysql_conn->conn,
		         "We have the same TRES and node names as before for %s, no need to update the database.",
		         mysql_conn->cluster_name);
		goto remove_disconnect;
	}

	query = xstrdup_printf(
		"update \"%s_%s\" set time_end=%ld where time_end=0",
		mysql_conn->cluster_name, event_table, event_time);

	rc = mysql_db_query(mysql_conn, query);
	xfree(query);
	handle_disconnect = false;

	if (rc != SLURM_SUCCESS)
		goto end_it;
add_it:
	query = xstrdup_printf(
		"insert into \"%s_%s\" (cluster_nodes, tres, "
		"time_start, reason) "
		"values ('%s', '%s', %ld, 'Cluster Registered TRES') "
		"on duplicate key update time_end=0, tres=VALUES(tres);",
		mysql_conn->cluster_name, event_table,
		cluster_nodes, *tres_str_in, event_time);

	rc = mysql_db_query(mysql_conn, query);
	xfree(query);

	if (trigger_reroll(mysql_conn, event_time))
		debug("Need to reroll usage from %s, cluster %s changes happened before last rollup.",
		      slurm_ctime2(&event_time), mysql_conn->cluster_name);

	if (rc != SLURM_SUCCESS)
		goto end_it;

remove_disconnect:
	/*
	 * The above update clears all with time_end=0, so no
	 * need to do this again.
	 */
	if (handle_disconnect) {
		query = xstrdup_printf(
			"update \"%s_%s\" set time_end=%ld where time_end=0 and state=%u and node_name='';",
			mysql_conn->cluster_name,
			event_table, event_time,
			NODE_STATE_DOWN);
		(void) mysql_db_query(mysql_conn, query);
		xfree(query);
	}

end_it:
	mysql_free_result(result);
	if (response && rc == SLURM_SUCCESS)
		rc = response;

	return rc;
}
