/*****************************************************************************\
 *  node_mgr.c - manage the node records of slurm
 *	Note: there is a global node table (node_record_table_ptr), its
 *	hash table (node_hash_table), time stamp (last_node_update) and
 *	configuration list (config_list)
 *****************************************************************************
 *  Copyright (C) 2002-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 Morris Jette <jette1@llnl.gov>, et. al.
 *  CODE-OCEC-09-009. 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 "config.h"

#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>

#include "src/common/bitstring.h"
#include "src/common/fd.h"
#include "src/common/fetch_config.h"
#include "src/common/hostlist.h"
#include "src/common/macros.h"
#include "src/common/node_features.h"
#include "src/common/pack.h"
#include "src/common/parse_time.h"
#include "src/common/parse_value.h"
#include "src/common/power_action.h"
#include "src/common/read_config.h"
#include "src/common/slurm_resource_info.h"
#include "src/common/state_save.h"
#include "src/common/timers.h"
#include "src/common/xassert.h"
#include "src/common/xstring.h"

#include "src/interfaces/accounting_storage.h"
#include "src/interfaces/acct_gather_energy.h"
#include "src/interfaces/auth.h"
#include "src/interfaces/conn.h"
#include "src/interfaces/gres.h"
#include "src/interfaces/mcs.h"
#include "src/interfaces/node_features.h"
#include "src/interfaces/select.h"
#include "src/interfaces/serializer.h"
#include "src/interfaces/topology.h"

#include "src/slurmctld/agent.h"
#include "src/slurmctld/locks.h"
#include "src/slurmctld/ping_nodes.h"
#include "src/slurmctld/power_save.h"
#include "src/slurmctld/proc_req.h"
#include "src/slurmctld/read_config.h"
#include "src/slurmctld/reservation.h"
#include "src/slurmctld/sackd_mgr.h"
#include "src/slurmctld/slurmctld.h"
#include "src/slurmctld/state_save.h"
#include "src/slurmctld/trigger_mgr.h"

/* No need to change we always pack SLURM_PROTOCOL_VERSION */
#define NODE_STATE_VERSION        "PROTOCOL_VERSION"

#define DEFAULT_NODE_REG_MEM_PERCENT 100.0
#define DEFAULT_CLOUD_REG_MEM_PERCENT 90.0

static bool config_list_update = false;
static pthread_mutex_t config_list_update_mutex = PTHREAD_MUTEX_INITIALIZER;

typedef struct {
	uid_t uid;
	part_record_t **visible_parts;
} pack_node_info_t;

/* Global variables */
bitstr_t *asap_node_bitmap = NULL; /* bitmap of rebooting asap nodes */
bitstr_t *avail_node_bitmap = NULL;	/* bitmap of available nodes */
bitstr_t *bf_ignore_node_bitmap = NULL; /* bitmap of nodes to ignore during a
					 * backfill cycle */
bitstr_t *booting_node_bitmap = NULL;	/* bitmap of booting nodes */
bitstr_t *cg_node_bitmap    = NULL;	/* bitmap of completing nodes */
bitstr_t *cloud_node_bitmap = NULL;	/* bitmap of cloud nodes */
bitstr_t *external_node_bitmap = NULL;	/* bitmap of external nodes */
bitstr_t *future_node_bitmap = NULL;	/* bitmap of FUTURE nodes */
bitstr_t *idle_node_bitmap  = NULL;	/* bitmap of idle nodes */
bitstr_t *power_down_node_bitmap = NULL; /* bitmap of powered down nodes */
bitstr_t *rs_node_bitmap    = NULL; 	/* bitmap of resuming nodes */
bitstr_t *share_node_bitmap = NULL;  	/* bitmap of sharable nodes */
bitstr_t *up_node_bitmap    = NULL;  	/* bitmap of non-down nodes */
bitstr_t *power_up_node_bitmap = NULL;	/* bitmap of power_up requested nodes */

static int _delete_node_ptr(node_record_t *node_ptr);
static void	_drain_node(node_record_t *node_ptr, char *reason,
			    uint32_t reason_uid);
static void    _make_node_unavail(node_record_t *node_ptr);
static void 	_make_node_down(node_record_t *node_ptr,
				time_t event_time);
static bool	_node_is_hidden(node_record_t *node_ptr,
				pack_node_info_t *pack_info);
static void 	_pack_node(node_record_t *dump_node_ptr, buf_t *buffer,
			   uint16_t protocol_version, uint16_t show_flags);
static void	_sync_bitmaps(node_record_t *node_ptr, int job_count);
static void	_update_config_ptr(bitstr_t *bitmap,
				   config_record_t *config_ptr);
static int	_update_node_gres(char *node_names, char *gres);
static int	_update_node_weight(char *node_names, uint32_t weight);
static bool 	_valid_node_state_change(uint32_t old, uint32_t new);

static char *_get_msg_hostname(slurm_msg_t *msg)
{
	slurm_addr_t *addr = &msg->address;
	char *name = NULL;

	if (addr->ss_family == AF_UNSPEC) {
		int fd = conn_g_get_fd(msg->conn);
		(void) slurm_get_peer_addr(fd, addr);
	}
	if (addr->ss_family != AF_UNSPEC) {
		name = xmalloc(INET6_ADDRSTRLEN);
		slurm_get_ip_str(addr, name, INET6_ADDRSTRLEN);
	}

	return name;
}

static void _dump_cluster_settings(buf_t *buffer)
{
	packstr(slurm_conf.suspend_exc_nodes, buffer);
	packstr(slurm_conf.suspend_exc_parts, buffer);
	packstr(slurm_conf.suspend_exc_states, buffer);
}

static int _load_cluster_settings(bool state_only,
				  buf_t *buffer,
				  uint16_t protocol_version)
{
	char *suspend_exc_nodes = NULL;
	char *suspend_exc_parts = NULL;
	char *suspend_exc_states = NULL;

	if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) {
		safe_unpackstr(&suspend_exc_nodes, buffer);
		safe_unpackstr(&suspend_exc_parts, buffer);
		safe_unpackstr(&suspend_exc_states, buffer);

		if (!state_only) {
			xfree(slurm_conf.suspend_exc_nodes);
			slurm_conf.suspend_exc_nodes = suspend_exc_nodes;
			suspend_exc_nodes = NULL;

			xfree(slurm_conf.suspend_exc_parts);
			slurm_conf.suspend_exc_parts = suspend_exc_parts;
			suspend_exc_parts = NULL;

			xfree(slurm_conf.suspend_exc_states);
			slurm_conf.suspend_exc_states = suspend_exc_states;
			suspend_exc_states = NULL;
		} else {
			xfree(suspend_exc_nodes);
			xfree(suspend_exc_parts);
			xfree(suspend_exc_states);
		}
	}

	return SLURM_SUCCESS;

unpack_error:
	xfree(suspend_exc_nodes);
	xfree(suspend_exc_parts);
	xfree(suspend_exc_states);

	return SLURM_ERROR;
}

/* dump_all_node_state - save the state of all nodes to file */
int dump_all_node_state ( void )
{
	/* Save high-water mark to avoid buffer growth with copies */
	static uint32_t high_buffer_size = (1024 * 1024);
	int error_code = 0, inx;
	node_record_t *node_ptr;
	/* Locks: Read config and node */
	slurmctld_lock_t node_read_lock = { READ_LOCK, NO_LOCK, READ_LOCK,
					    NO_LOCK, NO_LOCK };
	buf_t *buffer = init_buf(high_buffer_size);
	DEF_TIMERS;

	START_TIMER;
	/* write header: version, time */
	packstr(NODE_STATE_VERSION, buffer);
	pack16(SLURM_PROTOCOL_VERSION, buffer);
	pack_time(time (NULL), buffer);

	/* write node records to buffer */
	lock_slurmctld (node_read_lock);
	_dump_cluster_settings(buffer);
	sackd_mgr_dump_state(buffer, SLURM_PROTOCOL_VERSION);
	pack32(node_record_count, buffer);
	for (inx = 0; (node_ptr = next_node(&inx)); inx++) {
		xassert (node_ptr->magic == NODE_MAGIC);
		xassert (node_ptr->config_ptr->magic == CONFIG_MAGIC);
		pack32(inx, buffer);
		node_record_pack_state(node_ptr, SLURM_PROTOCOL_VERSION,
				       buffer);
	}
	unlock_slurmctld (node_read_lock);

	error_code = save_buf_to_state("node_state", buffer, &high_buffer_size);

	FREE_NULL_BUFFER(buffer);
	END_TIMER2(__func__);
	return error_code;
}

static void _queue_consolidate_config_list(void)
{
	slurm_mutex_lock(&config_list_update_mutex);
	config_list_update = true;
	slurm_mutex_unlock(&config_list_update_mutex);
}

static bool _get_config_list_update(void)
{
       bool rc;

       slurm_mutex_lock(&config_list_update_mutex);
       rc = config_list_update;
       slurm_mutex_unlock(&config_list_update_mutex);

       return rc;
}

static int _validate_nodes_vs_nodeset(char *nodes_str)
{
	hostlist_t *nodes = NULL;
	slurm_conf_nodeset_t **ptr;
	int count_nodeset = slurm_conf_nodeset_array(&ptr);

	if (!nodes_str)
		return SLURM_SUCCESS;

	nodes = hostlist_create(nodes_str);
	for (int i = 0; i < count_nodeset; i++) {
		if (hostlist_find(nodes, ptr[i]->name) != -1) {
			error("NodeSet with name %s overlaps with an existing NodeName",
			      ptr[i]->name);
			hostlist_destroy(nodes);
			return ESLURM_INVALID_NODE_NAME;
		}
	}

	hostlist_destroy(nodes);
	return SLURM_SUCCESS;
}

/*
 * load_all_node_state - Load the node state from file, recover on slurmctld
 *	restart. Execute this after loading the configuration file data.
 *	Data goes into common storage.
 * IN state_only - if true, overwrite only node state and reason
 *	Use this to overwrite the "UNKNOWN state typically used in slurm.conf
 * RET SUCCESS or error code
 */
extern int load_all_node_state ( bool state_only )
{
	char *state_file;
	int error_code = SLURM_SUCCESS, node_cnt = 0;

	node_record_t *node_ptr;
	time_t time_stamp;
	buf_t *buffer;
	char *ver_str = NULL;
	hostlist_t *down_nodes = NULL;
	uint16_t protocol_version = NO_VAL16;

	xassert(verify_lock(CONF_LOCK, READ_LOCK));

	/* read the file */
	buffer = state_save_open("node_state", &state_file);
	if (!buffer) {
		if ((clustername_existed == 1) && (!ignore_state_errors))
			fatal("No node state file (%s) to recover", state_file);
		info("No node state file (%s) to recover", state_file);
		xfree(state_file);
		return ENOENT;
	}
	xfree(state_file);

	safe_unpackstr(&ver_str, buffer);
	debug3("Version string in node_state header is %s", ver_str);
	if (ver_str && !xstrcmp(ver_str, NODE_STATE_VERSION))
		safe_unpack16(&protocol_version, buffer);

	if (!protocol_version || (protocol_version == NO_VAL16)) {
		if (!ignore_state_errors)
			fatal("Can not recover node state, data version incompatible, start with '-i' to ignore this. Warning: using -i will lose the data that can't be recovered.");
		error("*****************************************************");
		error("Can not recover node state, data version incompatible");
		error("*****************************************************");
		xfree(ver_str);
		FREE_NULL_BUFFER(buffer);
		return EFAULT;
	}
	xfree(ver_str);

	safe_unpack_time (&time_stamp, buffer);

	if (_load_cluster_settings(state_only, buffer, protocol_version))
		goto unpack_error;

	if (sackd_mgr_load_state(buffer, protocol_version))
		goto unpack_error;

	xfree(node_old_to_new_map);
	old_node_record_count = 0;
	if (protocol_version >= SLURM_26_05_PROTOCOL_VERSION) {
		safe_unpack32(&old_node_record_count, buffer);
		if (old_node_record_count) {
			node_old_to_new_map =
				xcalloc(old_node_record_count, sizeof(int));
			memset(node_old_to_new_map, -1,
			       old_node_record_count * sizeof(int));
		}
	}

	while (remaining_buf (buffer) > 0) {
		node_record_t *node_state_rec = NULL;
		uint32_t node_state, base_state;
		uint32_t old_inx = NO_VAL;

		if (protocol_version >= SLURM_26_05_PROTOCOL_VERSION)
			safe_unpack32(&old_inx, buffer);

		if (node_record_unpack((void *) &node_state_rec,
				       protocol_version, buffer)) {
			error("failed to unpack node state");
			goto unpack_error;
		}

		node_state = node_state_rec->node_state;
		base_state = node_state & NODE_STATE_BASE;

		/* validity test as possible */
		if ((node_state_rec->cpus == 0) ||
		    (node_state_rec->boards == 0) ||
		    (node_state_rec->tot_sockets == 0) ||
		    (node_state_rec->cores == 0) ||
		    (node_state_rec->threads == 0) ||
		    (base_state  >= NODE_STATE_END)) {
			error("Invalid data for node %s: procs=%u, boards=%u, "
			       "sockets=%u, cores=%u, threads=%u, state=%u",
				node_state_rec->name, node_state_rec->cpus,
				node_state_rec->boards,
				node_state_rec->tot_sockets,
				node_state_rec->cores, node_state_rec->threads,
				node_state);
			error("No more node data will be processed from the checkpoint file");
			purge_node_rec(node_state_rec);
			goto unpack_error;

		}

		/*
		 * When a NodeSet is defined with the same name than
		 * an existing node name found in the state file, and which
		 * might not be in the configuration, then fatal. This can
		 * happen for example when adding a dynamic node, then changing
		 * slurm.conf by adding a NodeSet with the same name than the
		 * dynamic node, and then restarting.
		 */
		if (_validate_nodes_vs_nodeset(node_state_rec->name) !=
		    SLURM_SUCCESS) {
			fatal("This error might happen when names overlap with dynamic nodes. Please rename the NodeSet in slurm.conf.");
		}

		if (node_state & NODE_STATE_DYNAMIC_NORM) {
			/*
			 * Create node record to restore node into.
			 *
			 * cpu_spec_list, core_spec_cnt, port are only restored
			 * for dynamic nodes, otherwise always trust slurm.conf
			 */
			config_record_t *config_ptr =
				config_record_from_node_record(node_state_rec);

			if ((error_code = add_node_record(node_state_rec->name,
							  config_ptr,
							  &node_ptr))) {
				error("%s (%s)",
				      slurm_strerror(error_code),
				      node_state_rec->name);
				error_code = SLURM_SUCCESS;
				list_delete_ptr(config_list, config_ptr);
			} else {
				if (node_state_rec->port) {
					node_ptr->port = node_state_rec->port;
					/*
					 * Get node in conf hash tables with
					 * port. set_node_comm_name() doesn't
					 * add the node with the port.
					 */
					slurm_conf_add_node(node_ptr);
				}
				/*
				 * add_node_record() populates gres_list but we
				 * want to use the gres_list from state.
				 */
				FREE_NULL_LIST(node_ptr->gres_list);
				_queue_consolidate_config_list();
			}
		}

		/* find record and perform update */
		node_ptr = find_node_record (node_state_rec->name);

		/* Record mapping of the old node index to the new node index */
		if (node_ptr && (old_inx != NO_VAL) && node_old_to_new_map &&
		    (old_inx < old_node_record_count))
			node_old_to_new_map[old_inx] = node_ptr->index;

		if (node_ptr == NULL) {
			error ("Node %s has vanished from configuration",
			       node_state_rec->name);
		} else if (state_only &&
			   !(node_state & NODE_STATE_DYNAMIC_NORM)) {
			uint32_t orig_flags;
			if ((IS_NODE_CLOUD(node_ptr) ||
			    (node_state & NODE_STATE_DYNAMIC_FUTURE)) &&
			    node_state_rec->comm_name &&
			    node_state_rec->node_hostname) {
				/* Recover NodeAddr and NodeHostName */
				set_node_comm_name(
					node_ptr,
					node_state_rec->comm_name,
					node_state_rec->node_hostname);
			}
			if (IS_NODE_FUTURE(node_ptr)) {
				/* preserve state for conf FUTURE nodes */
				node_ptr->node_state = node_state;
			} else if (IS_NODE_CLOUD(node_ptr)) {
				/*
				 * Replace FUTURE state with new state (idle),
				 * but preserve recovered state flags
				 * (e.g. POWER*).
				 */
				if ((node_state & NODE_STATE_BASE) ==
				    NODE_STATE_FUTURE) {
					node_state =
						((node_ptr->node_state &
						  NODE_STATE_BASE) |
						 (node_state &
						  NODE_STATE_FLAGS));

					/*
					 * If node was FUTURE, then it wasn't up
					 * so mark it as powered down.
					 */
					node_state |= NODE_STATE_POWERED_DOWN;
				}

				node_ptr->node_state =
					node_state | NODE_STATE_CLOUD;

				/*
				 * Preserve dynamic topology for cloud nodes.
				 * If the node is powered down then keep topo
				 * as configured by current config files to stop
				 * old slurm.conf node topology from persisting.
				 * Ignore topology_orig_str for similar reason
				 * for topology.conf config.
				 */
				if (node_state_rec->topology_str &&
				    !IS_NODE_POWERING_DOWN(node_ptr) &&
				    !IS_NODE_POWERED_DOWN(node_ptr)) {
					xfree(node_ptr->topology_str);
					node_ptr->topology_str =
						node_state_rec->topology_str;
					node_state_rec->topology_str = NULL;
				}

			} else if (IS_NODE_UNKNOWN(node_ptr)) {
				if (base_state == NODE_STATE_DOWN) {
					orig_flags = node_ptr->node_state &
						     NODE_STATE_FLAGS;
					node_ptr->node_state = NODE_STATE_DOWN
						| orig_flags;
				}
				if (node_state & NODE_STATE_DRAIN)
					 node_ptr->node_state |=
						 NODE_STATE_DRAIN;
				if (node_state & NODE_STATE_FAIL)
					node_ptr->node_state |=
						NODE_STATE_FAIL;
				if ((node_state & NODE_STATE_POWERED_DOWN) ||
				    (node_state & NODE_STATE_POWERING_DOWN)) {
					node_ptr->node_state |=
						node_state &
						(NODE_STATE_POWERED_DOWN |
						 NODE_STATE_POWERING_DOWN);
					/* Recover hardware state for powered
					 * down nodes */
					node_ptr->cpus = node_state_rec->cpus;
					node_ptr->boards =
						node_state_rec->boards;
					node_ptr->tot_sockets =
						node_state_rec->tot_sockets;
					node_ptr->cores = node_state_rec->cores;
					node_ptr->tot_cores =
						node_state_rec->tot_cores;
					node_ptr->threads =
						node_state_rec->threads;
					node_ptr->real_memory =
						node_state_rec->real_memory;
					node_ptr->res_cores_per_gpu =
						node_state_rec->
						res_cores_per_gpu;
					node_ptr->tmp_disk =
						node_state_rec->tmp_disk;
				}
				if (node_state & NODE_STATE_MAINT)
					node_ptr->node_state |= NODE_STATE_MAINT;
				if (node_state & NODE_STATE_REBOOT_REQUESTED)
					node_ptr->node_state |=
						NODE_STATE_REBOOT_REQUESTED;
				if (node_state & NODE_STATE_REBOOT_ISSUED)
					node_ptr->node_state |=
						NODE_STATE_REBOOT_ISSUED;
				if (node_state & NODE_STATE_POWERING_UP) {
					node_ptr->node_state |=
						NODE_STATE_POWERING_UP;
				}
			}

			if (!node_ptr->extra) {
				node_ptr->extra = node_state_rec->extra;
				node_state_rec->extra = NULL;
			}

			if (!node_ptr->cert_token) {
				node_ptr->cert_token =
					node_state_rec->cert_token;
				node_state_rec->cert_token = NULL;
			}

			if (!node_ptr->comment) {
				node_ptr->comment = node_state_rec->comment;
				node_state_rec->comment = NULL;
			}

			if (!node_ptr->instance_id) {
				node_ptr->instance_id =
					node_state_rec->instance_id;
				node_state_rec->instance_id = NULL;
			}

			if (!node_ptr->instance_type) {
				node_ptr->instance_type =
					node_state_rec->instance_type;
				node_state_rec->instance_type = NULL;
			}

			if (node_ptr->reason == NULL) {
				node_ptr->reason = node_state_rec->reason;
				node_state_rec->reason = NULL;
				node_ptr->reason_time =
					node_state_rec->reason_time;
				node_ptr->reason_uid =
					node_state_rec->reason_uid;
			}

			xfree(node_ptr->features_act);
			node_ptr->features_act = node_state_rec->features_act;
			node_state_rec->features_act = NULL;
			node_ptr->gres_list = node_state_rec->gres_list;
			node_state_rec->gres_list = NULL;
			node_ptr->gpu_spec_bitmap =
				node_state_rec->gpu_spec_bitmap;
			node_state_rec->gpu_spec_bitmap = NULL;
		} else {
			if ((IS_NODE_CLOUD(node_ptr) ||
			    (node_state & NODE_STATE_DYNAMIC_FUTURE) ||
			    (node_state & NODE_STATE_DYNAMIC_NORM)) &&
			    node_state_rec->comm_name &&
			    node_state_rec->node_hostname) {
				/* Recover NodeAddr and NodeHostName */
				set_node_comm_name(
					node_ptr,
					node_state_rec->comm_name,
					node_state_rec->node_hostname);
			}
			node_ptr->node_state    = node_state;
			xfree(node_ptr->extra);
			node_ptr->extra = node_state_rec->extra;
			node_state_rec->extra = NULL;
			xfree(node_ptr->cert_token);
			node_ptr->cert_token = node_state_rec->cert_token;
			node_state_rec->cert_token = NULL;
			xfree(node_ptr->comment);
			node_ptr->comment = node_state_rec->comment;
			node_state_rec->comment = NULL;
			xfree(node_ptr->instance_id);
			node_ptr->instance_id = node_state_rec->instance_id;
			node_state_rec->instance_id = NULL;
			xfree(node_ptr->instance_type);
			node_ptr->instance_type = node_state_rec->instance_type;
			node_state_rec->instance_type = NULL;
			xfree(node_ptr->parameters);
			node_ptr->parameters = node_state_rec->parameters;
			node_state_rec->parameters = NULL;
			xfree(node_ptr->reason);
			node_ptr->reason = node_state_rec->reason;
			node_state_rec->reason = NULL;
			node_ptr->reason_time = node_state_rec->reason_time;
			node_ptr->reason_uid = node_state_rec->reason_uid;
			xfree(node_ptr->features);
			node_ptr->features = node_state_rec->features;
			node_state_rec->features = NULL;
			xfree(node_ptr->features_act);
			node_ptr->features_act	= node_state_rec->features_act;
			node_state_rec->features_act = NULL;
			xfree(node_ptr->gres);
			node_ptr->gres = node_state_rec->gres;
			node_state_rec->gres = NULL;
			node_ptr->gres_list = node_state_rec->gres_list;
			node_state_rec->gres_list = NULL;
			node_ptr->part_cnt      = 0;
			xfree(node_ptr->part_pptr);
			node_ptr->cpu_bind = node_state_rec->cpu_bind;
			node_ptr->cpus = node_state_rec->cpus;
			node_ptr->boards = node_state_rec->boards;
			node_ptr->tot_sockets = node_state_rec->tot_sockets;
			node_ptr->cores = node_state_rec->cores;
			node_ptr->tot_cores = node_state_rec->tot_cores;
			node_ptr->threads = node_state_rec->threads;
			node_ptr->real_memory = node_state_rec->real_memory;
			node_ptr->res_cores_per_gpu =
				node_state_rec->res_cores_per_gpu;
			node_ptr->gpu_spec_bitmap =
				node_state_rec->gpu_spec_bitmap;
			node_state_rec->gpu_spec_bitmap = NULL;
			node_ptr->tmp_disk = node_state_rec->tmp_disk;
			xfree(node_ptr->mcs_label);
			node_ptr->mcs_label = node_state_rec->mcs_label;
			node_state_rec->mcs_label = NULL;

			/*
			 * Preserve dynamic topology.
			 * If the node is powered down then keep topo
			 * as configured by current config files to stop
			 * old slurm.conf node topology from persisting.
			 * This is required even on state_only=false to prevent
			 * nodes from being completely removed from the topology
			 * if topo is removed from slurm.conf but exists in
			 * topology.conf before restart.
			 */
			if (node_state_rec->topology_str &&
			    !IS_NODE_POWERING_DOWN(node_ptr) &&
			    !IS_NODE_POWERED_DOWN(node_ptr)) {
				xfree(node_ptr->topology_str);
				node_ptr->topology_str =
					node_state_rec->topology_str;
				node_state_rec->topology_str = NULL;
			}
		}

		if (node_ptr) {
			node_cnt++;

			node_ptr->next_state = node_state_rec->next_state;

			if (IS_NODE_DOWN(node_ptr)) {
				if (down_nodes)
					hostlist_push(down_nodes,
						      node_state_rec->name);
				else
					down_nodes = hostlist_create(
							node_state_rec->name);
			}

			if (node_state_rec->resume_after &&
			    (IS_NODE_DOWN(node_ptr) ||
			     IS_NODE_DRAINED(node_ptr)))
				node_ptr->resume_after =
					node_state_rec->resume_after;

			node_ptr->last_response = node_state_rec->last_response;
			node_ptr->boot_req_time = node_state_rec->boot_req_time;
			node_ptr->power_save_req_time =
				node_state_rec->power_save_req_time;

			if (node_state_rec->protocol_version &&
			    (node_state_rec->protocol_version != NO_VAL16))
				node_ptr->protocol_version =
					node_state_rec->protocol_version;
			else
				node_ptr->protocol_version = protocol_version;

			/* Sanity check to make sure we can take a version we
			 * actually understand.
			 */
			if (node_ptr->protocol_version <
			    SLURM_MIN_PROTOCOL_VERSION)
				node_ptr->protocol_version =
					SLURM_MIN_PROTOCOL_VERSION;

			if (!IS_NODE_POWERED_DOWN(node_ptr))
				node_ptr->last_busy = node_state_rec->last_busy;
		}

		purge_node_rec(node_state_rec);
	}

	/*
	 * Check if the ordering/count of nodes saved in state is different than
	 * the current ordering/count of nodes loaded from slurm.conf. This may
	 * change if nodes were added/removed, or if a different rank ordering
	 * is used (e.g. BlockAsNodeRank or SwitchAsNodeRank)
	 */
	is_node_table_changed = false;
	if (node_old_to_new_map &&
	    (old_node_record_count != node_record_count)) {
		is_node_table_changed = true;
	} else if (node_old_to_new_map &&
		   (old_node_record_count == node_record_count)) {
		for (int i = 0; i < old_node_record_count; i++) {
			if (node_old_to_new_map[i] != i) {
				is_node_table_changed = true;
				break;
			}
		}
	}

	/*
	 * If no changes are detected in the node record table ordering/count,
	 * node_old_to_new_map is freed as it provides no useful information.
	 */
	if (!is_node_table_changed) {
		xfree(node_old_to_new_map);
		old_node_record_count = 0;
		debug2("Node record table ordering and size from state file matches what was loaded in from slurm.conf.");
	} else if (get_log_level() >= LOG_LEVEL_DEBUG2) {
		debug2("Node record table ordering and size from state file does not match what was loaded from slurm.conf:");
		debug2("  node_record_count %u -> %d",
		       old_node_record_count, node_record_count);
		for (int i = 0; i < old_node_record_count; i++) {
			int new_inx = node_old_to_new_map[i];

			if (new_inx == -1) {
				debug2("  node at old index %d has been removed",
				       i);
				continue;
			}

			debug2("  node:%-15s old index:%-5d -> new index:%-5d",
			       node_record_table_ptr[new_inx] ?
			       node_record_table_ptr[new_inx]->name : NULL,
			       i, new_inx);
		}
	}

fini:	info("Recovered state of %d nodes", node_cnt);

	if (down_nodes) {
		char *down_host_str = NULL;
		down_host_str = hostlist_ranged_string_xmalloc(down_nodes);
		info("Down nodes: %s", down_host_str);
		xfree(down_host_str);
		hostlist_destroy(down_nodes);
	}

	FREE_NULL_BUFFER(buffer);
	return error_code;

unpack_error:
	if (!ignore_state_errors)
		fatal("Incomplete node data checkpoint file, start with '-i' to ignore this. Warning: using -i will lose the data that can't be recovered.");
	error("Incomplete node data checkpoint file");
	error_code = EFAULT;
	goto fini;
}

/* list_compare_config - compare two entry from the config list based upon
 *	weight, see common/list.h for documentation */
int list_compare_config (void *config_entry1, void *config_entry2)
{
	config_record_t *c1 = *(config_record_t **) config_entry1;
	config_record_t *c2 = *(config_record_t **) config_entry2;

	return slurm_sort_uint32_list_asc(&c1->weight, &c2->weight);
}

static bool _is_dup_config_record(config_record_t *c1, config_record_t *c2)
{
	if (c1 == c2)
		return false; /* This is the same pointer - ignore */

	if ((c1->boards == c2->boards) &&
	    (c1->core_spec_cnt == c2->core_spec_cnt) &&
	    (c1->cores == c2->cores) &&
	    (c1->cpu_bind == c2->cpu_bind) &&
	    (!xstrcmp(c1->cpu_spec_list, c2->cpu_spec_list)) &&
	    (c1->cpus == c2->cpus) &&
	    (!xstrcmp(c1->feature, c2->feature)) &&
	    (!xstrcmp(c1->gres, c2->gres)) &&
	    (!xstrcmp(c1->parameters, c2->parameters)) &&
	    (c1->mem_spec_limit == c2->mem_spec_limit) &&
	    (c1->real_memory == c2->real_memory) &&
	    (c1->res_cores_per_gpu == c2->res_cores_per_gpu) &&
	    (c1->suspend_time == c2->suspend_time) &&
	    (c1->threads == c2->threads) &&
	    (c1->tmp_disk == c2->tmp_disk) &&
	    (c1->tot_sockets == c2->tot_sockets) &&
	    (!xstrcmp(c1->topology_str, c2->topology_str)) &&
	    (!xstrcmp(c1->tres_weights_str, c2->tres_weights_str)) &&
	    (c1->weight == c2->weight)) {
		/* duplicate records */
		return true;
	}

	return false;
}

static void _combine_dup_config_records(config_record_t *curr_rec)
{
	bool changed = false;
	config_record_t *config_ptr;
	list_itr_t *iter;

	iter = list_iterator_create(config_list);
	while ((config_ptr = list_next(iter))) {
		if (!_is_dup_config_record(curr_rec, config_ptr))
			continue;

		changed = true;
		bit_or(curr_rec->node_bitmap, config_ptr->node_bitmap);
		list_delete_item(iter);
	}
	list_iterator_destroy(iter);

	if (!changed)
		return;

	xfree(curr_rec->nodes);
	curr_rec->nodes = bitmap2node_name(curr_rec->node_bitmap);
	debug("Consolidated duplicate config records into %s", curr_rec->nodes);

	/* Update each node_ptr in node_bitmap */
	_update_config_ptr(curr_rec->node_bitmap, curr_rec);
}

static bool _node_is_hidden(node_record_t *node_ptr,
			    pack_node_info_t *pack_info)
{
	int i;

	if ((slurm_conf.private_data & PRIVATE_DATA_NODES) &&
	    (slurm_mcs_get_privatedata() == 1) &&
	    (mcs_g_check_mcs_label(pack_info->uid, node_ptr->mcs_label,
				   false) != 0))
		return true;

	if (!node_ptr->part_cnt)
		return false;

	for (i = 0; i < node_ptr->part_cnt; i++) {
		part_record_t *part_ptr = node_ptr->part_pptr[i];
		/* return false if the node belongs to any visible partition */
		for (int j = 0; pack_info->visible_parts[j]; j++)
			if (pack_info->visible_parts[j] == part_ptr)
				return false;
	}

	return true;
}

static void _free_pack_node_info_members(pack_node_info_t *pack_info)
{
	xfree(pack_info->visible_parts);
}

static bool _determine_if_node_is_hidden(
	node_record_t *node_ptr,
	pack_node_info_t *pack_info,
	uint16_t show_flags,
	bool privileged)
{
	if (!node_ptr)
		return true;

	xassert(node_ptr->magic == NODE_MAGIC);
	xassert(node_ptr->config_ptr->magic == CONFIG_MAGIC);

	/*
	 * We can't avoid packing node records without breaking
	 * the node index pointers. So pack a node with a name
	 * of NULL and let the caller deal with it.
	 */
	if (!(show_flags & SHOW_ALL) &&
	    !privileged &&
	    (_node_is_hidden(node_ptr, pack_info)))
		return true;
	else if (IS_NODE_FUTURE(node_ptr) &&
		 (!(show_flags & SHOW_FUTURE)))
		return true;
	else if (!node_ptr->name || (node_ptr->name[0] == '\0'))
		return true;

	return false;
}

/*
 * pack_all_nodes - dump all configuration and node information for all nodes
 *	in machine independent form (for network transmission)
 * IN show_flags - node filtering options
 * IN uid - uid of user making request (for partition filtering)
 * IN protocol_version - slurm protocol version of client
 * OUT buffer
 * global: node_record_table_ptr - pointer to global node table
 * NOTE: change slurm_load_node() in api/node_info.c when data format changes
 */
extern buf_t *pack_all_nodes(uint16_t show_flags, uid_t uid,
			     uint16_t protocol_version)
{
	int inx;
	uint32_t nodes_packed, tmp_offset;
	buf_t *buffer;
	time_t now = time(NULL);
	bool privileged = validate_operator(uid);
	pack_node_info_t pack_info = {
		.uid = uid,
		.visible_parts = build_visible_parts(uid, privileged)
	};

	xassert(verify_lock(CONF_LOCK, READ_LOCK));
	xassert(verify_lock(PART_LOCK, READ_LOCK));

	buffer = init_buf(BUF_SIZE * 16);
	nodes_packed = 0;

	if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) {
		bitstr_t *hidden_nodes = bit_alloc(node_record_count);
		uint32_t pack_bitmap_offset;
		bool repack_hidden = false;

		/* write header: count, time, hidden node bitmap */
		pack32(nodes_packed, buffer);
		pack_time(now, buffer);
		pack_bitmap_offset = get_buf_offset(buffer);
		pack_bit_str_hex(hidden_nodes, buffer);

		/* write node records */
		for (inx = 0; inx < node_record_count; inx++) {
			if (_determine_if_node_is_hidden(
				    node_record_table_ptr[inx],
				    &pack_info,
				    show_flags,
				    privileged)) {
				bit_set(hidden_nodes, inx);
				repack_hidden = true;
			} else {
				_pack_node(node_record_table_ptr[inx],
					   buffer, protocol_version,
					   show_flags);
			}
			nodes_packed++;
		}

		if (repack_hidden) {
			tmp_offset = get_buf_offset(buffer);
			set_buf_offset(buffer, pack_bitmap_offset);
			pack_bit_str_hex(hidden_nodes, buffer);
			set_buf_offset(buffer, tmp_offset);
		}
		FREE_NULL_BITMAP(hidden_nodes);
	} else {
		error("%s: protocol_version %hu not supported",
		      __func__, protocol_version);
	}

	tmp_offset = get_buf_offset(buffer);
	set_buf_offset(buffer, 0);
	pack32(nodes_packed, buffer);
	set_buf_offset(buffer, tmp_offset);

	_free_pack_node_info_members(&pack_info);

	return buffer;
}

/*
 * pack_one_node - dump all configuration and node information for one node
 *	in machine independent form (for network transmission)
 * IN show_flags - node filtering options
 * IN uid - uid of user making request (for partition filtering)
 * IN node_name - name of node for which information is desired,
 *		  use first node if name is NULL
 * IN protocol_version - slurm protocol version of client
 * OUT buffer
 * global: node_record_table_ptr - pointer to global node table
 * NOTE: change slurm_load_node() in api/node_info.c when data format changes
 */
extern buf_t *pack_one_node(uint16_t show_flags, uid_t uid, char *node_name,
			    uint16_t protocol_version)
{
	uint32_t nodes_packed, tmp_offset;
	buf_t *buffer;
	time_t now = time(NULL);
	node_record_t *node_ptr;
	bool privileged = validate_operator(uid);
	pack_node_info_t pack_info = {
		.uid = uid,
		.visible_parts = build_visible_parts(uid, privileged)
	};

	xassert(verify_lock(CONF_LOCK, READ_LOCK));
	xassert(verify_lock(PART_LOCK, READ_LOCK));

	buffer = init_buf(BUF_SIZE);
	nodes_packed = 0;

	if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) {
		/* write header: count and time */
		pack32(nodes_packed, buffer);
		pack_time(now, buffer);
		/* Mirror _unpack_node_info_msg */
		pack_bit_str_hex(NULL, buffer);

		/* write node records */
		if (node_name)
			node_ptr = find_node_record(node_name);
		else
			node_ptr = node_record_table_ptr[0];
		if (node_ptr) {
			if (!_determine_if_node_is_hidden(
				    node_ptr,
				    &pack_info,
				    show_flags,
				    privileged)) {
				_pack_node(node_ptr, buffer, protocol_version,
					   show_flags);
				nodes_packed++;
			}
		}
	} else {
		error("%s: protocol_version %hu not supported",
		      __func__, protocol_version);
	}

	tmp_offset = get_buf_offset(buffer);
	set_buf_offset(buffer, 0);
	pack32(nodes_packed, buffer);
	set_buf_offset(buffer, tmp_offset);

	_free_pack_node_info_members(&pack_info);

	return buffer;
}

/*
 * _pack_node - dump all configuration information about a specific node in
 *	machine independent form (for network transmission)
 * IN dump_node_ptr - pointer to node for which information is requested
 * IN/OUT buffer - buffer where data is placed, pointers automatically updated
 * IN protocol_version - slurm protocol version of client
 * IN show_flags -
 * NOTE: if you make any changes here be sure to make the corresponding changes
 * 	to _unpack_node_info_members() in common/slurm_protocol_pack.c
 */
static void _pack_node(node_record_t *dump_node_ptr, buf_t *buffer,
		       uint16_t protocol_version, uint16_t show_flags)
{
	char *gres_drain = NULL, *gres_used = NULL;
	node_select_stats_t *dump_node_stats;

	xassert(verify_lock(CONF_LOCK, READ_LOCK));
	xassert(verify_lock(SELECT_NODE_LOCK, READ_LOCK));

	dump_node_stats = node_select_stats_array[dump_node_ptr->index];

	if (protocol_version >= SLURM_26_05_PROTOCOL_VERSION) {
		packstr(dump_node_ptr->name, buffer);
		packstr(dump_node_ptr->node_hostname, buffer);
		packstr(dump_node_ptr->comm_name, buffer);
		packstr(dump_node_ptr->bcast_address, buffer);

		/* turns into cert_flags field on remote */
		if (dump_node_ptr->cert_token) {
			pack16(NODE_CERT_TOKEN_SET, buffer);
		} else {
			pack16(0, buffer);
		}

		pack16(dump_node_ptr->port, buffer);
		pack32(dump_node_ptr->next_state, buffer);
		pack32(dump_node_ptr->node_state, buffer);
		packstr(dump_node_ptr->version, buffer);

		/* Only data from config_record used for scheduling */
		pack16(dump_node_ptr->config_ptr->cpus, buffer);
		pack16(dump_node_ptr->config_ptr->boards, buffer);
		pack16(dump_node_ptr->config_ptr->tot_sockets, buffer);
		pack16(dump_node_ptr->config_ptr->cores, buffer);
		pack16(dump_node_ptr->config_ptr->threads, buffer);
		pack64(dump_node_ptr->config_ptr->real_memory, buffer);
		pack32(dump_node_ptr->config_ptr->tmp_disk, buffer);

		packstr(dump_node_ptr->gpu_spec, buffer);
		packstr(dump_node_ptr->mcs_label, buffer);
		pack32(dump_node_ptr->owner, buffer);
		pack16(dump_node_ptr->core_spec_cnt, buffer);
		pack32(dump_node_ptr->cpu_bind, buffer);
		pack64(dump_node_ptr->mem_spec_limit, buffer);
		packstr(dump_node_ptr->cpu_spec_list, buffer);
		pack16(dump_node_ptr->cpus_efctv, buffer);

		pack32(dump_node_ptr->cpu_load, buffer);
		pack64(dump_node_ptr->free_mem, buffer);
		pack32(dump_node_ptr->config_ptr->weight, buffer);
		pack16(dump_node_ptr->res_cores_per_gpu, buffer);
		pack32(dump_node_ptr->reason_uid, buffer);
		pack32(dump_node_ptr->suspend_time, buffer);

		pack_time(dump_node_ptr->boot_time, buffer);
		pack_time(dump_node_ptr->last_busy, buffer);
		pack_time(dump_node_ptr->reason_time, buffer);
		pack_time(dump_node_ptr->resume_after, buffer);
		pack_time(dump_node_ptr->slurmd_start_time, buffer);
		pack_time(dump_node_ptr->cert_last_renewal, buffer);

		pack16(dump_node_stats->alloc_cpus, buffer);
		pack64(dump_node_stats->alloc_memory, buffer);
		packstr(dump_node_stats->alloc_tres_fmt_str, buffer);

		packstr(dump_node_ptr->arch, buffer);
		packstr(dump_node_ptr->features, buffer);
		packstr(dump_node_ptr->features_act, buffer);
		if (dump_node_ptr->gres)
			packstr(dump_node_ptr->gres, buffer);
		else
			packstr(dump_node_ptr->config_ptr->gres, buffer);

		/* Gathering GRES details is slow, so don't by default */
		if (show_flags & SHOW_DETAIL) {
			gres_drain =
				gres_get_node_drain(dump_node_ptr->gres_list);
			gres_used =
				gres_get_node_used(dump_node_ptr->gres_list);
		}
		packstr(gres_drain, buffer);
		packstr(gres_used, buffer);
		xfree(gres_drain);
		xfree(gres_used);

		packstr(dump_node_ptr->os, buffer);
		packstr(dump_node_ptr->comment, buffer);
		packstr(dump_node_ptr->extra, buffer);
		packstr(dump_node_ptr->instance_id, buffer);
		packstr(dump_node_ptr->instance_type, buffer);
		packstr(dump_node_ptr->parameters, buffer);
		packstr(dump_node_ptr->reason, buffer);
		acct_gather_energy_pack(dump_node_ptr->energy, buffer,
					protocol_version);

		packstr(dump_node_ptr->tres_fmt_str, buffer);
		packstr(dump_node_ptr->resv_name, buffer);
		packstr(dump_node_ptr->topology_str, buffer);
	} else if (protocol_version >= SLURM_25_11_PROTOCOL_VERSION) {
		packstr(dump_node_ptr->name, buffer);
		packstr(dump_node_ptr->node_hostname, buffer);
		packstr(dump_node_ptr->comm_name, buffer);
		packstr(dump_node_ptr->bcast_address, buffer);

		/* turns into cert_flags field on remote */
		if (dump_node_ptr->cert_token) {
			pack16(NODE_CERT_TOKEN_SET, buffer);
		} else {
			pack16(0, buffer);
		}

		pack16(dump_node_ptr->port, buffer);
		pack32(dump_node_ptr->next_state, buffer);
		pack32(dump_node_ptr->node_state, buffer);
		packstr(dump_node_ptr->version, buffer);

		/* Only data from config_record used for scheduling */
		pack16(dump_node_ptr->config_ptr->cpus, buffer);
		pack16(dump_node_ptr->config_ptr->boards, buffer);
		pack16(dump_node_ptr->config_ptr->tot_sockets, buffer);
		pack16(dump_node_ptr->config_ptr->cores, buffer);
		pack16(dump_node_ptr->config_ptr->threads, buffer);
		pack64(dump_node_ptr->config_ptr->real_memory, buffer);
		pack32(dump_node_ptr->config_ptr->tmp_disk, buffer);

		packstr(dump_node_ptr->gpu_spec, buffer);
		packstr(dump_node_ptr->mcs_label, buffer);
		pack32(dump_node_ptr->owner, buffer);
		pack16(dump_node_ptr->core_spec_cnt, buffer);
		pack32(dump_node_ptr->cpu_bind, buffer);
		pack64(dump_node_ptr->mem_spec_limit, buffer);
		packstr(dump_node_ptr->cpu_spec_list, buffer);
		pack16(dump_node_ptr->cpus_efctv, buffer);

		pack32(dump_node_ptr->cpu_load, buffer);
		pack64(dump_node_ptr->free_mem, buffer);
		pack32(dump_node_ptr->config_ptr->weight, buffer);
		pack16(dump_node_ptr->res_cores_per_gpu, buffer);
		pack32(dump_node_ptr->reason_uid, buffer);

		pack_time(dump_node_ptr->boot_time, buffer);
		pack_time(dump_node_ptr->last_busy, buffer);
		pack_time(dump_node_ptr->reason_time, buffer);
		pack_time(dump_node_ptr->resume_after, buffer);
		pack_time(dump_node_ptr->slurmd_start_time, buffer);
		pack_time(dump_node_ptr->cert_last_renewal, buffer);

		pack16(dump_node_stats->alloc_cpus, buffer);
		pack64(dump_node_stats->alloc_memory, buffer);
		packstr(dump_node_stats->alloc_tres_fmt_str, buffer);

		packstr(dump_node_ptr->arch, buffer);
		packstr(dump_node_ptr->features, buffer);
		packstr(dump_node_ptr->features_act, buffer);
		if (dump_node_ptr->gres)
			packstr(dump_node_ptr->gres, buffer);
		else
			packstr(dump_node_ptr->config_ptr->gres, buffer);

		/* Gathering GRES details is slow, so don't by default */
		if (show_flags & SHOW_DETAIL) {
			gres_drain =
				gres_get_node_drain(dump_node_ptr->gres_list);
			gres_used =
				gres_get_node_used(dump_node_ptr->gres_list);
		}
		packstr(gres_drain, buffer);
		packstr(gres_used, buffer);
		xfree(gres_drain);
		xfree(gres_used);

		packstr(dump_node_ptr->os, buffer);
		packstr(dump_node_ptr->comment, buffer);
		packstr(dump_node_ptr->extra, buffer);
		packstr(dump_node_ptr->instance_id, buffer);
		packstr(dump_node_ptr->instance_type, buffer);
		packstr(dump_node_ptr->parameters, buffer);
		packstr(dump_node_ptr->reason, buffer);
		acct_gather_energy_pack(dump_node_ptr->energy, buffer,
					protocol_version);

		packstr(dump_node_ptr->tres_fmt_str, buffer);
		packstr(dump_node_ptr->resv_name, buffer);
		packstr(dump_node_ptr->topology_str, buffer);
	} else if (protocol_version >= SLURM_25_05_PROTOCOL_VERSION) {
		packstr(dump_node_ptr->name, buffer);
		packstr(dump_node_ptr->node_hostname, buffer);
		packstr(dump_node_ptr->comm_name, buffer);
		packstr(dump_node_ptr->bcast_address, buffer);

		/* turns into cert_flags field on remote */
		if (dump_node_ptr->cert_token) {
			pack16(NODE_CERT_TOKEN_SET, buffer);
		} else {
			pack16(0, buffer);
		}

		pack16(dump_node_ptr->port, buffer);
		pack32(dump_node_ptr->next_state, buffer);
		pack32(dump_node_ptr->node_state, buffer);
		packstr(dump_node_ptr->version, buffer);

		/* Only data from config_record used for scheduling */
		pack16(dump_node_ptr->config_ptr->cpus, buffer);
		pack16(dump_node_ptr->config_ptr->boards, buffer);
		pack16(dump_node_ptr->config_ptr->tot_sockets, buffer);
		pack16(dump_node_ptr->config_ptr->cores, buffer);
		pack16(dump_node_ptr->config_ptr->threads, buffer);
		pack64(dump_node_ptr->config_ptr->real_memory, buffer);
		pack32(dump_node_ptr->config_ptr->tmp_disk, buffer);

		packstr(dump_node_ptr->gpu_spec, buffer);
		packstr(dump_node_ptr->mcs_label, buffer);
		pack32(dump_node_ptr->owner, buffer);
		pack16(dump_node_ptr->core_spec_cnt, buffer);
		pack32(dump_node_ptr->cpu_bind, buffer);
		pack64(dump_node_ptr->mem_spec_limit, buffer);
		packstr(dump_node_ptr->cpu_spec_list, buffer);
		pack16(dump_node_ptr->cpus_efctv, buffer);

		pack32(dump_node_ptr->cpu_load, buffer);
		pack64(dump_node_ptr->free_mem, buffer);
		pack32(dump_node_ptr->config_ptr->weight, buffer);
		pack16(dump_node_ptr->res_cores_per_gpu, buffer);
		pack32(dump_node_ptr->reason_uid, buffer);

		pack_time(dump_node_ptr->boot_time, buffer);
		pack_time(dump_node_ptr->last_busy, buffer);
		pack_time(dump_node_ptr->reason_time, buffer);
		pack_time(dump_node_ptr->resume_after, buffer);
		pack_time(dump_node_ptr->slurmd_start_time, buffer);
		pack_time(dump_node_ptr->cert_last_renewal, buffer);

		pack16(dump_node_stats->alloc_cpus, buffer);
		pack64(dump_node_stats->alloc_memory, buffer);
		packstr(dump_node_stats->alloc_tres_fmt_str, buffer);

		packstr(dump_node_ptr->arch, buffer);
		packstr(dump_node_ptr->features, buffer);
		packstr(dump_node_ptr->features_act, buffer);
		if (dump_node_ptr->gres)
			packstr(dump_node_ptr->gres, buffer);
		else
			packstr(dump_node_ptr->config_ptr->gres, buffer);

		/* Gathering GRES details is slow, so don't by default */
		if (show_flags & SHOW_DETAIL) {
			gres_drain =
				gres_get_node_drain(dump_node_ptr->gres_list);
			gres_used =
				gres_get_node_used(dump_node_ptr->gres_list);
		}
		packstr(gres_drain, buffer);
		packstr(gres_used, buffer);
		xfree(gres_drain);
		xfree(gres_used);

		packstr(dump_node_ptr->os, buffer);
		packstr(dump_node_ptr->comment, buffer);
		packstr(dump_node_ptr->extra, buffer);
		packstr(dump_node_ptr->instance_id, buffer);
		packstr(dump_node_ptr->instance_type, buffer);
		packstr(dump_node_ptr->reason, buffer);
		acct_gather_energy_pack(dump_node_ptr->energy, buffer,
					protocol_version);

		packstr(dump_node_ptr->tres_fmt_str, buffer);
		packstr(dump_node_ptr->resv_name, buffer);
		packstr(dump_node_ptr->topology_str, buffer);
	} else if (protocol_version >= SLURM_MIN_PROTOCOL_VERSION) {
		packstr(dump_node_ptr->name, buffer);
		packstr(dump_node_ptr->node_hostname, buffer);
		packstr(dump_node_ptr->comm_name, buffer);
		packstr(dump_node_ptr->bcast_address, buffer);
		pack16(dump_node_ptr->port, buffer);
		pack32(dump_node_ptr->next_state, buffer);
		pack32(dump_node_ptr->node_state, buffer);
		packstr(dump_node_ptr->version, buffer);

		/* Only data from config_record used for scheduling */
		pack16(dump_node_ptr->config_ptr->cpus, buffer);
		pack16(dump_node_ptr->config_ptr->boards, buffer);
		pack16(dump_node_ptr->config_ptr->tot_sockets, buffer);
		pack16(dump_node_ptr->config_ptr->cores, buffer);
		pack16(dump_node_ptr->config_ptr->threads, buffer);
		pack64(dump_node_ptr->config_ptr->real_memory, buffer);
		pack32(dump_node_ptr->config_ptr->tmp_disk, buffer);

		packstr(dump_node_ptr->gpu_spec, buffer);
		packstr(dump_node_ptr->mcs_label, buffer);
		pack32(dump_node_ptr->owner, buffer);
		pack16(dump_node_ptr->core_spec_cnt, buffer);
		pack32(dump_node_ptr->cpu_bind, buffer);
		pack64(dump_node_ptr->mem_spec_limit, buffer);
		packstr(dump_node_ptr->cpu_spec_list, buffer);
		pack16(dump_node_ptr->cpus_efctv, buffer);

		pack32(dump_node_ptr->cpu_load, buffer);
		pack64(dump_node_ptr->free_mem, buffer);
		pack32(dump_node_ptr->config_ptr->weight, buffer);
		pack16(dump_node_ptr->res_cores_per_gpu, buffer);
		pack32(dump_node_ptr->reason_uid, buffer);

		pack_time(dump_node_ptr->boot_time, buffer);
		pack_time(dump_node_ptr->last_busy, buffer);
		pack_time(dump_node_ptr->reason_time, buffer);
		pack_time(dump_node_ptr->resume_after, buffer);
		pack_time(dump_node_ptr->slurmd_start_time, buffer);

		select_plugin_id_pack(buffer);
		pack16(dump_node_stats->alloc_cpus, buffer);
		pack64(dump_node_stats->alloc_memory, buffer);
		packstr(dump_node_stats->alloc_tres_fmt_str, buffer);
		packdouble(0, buffer); /* was alloc_tres_weighted */

		packstr(dump_node_ptr->arch, buffer);
		packstr(dump_node_ptr->features, buffer);
		packstr(dump_node_ptr->features_act, buffer);
		if (dump_node_ptr->gres)
			packstr(dump_node_ptr->gres, buffer);
		else
			packstr(dump_node_ptr->config_ptr->gres, buffer);

		/* Gathering GRES details is slow, so don't by default */
		if (show_flags & SHOW_DETAIL) {
			gres_drain =
				gres_get_node_drain(dump_node_ptr->gres_list);
			gres_used  =
				gres_get_node_used(dump_node_ptr->gres_list);
		}
		packstr(gres_drain, buffer);
		packstr(gres_used, buffer);
		xfree(gres_drain);
		xfree(gres_used);

		packstr(dump_node_ptr->os, buffer);
		packstr(dump_node_ptr->comment, buffer);
		packstr(dump_node_ptr->extra, buffer);
		packstr(dump_node_ptr->instance_id, buffer);
		packstr(dump_node_ptr->instance_type, buffer);
		packstr(dump_node_ptr->reason, buffer);
		acct_gather_energy_pack(dump_node_ptr->energy, buffer,
					protocol_version);

		packstr(dump_node_ptr->tres_fmt_str, buffer);
		packstr(dump_node_ptr->resv_name, buffer);
	} else {
		error("_pack_node: protocol_version "
		      "%hu not supported", protocol_version);
	}
}

/* Return "true" if a node's state is already "new_state". This is more
 * complex than simply comparing the state values due to flags (e.g.
 * A node might be DOWN + NO_RESPOND or IDLE + DRAIN) */
static bool _equivalent_node_state(node_record_t *node_ptr, uint32_t new_state)
{
	if (new_state == NO_VAL)	/* No change */
		return true;
	if ((new_state == NODE_STATE_DOWN)  && IS_NODE_DOWN(node_ptr))
		return true;
	if ((new_state == NODE_STATE_DRAIN) && IS_NODE_DRAIN(node_ptr))
		return true;
	if ((new_state == NODE_STATE_FAIL)  && IS_NODE_FAIL(node_ptr))
		return true;
	/* Other states might be added here */
	return false;
}

/* Confirm that the selected ActiveFeatures are a subset of AvailableFeatures */
static bool _valid_features_act(char *features_act, char *features)
{
	bool valid_subset = true;
	char *tmp_act, *last_act = NULL, *tok_act;
	char *tmp_avail, *last_avail = NULL, *tok_avail;

	if (!features_act || (features_act[0] == '\0'))
		return true;
	if (!features || (features[0] == '\0'))
		return false;

	tmp_act = xstrdup(features_act);
        tok_act = strtok_r(tmp_act, ",", &last_act);
        while (tok_act) {
		last_avail = NULL;
		tmp_avail = xstrdup(features);
		tok_avail = strtok_r(tmp_avail, ",", &last_avail);
		while (tok_avail) {
			if (!xstrcmp(tok_act, tok_avail))
				break;
		        tok_avail = strtok_r(NULL, ",", &last_avail);
		}
		xfree(tmp_avail);
		if (!tok_avail) {	/* No match found */
			valid_subset = false;
			break;
		}
                tok_act = strtok_r(NULL, ",", &last_act);
	}
	xfree(tmp_act);

	return valid_subset;
}

/*
 * Validate that reported active changeable features are a superset of the
 * current active changeable features in the controller.
 *
 * If the node doesn't report any features then don't do any validation.
 */
static bool _valid_reported_active_features(const char *reg_active_features,
					    const char *node_active_features)
{
	char *tok, *saveptr;
	char *tmp_node_act, *tmp_reg_act;
	list_t *changeable_list = NULL;
	bool valid = true;

	if (!node_active_features || !reg_active_features)
		return true;

	tmp_reg_act = xstrdup(reg_active_features);
	for (tok = strtok_r(tmp_reg_act, ",", &saveptr);
	     tok;
	     tok = strtok_r(NULL, ",", &saveptr)) {

		if (!node_features_g_changeable_feature(tok))
			continue;

		if (!changeable_list)
			changeable_list = list_create(NULL);
		list_append(changeable_list, tok);
	}

	/*
	 * The node's current active changeable features should be a subset of
	 * the changeable features in the registration message.
	 */
	if (changeable_list && list_count(changeable_list)) {
		tmp_node_act = xstrdup(node_active_features);
		for (tok = strtok_r(tmp_node_act, ",", &saveptr);
		     tok;
		     tok = strtok_r(NULL, ",", &saveptr)) {
			if (node_features_g_changeable_feature(tok) &&
			    !list_delete_all(changeable_list,
					     slurm_find_char_in_list,
					     tok)) {
				/* feature not in current active list */
				valid = false;
				break;
			}
		}
		xfree(tmp_node_act);
	}

	FREE_NULL_LIST(changeable_list);
	xfree(tmp_reg_act);

	return valid;
}

/*
 * Return a new string containing containing only changeable features.
 */
static char *_node_changeable_features(char *all_features)
{
	char *tmp_features;
	char *tok, *saveptr;
	char *changeable_features = NULL;

	tmp_features = xstrdup(all_features);
	for (tok = strtok_r(tmp_features, ",", &saveptr);
	     tok;
	     tok = strtok_r(NULL, ",", &saveptr)) {

		if (!node_features_g_changeable_feature(tok))
			continue;

		xstrfmtcat(changeable_features, "%s%s",
			   changeable_features ? "," : "",
			   tok);
	}
	xfree(tmp_features);

	return changeable_features;
}

static void _undo_reboot_asap(node_record_t *node_ptr)
{
	if (IS_NODE_IDLE(node_ptr) || IS_NODE_ALLOCATED(node_ptr))
		bit_set(avail_node_bitmap, node_ptr->index);
	node_ptr->node_state &= (~NODE_STATE_DRAIN);
	xfree(node_ptr->reason);
}

static void _require_node_reg(node_record_t *node_ptr)
{
	if (IS_NODE_EXTERNAL(node_ptr))
		return;
	node_ptr->node_state |= NODE_STATE_NO_RESPOND;
	node_ptr->last_response = time(NULL);
	node_ptr->boot_time = 0;
	ping_nodes_now = true;
}

int update_node(update_node_msg_t *update_node_msg, uid_t auth_uid)
{
	int error_code = 0, node_cnt;
	node_record_t *node_ptr = NULL;
	char *this_node_name = NULL, *tmp_feature, *orig_features_act = NULL;
	hostlist_t *host_list = NULL, *hostaddr_list = NULL;
	hostlist_t *hostname_list = NULL;
	hostlist_t *instance_id_list = NULL;
	hostlist_t *instance_type_list = NULL;
	char *instance_id_broadcast = NULL;
	char *instance_type_broadcast = NULL;
	uint32_t base_state = 0, node_flags, state_val, resume_after = NO_VAL;
	time_t now = time(NULL);
	bool uniq = true;

	if (update_node_msg->node_names == NULL ) {
		info("%s: invalid node name", __func__);
		error_code = ESLURM_INVALID_NODE_NAME;
		goto end;
	}

	if (update_node_msg->node_addr || update_node_msg->node_hostname ||
	    (update_node_msg->instance_id && update_node_msg->instance_id[0]) ||
	    (update_node_msg->instance_type &&
	     update_node_msg->instance_type[0]))
		uniq = false;

	if (!(host_list = nodespec_to_hostlist(update_node_msg->node_names,
					       uniq, NULL))) {
		error_code = ESLURM_INVALID_NODE_NAME;
		goto end;
	}

	if (!(node_cnt = hostlist_count(host_list))) {
		info("%s: expansion of node specification '%s' resulted in zero nodes",
		     __func__, update_node_msg->node_names);
		error_code = ESLURM_INVALID_NODE_NAME;
		goto end;
	}

	if (update_node_msg->node_addr) {
		hostaddr_list = hostlist_create(update_node_msg->node_addr);
		if (hostaddr_list == NULL) {
			info("update_node: hostlist_create error on %s: %m",
			     update_node_msg->node_addr);
			error_code = ESLURM_INVALID_NODE_NAME;
			goto end;
		}
		if (node_cnt != hostlist_count(hostaddr_list)) {
			info("update_node: nodecount mismatch");
			error_code = ESLURM_INVALID_NODE_NAME;
			goto end;
		}
	}

	if (update_node_msg->node_hostname) {
		hostname_list = hostlist_create(update_node_msg->node_hostname);
		if (hostname_list == NULL) {
			info("update_node: hostlist_create error on %s: %m",
			     update_node_msg->node_hostname);
			error_code = ESLURM_INVALID_NODE_NAME;
			goto end;
		}
		if (node_cnt != hostlist_count(hostname_list)) {
			info("update_node: nodecount mismatch");
			error_code = ESLURM_INVALID_NODE_NAME;
			goto end;
		}
	}

	if (update_node_msg->instance_id && update_node_msg->instance_id[0]) {
		instance_id_list =
			hostlist_create(update_node_msg->instance_id);
		if (instance_id_list == NULL) {
			info("update_node: hostlist_create error on %s: %m",
			     update_node_msg->instance_id);
			error_code = ESLURM_INVALID_NODE_NAME;
			goto end;
		}
		if ((hostlist_count(instance_id_list) != 1) &&
		    (node_cnt != hostlist_count(instance_id_list))) {
			info("update_node: nodecount mismatch");
			error_code = ESLURM_INVALID_NODE_NAME;
			goto end;
		}
	}

	if (update_node_msg->instance_type &&
	    update_node_msg->instance_type[0]) {
		instance_type_list =
			hostlist_create(update_node_msg->instance_type);
		if (instance_type_list == NULL) {
			info("update_node: hostlist_create error on %s: %m",
			     update_node_msg->instance_type);
			error_code = ESLURM_INVALID_NODE_NAME;
			goto end;
		}
		if ((hostlist_count(instance_type_list) != 1) &&
		    (node_cnt != hostlist_count(instance_type_list))) {
			info("update_node: nodecount mismatch");
			error_code = ESLURM_INVALID_NODE_NAME;
			goto end;
		}
	}

	if ((max_powered_nodes != NO_VAL) &&
	    (update_node_msg->node_state != NO_VAL) &&
	    (update_node_msg->node_state & NODE_STATE_POWER_UP)) {
		bitstr_t *bmp = NULL;
		int count;

		if (hostlist2bitmap(host_list, false, &bmp)) {
			info("update_node: hostlist2bitmap failed");
			FREE_NULL_BITMAP(bmp);
			error_code = ESLURM_INVALID_NODE_NAME;
			goto end;
		}
		bit_or(bmp, power_up_node_bitmap);
		count = bit_set_count(bmp);
		FREE_NULL_BITMAP(bmp);
		if (count > max_powered_nodes) {
			error("update_node: Cannot power up more nodes due to MaxPoweredUpNodes=%d",
			      max_powered_nodes);
			error_code = ESLURM_MAX_POWERED_NODES;
			goto end;
		}
		log_flag(POWER, "powered nodes good %d", count);
	}

	/* Validate specified power action or default action for type */
	if (((update_node_msg->node_state != NO_VAL) &&
	     (update_node_msg->node_state &
	      (NODE_STATE_POWER_UP | NODE_STATE_POWER_DOWN))) ||
	    update_node_msg->power_action_name) {
		power_action_type_t type = POWER_ACTION_NONE;

		if (update_node_msg->node_state & NODE_STATE_POWER_UP)
			type = POWER_ACTION_RESUME;
		else if (update_node_msg->node_state & NODE_STATE_POWER_DOWN)
			type = POWER_ACTION_SUSPEND;

		if (!power_save_valid_action_default(
			    type, update_node_msg->power_action_name)) {
			error("Invalid power action %s for nodes %s",
			      update_node_msg->power_action_name ?
			      update_node_msg->power_action_name : "(default)",
			      update_node_msg->node_names);
			error_code = ESLURM_INVALID_POWER_ACTION;
			goto end;
		}
	}

	/*
	 * If a single instance_id/instance_type is specified for multiple
	 * nodes, broadcast the value to all nodes.
	 */
	if (instance_id_list && (hostlist_count(instance_id_list) == 1)) {
		instance_id_broadcast = hostlist_shift(instance_id_list);
		FREE_NULL_HOSTLIST(instance_id_list);
	}
	if (instance_type_list && (hostlist_count(instance_type_list) == 1)) {
		instance_type_broadcast = hostlist_shift(instance_type_list);
		FREE_NULL_HOSTLIST(instance_type_list);
	}

	while ( (this_node_name = hostlist_shift (host_list)) ) {
		int err_code = 0;
		bool acct_updated = false;
		bool update_db = false;

		node_ptr = find_node_record (this_node_name);
		if (node_ptr == NULL) {
			error ("update_node: node %s does not exist",
				this_node_name);
			error_code = ESLURM_INVALID_NODE_NAME;
			free (this_node_name);
			break;
		}

		if (hostaddr_list) {
			char *this_addr = hostlist_shift(hostaddr_list);
			xfree(node_ptr->comm_name);
			node_ptr->comm_name = xstrdup(this_addr);
			free(this_addr);
		}
		if (hostname_list) {
			char *this_hostname = hostlist_shift(hostname_list);
			xfree(node_ptr->node_hostname);
			node_ptr->node_hostname = xstrdup(this_hostname);
			free(this_hostname);
		}
		if (hostaddr_list || hostname_list) {
			/* This updates the lookup table addresses */
			slurm_reset_alias(node_ptr->name, node_ptr->comm_name,
					  node_ptr->node_hostname);
		}

		if (update_node_msg->cert_token) {
			xfree(node_ptr->cert_token);
			if (update_node_msg->cert_token[0])
				node_ptr->cert_token = xstrdup(
					update_node_msg->cert_token);
		}

		if (update_node_msg->cpu_bind) {
			char tmp_str[128];
			slurm_sprint_cpu_bind_type(tmp_str,
						   update_node_msg->cpu_bind);
			info("update_node: setting CpuBind to %s for node %s",
			     tmp_str, this_node_name);
			if (update_node_msg->cpu_bind == CPU_BIND_OFF)
				node_ptr->cpu_bind = 0;
			else
				node_ptr->cpu_bind = update_node_msg->cpu_bind;
		}

		if (update_node_msg->features || update_node_msg->features_act) {
			char *features_act = NULL, *features_avail = NULL;

			if (update_node_msg->features_act)
				features_act = update_node_msg->features_act;
			else
				features_act = node_ptr->features_act;

			if (update_node_msg->features)
				features_avail = update_node_msg->features;
			else
				features_avail = node_ptr->features;
			if (!_valid_features_act(features_act, features_avail)){
				info("%s: Invalid ActiveFeatures (\'%s\' not subset of \'%s\' on node %s)",
				     __func__, features_act, features_avail,
				     node_ptr->name);
				error_code = ESLURM_ACTIVE_FEATURE_NOT_SUBSET;
				xfree(update_node_msg->features);
				xfree(update_node_msg->features_act);
			}
		}

		if (update_node_msg->features_act) {
			if (node_ptr->features_act)
				orig_features_act =
					xstrdup(node_ptr->features_act);
			else
				orig_features_act = xstrdup(node_ptr->features);
		}
		if (update_node_msg->features) {
			if (!update_node_msg->features_act &&
			    (node_features_g_count() == 0)) {
				/*
				 * If no NodeFeatures plugin and no explicit
				 * active features, then make active and
				 * available feature values match
				 */
				update_node_msg->features_act =
					xstrdup(update_node_msg->features);
			}
			xfree(node_ptr->features);
			if (update_node_msg->features[0]) {
				node_ptr->features =
					node_features_g_node_xlate2(
						update_node_msg->features);
			}
			/*
			 * update_node_avail_features() logs and updates
			 * avail_feature_list below
			 */
		}

		if (update_node_msg->features_act) {
			tmp_feature = node_features_g_node_xlate(
					update_node_msg->features_act,
					orig_features_act, node_ptr->features,
					node_ptr->index);
			xfree(node_ptr->features_act);
			node_ptr->features_act = tmp_feature;
			error_code = update_node_active_features(
						node_ptr->name,
						node_ptr->features_act,
						FEATURE_MODE_COMB);
			xfree(orig_features_act);
		}

		if (update_node_msg->gres) {
			xfree(node_ptr->gres);
			if (update_node_msg->gres[0])
				node_ptr->gres = xstrdup(update_node_msg->gres);
			/* _update_node_gres() logs and updates config */
		}

		if (update_node_msg->extra) {
			data_t *data = NULL;
			char *extra = update_node_msg->extra;

			if (extra[0] && extra_constraints_enabled() &&
			    serialize_g_string_to_data(&data, extra,
						       strlen(extra),
						       MIME_TYPE_JSON)) {
				error("Failed to decode extra \"%s\" for node %s",
				      update_node_msg->extra, node_ptr->name);
				error_code = ESLURM_INVALID_EXTRA;
			} else {
				FREE_NULL_DATA(node_ptr->extra_data);
				node_ptr->extra_data = data;
				xfree(node_ptr->extra);
				if (update_node_msg->extra[0]) {
					node_ptr->extra =
						xstrdup(update_node_msg->extra);
				}
				/*
				 * Skip db updates for extra field changes,
				 * otherwise we'll overwhelm it with event records
				 * if someone is updating these constantly.
				 */
				// update_db = true;
			}
		}

		if (update_node_msg->comment) {
			xfree(node_ptr->comment);
			if (update_node_msg->comment[0])
				node_ptr->comment =
					xstrdup(update_node_msg->comment);
		}

		if (instance_id_broadcast) {
			xfree(node_ptr->instance_id);
			node_ptr->instance_id = xstrdup(instance_id_broadcast);
			update_db = true;
		} else if (instance_id_list) {
			char *this_instance_id =
				hostlist_shift(instance_id_list);
			xfree(node_ptr->instance_id);
			node_ptr->instance_id = xstrdup(this_instance_id);
			free(this_instance_id);
			update_db = true;
		} else if (update_node_msg->instance_id) {
			xfree(node_ptr->instance_id);
			update_db = true;
		}

		if (instance_type_broadcast) {
			xfree(node_ptr->instance_type);
			node_ptr->instance_type =
				xstrdup(instance_type_broadcast);
			update_db = true;
		} else if (instance_type_list) {
			char *this_instance_type =
				hostlist_shift(instance_type_list);
			xfree(node_ptr->instance_type);
			node_ptr->instance_type = xstrdup(this_instance_type);
			free(this_instance_type);
			update_db = true;
		} else if (update_node_msg->instance_type) {
			xfree(node_ptr->instance_type);
			update_db = true;
		}

		if (update_db) {
			clusteracct_storage_g_node_update(acct_db_conn,
							  node_ptr);
		}

		if ((update_node_msg->resume_after != NO_VAL) &&
		    ((update_node_msg->node_state == NODE_STATE_DOWN) ||
		     (update_node_msg->node_state == NODE_STATE_DRAIN))) {
			if (update_node_msg->resume_after == INFINITE)
				resume_after = 0;
			else
				resume_after =
					now + update_node_msg->resume_after;
		}

		if (update_node_msg->topology_str) {
			int rc = SLURM_SUCCESS;
			if ((rc = node_mgr_set_node_topology(
				     node_ptr, update_node_msg->topology_str)))
				error_code = rc;
		}

		state_val = update_node_msg->node_state;
		if (_equivalent_node_state(node_ptr, state_val)) {
			/* Update resume time if another equivalent update */
			if (resume_after != NO_VAL) {
				node_ptr->resume_after = resume_after;
				info("update_node: node %s will be resumed on %lu",
				     this_node_name, node_ptr->resume_after);
			}

			/*
			 * No accounting update if node state and reason are
			 * unchanged
			 */
			if(!xstrcmp(node_ptr->reason,
				    update_node_msg->reason)) {
				free(this_node_name);
				continue;
			}
		} else if (resume_after != NO_VAL) {
			/* Set resume time for the 1st time */
			node_ptr->resume_after = resume_after;
			info("update_node: node %s will be resumed on %lu",
			     this_node_name, node_ptr->resume_after);
		} else if (node_ptr->resume_after) {
			/*
			 * Reset resume time if the state updates to another
			 * different from down or drain
			 */
			node_ptr->resume_after = 0;
			info("update_node: ResumeAfter reset for node %s after a state change",
			     this_node_name);
		}

		if ((update_node_msg -> reason) &&
		    (update_node_msg -> reason[0])) {
			xfree(node_ptr->reason);
			node_ptr->reason = xstrdup(update_node_msg->reason);
			node_ptr->reason_time = now;
			node_ptr->reason_uid = auth_uid;
			info ("update_node: node %s reason set to: %s",
				this_node_name, node_ptr->reason);
		}

		if (state_val != NO_VAL) {
			base_state = node_ptr->node_state;
			if (!_valid_node_state_change(base_state, state_val)) {
				info("Invalid node state transition requested "
				     "for node %s from=%s to=%s",
				     this_node_name,
				     node_state_string(base_state),
				     node_state_string(state_val));
				state_val = NO_VAL;
				error_code =
					ESLURM_INVALID_NODE_STATE_TRANSITION;
			}
			base_state &= NODE_STATE_BASE;
		}

		if (state_val != NO_VAL) {
			node_flags = node_ptr->node_state & NODE_STATE_FLAGS;
			if (state_val == NODE_RESUME) {
				trigger_node_resume(node_ptr);
				if (IS_NODE_IDLE(node_ptr) &&
				    (IS_NODE_DRAIN(node_ptr) ||
				     IS_NODE_FAIL(node_ptr))) {
					clusteracct_storage_g_node_up(
						acct_db_conn,
						node_ptr,
						now);
					acct_updated = true;
				}
				node_ptr->node_state &= (~NODE_STATE_DRAIN);
				node_ptr->node_state &= (~NODE_STATE_FAIL);
				node_ptr->node_state &=
					(~NODE_STATE_REBOOT_REQUESTED);
				node_ptr->node_state &=
					(~NODE_STATE_REBOOT_ISSUED);

				if (IS_NODE_POWERING_DOWN(node_ptr)) {
					node_ptr->node_state &=
						(~NODE_STATE_INVALID_REG);
					node_ptr->node_state &=
						(~NODE_STATE_POWERING_DOWN);
					node_ptr->node_state |=
						NODE_STATE_POWERED_DOWN;

					if (IS_NODE_CLOUD(node_ptr))
						set_node_comm_name(
							node_ptr, NULL,
							node_ptr->name);

					node_ptr->power_save_req_time = 0;

					reset_node_active_features(node_ptr);
					reset_node_instance(node_ptr);
					reset_node_topology(node_ptr);

					clusteracct_storage_g_node_down(
						acct_db_conn,
						node_ptr, now,
						"Powered down after resume",
						node_ptr->reason_uid);
				}

				if (IS_NODE_DOWN(node_ptr)) {
					state_val = NODE_STATE_IDLE;
					_require_node_reg(node_ptr);
				} else if (IS_NODE_FUTURE(node_ptr)) {
					if (node_ptr->port == 0) {
						node_ptr->port =
							slurm_conf.slurmd_port;
					}
					state_val = NODE_STATE_IDLE;
					bit_clear(future_node_bitmap,
						  node_ptr->index);

					_require_node_reg(node_ptr);
				} else if (node_flags & NODE_STATE_DRAIN) {
					state_val = base_state;
					_require_node_reg(node_ptr);
				} else
					state_val = base_state;
			} else if (state_val == NODE_STATE_UNDRAIN) {
				if (IS_NODE_IDLE(node_ptr) &&
				    IS_NODE_DRAIN(node_ptr)) {
					clusteracct_storage_g_node_up(
						acct_db_conn,
						node_ptr,
						now);
					acct_updated = true;
				}
				node_ptr->node_state &= (~NODE_STATE_DRAIN);
				_require_node_reg(node_ptr);
				state_val = base_state;
			}

			if ((state_val == NODE_STATE_DOWN) ||
			    (state_val == NODE_STATE_FUTURE)) {
				/* We must set node DOWN before killing
				 * its jobs */
				_make_node_down(node_ptr, now);
				kill_running_job_by_node_ptr(node_ptr);
				if (state_val == NODE_STATE_FUTURE) {
					bool dyn_norm_node = false;
					if (IS_NODE_DYNAMIC_FUTURE(node_ptr)) {
						/* Reset comm and hostname */
						set_node_comm_name(
							node_ptr, NULL,
							node_ptr->name);
						reset_node_instance(node_ptr);
					}
					/*
					 * Preserve dynamic norm state until
					 * node is deleted.
					 */
					if (IS_NODE_DYNAMIC_NORM(node_ptr))
						dyn_norm_node = true;
					node_ptr->node_state =
						NODE_STATE_FUTURE;
					if (dyn_norm_node)
						node_ptr->node_state |=
							NODE_STATE_DYNAMIC_NORM;
					bit_set(future_node_bitmap,
						node_ptr->index);
					bit_clear(power_up_node_bitmap,
						  node_ptr->index);
					clusteracct_storage_g_node_down(
						acct_db_conn,
						node_ptr, now,
						"Set to State=FUTURE",
						node_ptr->reason_uid);
				}
			} else if (state_val == NODE_STATE_IDLE) {
				/* assume they want to clear DRAIN and
				 * FAIL flags too */
				if (IS_NODE_DOWN(node_ptr)) {
					trigger_node_up(node_ptr);
					clusteracct_storage_g_node_up(
						acct_db_conn,
						node_ptr,
						now);
					acct_updated = true;
				} else if (IS_NODE_IDLE(node_ptr)   &&
					   (IS_NODE_DRAIN(node_ptr) ||
					    IS_NODE_FAIL(node_ptr))) {
					clusteracct_storage_g_node_up(
						acct_db_conn,
						node_ptr,
						now);
					acct_updated = true;
				}	/* else already fully available */
				node_ptr->node_state &= (~NODE_STATE_DRAIN);
				node_ptr->node_state &= (~NODE_STATE_FAIL);
				if (!IS_NODE_NO_RESPOND(node_ptr) ||
				     IS_NODE_POWERED_DOWN(node_ptr))
					make_node_avail(node_ptr);
				bit_set (idle_node_bitmap, node_ptr->index);
				bit_set (up_node_bitmap, node_ptr->index);
				if (IS_NODE_POWERED_DOWN(node_ptr))
					node_ptr->last_busy = 0;
				else
					node_ptr->last_busy = now;
			} else if (state_val == NODE_STATE_ALLOCATED) {
				if (!IS_NODE_DRAIN(node_ptr) &&
				    !IS_NODE_FAIL(node_ptr)  &&
				    !IS_NODE_NO_RESPOND(node_ptr))
					make_node_avail(node_ptr);
				bit_set (up_node_bitmap, node_ptr->index);
				bit_clear (idle_node_bitmap, node_ptr->index);
			} else if ((state_val == NODE_STATE_DRAIN) ||
				   (state_val == NODE_STATE_FAIL)) {
				if (IS_NODE_ALLOCATED(node_ptr) &&
				    (IS_NODE_POWERED_DOWN(node_ptr) ||
				     IS_NODE_POWERING_UP(node_ptr))) {
					info("%s: DRAIN/FAIL request for node %s which is allocated and being powered up. Requeuing jobs",
					     __func__, this_node_name);
					kill_running_job_by_node_ptr(node_ptr);
				}
				/* Notify of strigger event */
				if (state_val == NODE_STATE_DRAIN)
					trigger_node_draining(node_ptr);
				else
					trigger_node_failing(node_ptr);
				bit_clear (avail_node_bitmap, node_ptr->index);
				node_ptr->node_state &= (~NODE_STATE_DRAIN);
				node_ptr->node_state &= (~NODE_STATE_FAIL);
				state_val = node_ptr->node_state |= state_val;
				if ((node_ptr->run_job_cnt  == 0) &&
				    (node_ptr->comp_job_cnt == 0)) {
					if (state_val & NODE_STATE_DRAIN)
						trigger_node_drained(node_ptr);
					clusteracct_storage_g_node_down(
						acct_db_conn,
						node_ptr, now, NULL,
						node_ptr->reason_uid);
				}
			} else if (state_val & NODE_STATE_POWER_DOWN) {
				if ((state_val & NODE_STATE_POWER_UP) &&
				    (IS_NODE_POWERING_UP(node_ptr))) {
					/* Clear any reboot op in progress */
					node_ptr->node_state &=
						(~NODE_STATE_POWERING_UP);
					node_ptr->last_response = now;
					free(this_node_name);
					continue;
				}

				/* Abort reboot request */
				if (IS_NODE_REBOOT_REQUESTED(node_ptr) ||
				    IS_NODE_REBOOT_ISSUED(node_ptr)) {
					/*
					 * A node is DOWN+REBOOT_ISSUED when the
					 * reboot has been issued. Set node
					 * state back to idle only if the reboot
					 * has been issued. Node will remain
					 * cleared in the avail_node_bitmap
					 * until the node is powered down.
					 */
					if (IS_NODE_REBOOT_ISSUED(node_ptr) &&
					    IS_NODE_DOWN(node_ptr)) {
						node_ptr->node_state =
							NODE_STATE_IDLE |
							(node_ptr->node_state &
							 NODE_STATE_FLAGS);
					}

					node_ptr->node_state &=
						(~NODE_STATE_REBOOT_REQUESTED);
					node_ptr->node_state &=
						(~NODE_STATE_REBOOT_ISSUED);
					xfree(node_ptr->reason);

					info("Canceling REBOOT on node %s",
					     this_node_name);
				}

				if (IS_NODE_POWERING_DOWN(node_ptr)) {
					info("ignoring power down request for node %s, already powering down",
					     this_node_name);
					node_ptr->next_state = NO_VAL;
					free(this_node_name);
					continue;
				}

				if (state_val & NODE_STATE_POWERED_DOWN) {
					/* Force power down */
					_make_node_unavail(node_ptr);
					/*
					 * Kill any running jobs and requeue if
					 * possible.
					 */
					kill_running_job_by_node_ptr(node_ptr);
					node_ptr->node_state &=
						(~NODE_STATE_POWER_UP);
					node_ptr->node_state &=
						(~NODE_STATE_POWERING_UP);
				} else if (state_val & NODE_STATE_POWER_DRAIN) {
					/* power down asap -- drain */
					_drain_node(node_ptr, "POWER_DOWN_ASAP",
						    node_ptr->reason_uid);
				}
				if (IS_NODE_DOWN(node_ptr)) {
					/* Abort any power up request */
					node_ptr->node_state &=
						(~NODE_STATE_POWERING_UP);
				}

				if (IS_NODE_POWERED_DOWN(node_ptr))
					info("power down request repeating for node %s",
					     this_node_name);
				else
					info("powering down node %s",
					     this_node_name);

				xfree(node_ptr->power_action_name);
				node_ptr->power_action_name =
					xstrdup(update_node_msg
							->power_action_name);
				node_ptr->node_state |=
					NODE_STATE_POWER_DOWN;

				if (IS_NODE_IDLE(node_ptr)) {
					/*
					 * remove node from avail_node_bitmap so
					 * that it will power_down before jobs
					 * get on it.
					 */
					bit_clear(avail_node_bitmap,
						  node_ptr->index);
				}

				node_ptr->next_state = NO_VAL;
				bit_clear(rs_node_bitmap, node_ptr->index);
				free(this_node_name);
				continue;
			} else if (state_val & NODE_STATE_POWER_UP) {
				if (state_val & NODE_STATE_POWERED_DOWN) {
					info("force power up request for node %s",
					     this_node_name);
					/*
					 * Kill any running jobs and requeue if
					 * possible.
					 */
					kill_running_job_by_node_ptr(node_ptr);
					node_ptr->node_state |=
						NODE_STATE_POWERED_DOWN;
					node_ptr->node_state &=
						(~NODE_STATE_POWERING_DOWN);
					node_ptr->node_state &=
						(~NODE_STATE_POWERING_UP);
					node_ptr->node_state &=
						(~NODE_STATE_POWER_DRAIN);
				} else if (!IS_NODE_POWERED_DOWN(node_ptr)) {
					if (IS_NODE_POWERING_UP(node_ptr)) {
						info("power up request repeating for node %s",
						     this_node_name);
						node_ptr->node_state |=
							NODE_STATE_POWERED_DOWN;
					} else {
						verbose("node %s is already powered up",
							this_node_name);
						free(this_node_name);
						continue;
					}
				} else {
					info("powering up node %s",
					     this_node_name);
				}
				node_ptr->node_state |= NODE_STATE_POWER_UP;

				xfree(node_ptr->power_action_name);
				node_ptr->power_action_name =
					xstrdup(update_node_msg
							->power_action_name);
				bit_set(power_up_node_bitmap, node_ptr->index);
				node_ptr->next_state = NO_VAL;
				bit_clear(rs_node_bitmap, node_ptr->index);
				free(this_node_name);
				continue;
			} else if (state_val == NODE_STATE_NO_RESPOND) {
				node_ptr->node_state |= NODE_STATE_NO_RESPOND;
				state_val = base_state;
				bit_clear(avail_node_bitmap, node_ptr->index);
			} else if (state_val == NODE_STATE_REBOOT_CANCEL) {
				if (!IS_NODE_REBOOT_ISSUED(node_ptr)) {
					node_ptr->node_state &=
						(~NODE_STATE_REBOOT_REQUESTED);
					state_val = base_state;
					if ((node_ptr->next_state &
					     NODE_STATE_FLAGS) &
					    NODE_STATE_UNDRAIN)
						_undo_reboot_asap(node_ptr);
				} else {
					info("REBOOT on node %s already in progress -- unable to cancel",
					     this_node_name);
					err_code = error_code =
						ESLURM_REBOOT_IN_PROGRESS;
				}
			} else {
				info("Invalid node state specified %u",
				     state_val);
				err_code = 1;
				error_code = ESLURM_INVALID_NODE_STATE;
			}

			if (err_code == 0) {
				node_ptr->node_state = state_val |
						(node_ptr->node_state &
						 NODE_STATE_FLAGS);

				if (!IS_NODE_REBOOT_REQUESTED(node_ptr) &&
				    !IS_NODE_REBOOT_ISSUED(node_ptr))
					node_ptr->next_state = NO_VAL;
				bit_clear(rs_node_bitmap, node_ptr->index);

				info ("update_node: node %s state set to %s",
					this_node_name,
					node_state_string(state_val));
			}
		}

		/*
		 * After all the possible state changes, check if we need to
		 * clear the asap_node_bitmap.
		 */
		if (!IS_NODE_REBOOT_ASAP(node_ptr))
			bit_clear(asap_node_bitmap, node_ptr->index);

		if (!acct_updated && !IS_NODE_DOWN(node_ptr) &&
		    !IS_NODE_DRAIN(node_ptr) && !IS_NODE_FAIL(node_ptr)) {
			/* reason information is handled in
			   clusteracct_storage_g_node_up()
			*/
			clusteracct_storage_g_node_up(
				acct_db_conn, node_ptr, now);
		}

		free (this_node_name);
	}

	/* Write/clear log */
	(void)update_node_active_features(NULL, NULL, FEATURE_MODE_PEND);

	last_node_update = now;

	if ((error_code == SLURM_SUCCESS) && (update_node_msg->features)) {
		error_code = update_node_avail_features(
					update_node_msg->node_names,
					update_node_msg->features,
					FEATURE_MODE_IND);
	}
	if ((error_code == SLURM_SUCCESS) && (update_node_msg->gres)) {
		error_code = _update_node_gres(update_node_msg->node_names,
					       update_node_msg->gres);
	}

	/*
	 * Update weight. Weight is part of config_ptr,
	 * hence split config records if required
	 */
	if ((error_code == SLURM_SUCCESS) &&
	    (update_node_msg->weight != NO_VAL))	{
		error_code = _update_node_weight(update_node_msg->node_names,
						 update_node_msg->weight);
		if (error_code == SLURM_SUCCESS) {
			/* sort config_list by weight for scheduling */
			list_sort(config_list, &list_compare_config);
		}
	}

end:
	FREE_NULL_HOSTLIST(host_list);
	FREE_NULL_HOSTLIST(hostaddr_list);
	FREE_NULL_HOSTLIST(hostname_list);
	FREE_NULL_HOSTLIST(instance_id_list);
	FREE_NULL_HOSTLIST(instance_type_list);
	free(instance_id_broadcast);
	free(instance_type_broadcast);

	return error_code;
}

/*
 * restore_node_features - Make node and config (from slurm.conf) fields
 *	consistent for Features, Gres and Weight
 * IN recover -
 *              0, 1 - use data from config record, built using slurm.conf
 *              2 = use data from node record, built from saved state
 */
extern void restore_node_features(int recover)
{
	int i, node_features_plugin_cnt;
	node_record_t *node_ptr;

	node_features_plugin_cnt = node_features_g_count();
	for (i = 0; (node_ptr = next_node(&i)); i++) {
		char *gres_name = NULL;

		if (node_ptr->weight != node_ptr->config_ptr->weight) {
			error("Node %s Weight(%u) differ from slurm.conf",
			      node_ptr->name, node_ptr->weight);
			if (recover == 2) {
				_update_node_weight(node_ptr->name,
						    node_ptr->weight);
			} else {
				node_ptr->weight = node_ptr->config_ptr->
						   weight;
			}
		}
		if (xstrcmp(node_ptr->config_ptr->feature, node_ptr->features)){
			if (node_features_plugin_cnt == 0) {
				error("Node %s Features(%s) differ from slurm.conf",
				      node_ptr->name, node_ptr->features);
			}
			if (recover == 2) {
				update_node_avail_features(node_ptr->name,
							   node_ptr->features,
							   FEATURE_MODE_COMB);
			}
		}

		/*
		 * Rebuild extra_data
		 */
		if (node_ptr->extra && node_ptr->extra[0] &&
		    extra_constraints_enabled()) {
			data_t *data = NULL;
			if (serialize_g_string_to_data(&data, node_ptr->extra,
						       strlen(node_ptr->extra),
						       MIME_TYPE_JSON)) {
				info("Failed to decode extra \"%s\" for node %s",
				     node_ptr->extra, node_ptr->name);
			} else {
				node_ptr->extra_data = data;
			}
		}

		/* node_ptr->gres is set when recover == 2 */
		if (node_ptr->gres)
			gres_name = node_ptr->gres;
		else
			gres_name = node_ptr->config_ptr->gres;

		(void) gres_node_reconfig(
			node_ptr->name,
			gres_name,
			&node_ptr->gres,
			&node_ptr->gres_list,
			slurm_conf.conf_flags & CONF_FLAG_OR,
			node_ptr->cores,
			node_ptr->tot_sockets);
		gres_node_state_log(node_ptr->gres_list, node_ptr->name);
	}
	update_node_avail_features(NULL, NULL, FEATURE_MODE_PEND);
}

/* Duplicate a configuration record except for the node names & bitmap */
config_record_t *_dup_config(config_record_t *config_ptr)
{
	config_record_t *new_config_ptr;

	new_config_ptr = create_config_record();
	new_config_ptr->magic       = config_ptr->magic;
	new_config_ptr->cpus        = config_ptr->cpus;
	new_config_ptr->cpu_spec_list = xstrdup(config_ptr->cpu_spec_list);
	new_config_ptr->boards      = config_ptr->boards;
	new_config_ptr->tot_sockets     = config_ptr->tot_sockets;
	new_config_ptr->cores       = config_ptr->cores;
	new_config_ptr->core_spec_cnt = config_ptr->core_spec_cnt;
	new_config_ptr->threads     = config_ptr->threads;
	new_config_ptr->real_memory = config_ptr->real_memory;
	new_config_ptr->res_cores_per_gpu = config_ptr->res_cores_per_gpu;
	new_config_ptr->mem_spec_limit = config_ptr->mem_spec_limit;
	new_config_ptr->tmp_disk    = config_ptr->tmp_disk;
	new_config_ptr->weight      = config_ptr->weight;
	new_config_ptr->feature     = xstrdup(config_ptr->feature);
	new_config_ptr->gres        = xstrdup(config_ptr->gres);

	_queue_consolidate_config_list();

	return new_config_ptr;
}

/*
 * _update_node_weight - Update weight associated with nodes
 *	build new config list records as needed
 * IN node_names - list of nodes to update
 * IN weight - New weight value
 * RET: SLURM_SUCCESS or error code
 */
static int _update_node_weight(char *node_names, uint32_t weight)
{
	bitstr_t *node_bitmap = NULL, *tmp_bitmap;
	list_itr_t *config_iterator;
	config_record_t *config_ptr, *new_config_ptr, *first_new = NULL;
	int rc, config_cnt, tmp_cnt;

	rc = node_name2bitmap(node_names, false, &node_bitmap, NULL);
	if (rc) {
		info("_update_node_weight: invalid node_name");
		return rc;
	}

	/* For each config_record with one of these nodes,
	 * update it (if all nodes updated) or split it into
	 * a new entry */
	config_iterator = list_iterator_create(config_list);
	while ((config_ptr = list_next(config_iterator))) {
		if (config_ptr == first_new)
			break;	/* done with all original records */

		tmp_bitmap = bit_copy(node_bitmap);
		bit_and(tmp_bitmap, config_ptr->node_bitmap);
		config_cnt = bit_set_count(config_ptr->node_bitmap);
		tmp_cnt = bit_set_count(tmp_bitmap);
		if (tmp_cnt == 0) {
			/* no overlap, leave alone */
		} else if (tmp_cnt == config_cnt) {
			/* all nodes changed, update in situ */
			config_ptr->weight = weight;
		} else {
			/* partial update, split config_record */
			new_config_ptr = _dup_config(config_ptr);
			if (first_new == NULL)
				first_new = new_config_ptr;
			/* Change weight for the given nodes */
			new_config_ptr->weight      = weight;
			new_config_ptr->node_bitmap = bit_copy(tmp_bitmap);
			new_config_ptr->nodes = bitmap2node_name(tmp_bitmap);
			_update_config_ptr(tmp_bitmap, new_config_ptr);

			/* Update remaining records */
			bit_and_not(config_ptr->node_bitmap, tmp_bitmap);
			xfree(config_ptr->nodes);
			config_ptr->nodes = bitmap2node_name(
						config_ptr->node_bitmap);
		}
		FREE_NULL_BITMAP(tmp_bitmap);
	}
	list_iterator_destroy(config_iterator);
	FREE_NULL_BITMAP(node_bitmap);

	info("_update_node_weight: nodes %s weight set to: %u",
		node_names, weight);
	return SLURM_SUCCESS;
}

static inline void _update_node_features_post(
	char *node_names,
	char **last_features, char *features,
	bitstr_t **last_node_bitmap, bitstr_t **node_bitmap,
	int mode, const char *type)
{

	xassert(last_features);
	xassert(last_node_bitmap);
	xassert(node_bitmap);

	if (mode == FEATURE_MODE_IND) {
		debug2("%s: nodes %s %s features set to: %s",
		       __func__, node_names, type, features);
	} else if (*last_features && *last_node_bitmap &&
		   ((mode == FEATURE_MODE_PEND) ||
		    xstrcmp(features, *last_features))) {
		char *last_node_names = bitmap2node_name(*last_node_bitmap);
		debug2("%s: nodes %s %s features set to: %s",
		       __func__, last_node_names, type, *last_features);
		xfree(last_node_names);
		xfree(*last_features);
		FREE_NULL_BITMAP(*last_node_bitmap);
	}

	if (mode == FEATURE_MODE_COMB) {
		if (!*last_features) {
			/* Start combining records */
			*last_features = xstrdup(features);
			*last_node_bitmap = *node_bitmap;
			*node_bitmap = NULL;
		} else {
			/* Add this node to existing log info */
			bit_or(*last_node_bitmap, *node_bitmap);
		}
	}
}

extern int update_node_active_features(char *node_names, char *active_features,
				       int mode)
{
	static char *last_active_features = NULL;
	static bitstr_t *last_node_bitmap = NULL;
	bitstr_t *node_bitmap = NULL;
	int rc;

	if (mode < FEATURE_MODE_PEND) {
		/* Perform update of node active features */
		rc = node_name2bitmap(node_names, false, &node_bitmap, NULL);
		if (rc) {
			info("%s: invalid node_name (%s)", __func__,
			     node_names);
			return rc;
		}
		node_features_update_list(active_feature_list, active_features,
					  node_bitmap);
	}

	_update_node_features_post(node_names,
				   &last_active_features, active_features,
				   &last_node_bitmap, &node_bitmap,
				   mode, "active");
	FREE_NULL_BITMAP(node_bitmap);

	return SLURM_SUCCESS;
}

extern int update_node_avail_features(char *node_names, char *avail_features,
				      int mode)
{
	static char *last_avail_features = NULL;
	static bitstr_t *last_node_bitmap = NULL;
	hostlist_t *host_list = NULL;
	bitstr_t *node_bitmap = NULL, *tmp_bitmap;
	list_itr_t *config_iterator;
	config_record_t *config_ptr, *new_config_ptr, *first_new = NULL;
	int rc, config_cnt, tmp_cnt;

	if (mode < FEATURE_MODE_PEND) {
		host_list = nodespec_to_hostlist(node_names, false, NULL);
		if (!host_list)
			return ESLURM_INVALID_NODE_NAME;
		rc = hostlist2bitmap(host_list, false, &node_bitmap);
		if (rc) {
			FREE_NULL_BITMAP(node_bitmap);
			FREE_NULL_HOSTLIST(host_list);
			return rc;
		}

		/*
		 * For each config_record with one of these nodes, update it
		 * (if all nodes updated) or split it into a new entry
		 */
		config_iterator = list_iterator_create(config_list);
		while ((config_ptr = list_next(config_iterator))) {
			if (config_ptr == first_new)
				break;	/* done with all original records */

			tmp_bitmap = bit_copy(node_bitmap);
			bit_and(tmp_bitmap, config_ptr->node_bitmap);
			config_cnt = bit_set_count(config_ptr->node_bitmap);
			tmp_cnt = bit_set_count(tmp_bitmap);
			if (tmp_cnt == 0) {
				/* no overlap, leave alone */
			} else if (tmp_cnt == config_cnt) {
				/* all nodes changed, update in situ */
				xfree(config_ptr->feature);
				if (avail_features && avail_features[0]) {
					config_ptr->feature =
						xstrdup(avail_features);
				}
			} else {
				/* partial update, split config_record */
				new_config_ptr = _dup_config(config_ptr);
				if (first_new == NULL)
					first_new = new_config_ptr;
				xfree(new_config_ptr->feature);
				if (avail_features && avail_features[0]) {
					new_config_ptr->feature =
						xstrdup(avail_features);
				}
				new_config_ptr->node_bitmap =
						bit_copy(tmp_bitmap);
				new_config_ptr->nodes =
						bitmap2node_name(tmp_bitmap);
				_update_config_ptr(tmp_bitmap, new_config_ptr);

				/* Update remaining records */
				bit_and_not(config_ptr->node_bitmap, tmp_bitmap);
				xfree(config_ptr->nodes);
				config_ptr->nodes = bitmap2node_name(
						    config_ptr->node_bitmap);
			}
			FREE_NULL_BITMAP(tmp_bitmap);
		}
		list_iterator_destroy(config_iterator);
		if (avail_feature_list) {	/* list not set at startup */
			node_features_update_list(avail_feature_list,
						  avail_features, node_bitmap);
		}
	}

	_update_node_features_post(node_names,
				   &last_avail_features, avail_features,
				   &last_node_bitmap, &node_bitmap,
				   mode, "available");
	FREE_NULL_HOSTLIST(host_list);
	FREE_NULL_BITMAP(node_bitmap);

	return SLURM_SUCCESS;
}

extern char *filter_out_changeable_features(const char *features)
{
	char *conf_features = NULL, *tmp_feat, *tok, *saveptr;

	if (!features)
		return NULL;

	tmp_feat = xstrdup(features);
	for (tok = strtok_r(tmp_feat, ",", &saveptr); tok;
	     tok = strtok_r(NULL, ",", &saveptr)) {
		if (node_features_g_changeable_feature(tok))
			continue;
		xstrfmtcat(conf_features, "%s%s",
			   conf_features ? "," : "", tok);
	}
	xfree(tmp_feat);

	return conf_features;
}

extern void reset_node_active_features(node_record_t *node_ptr)
{
	xassert(node_ptr);

	xfree(node_ptr->features_act);
	node_ptr->features_act =
		filter_out_changeable_features(node_ptr->features);
	update_node_active_features(node_ptr->name, node_ptr->features_act,
				    FEATURE_MODE_IND);
}

extern void reset_node_instance(node_record_t *node_ptr)
{
	xassert(node_ptr);

	xfree(node_ptr->instance_id);
	xfree(node_ptr->instance_type);
}

extern void reset_node_topology(node_record_t *node_ptr)
{
	xassert(node_ptr);

	char *old_topo = NULL;

	if (node_ptr->config_ptr->topology_str)
		old_topo = node_ptr->config_ptr->topology_str;
	else
		old_topo = node_ptr->topology_orig_str;

	if (!node_ptr->config_ptr->topology_str && !node_ptr->topology_str &&
	    !node_ptr->topology_orig_str) {
		/*
		 * No need to reset topo if the topology was never modified to
		 * start with. Topology is only from topology.conf|yaml.
		 */
	} else {
		node_mgr_set_node_topology(node_ptr, old_topo ? old_topo : "");
	}

	if (old_topo == node_ptr->topology_orig_str) {
		xfree(node_ptr->topology_str);
	}
	xfree(node_ptr->topology_orig_str);
}

/*
 * _update_node_gres - Update generic resources associated with nodes
 *	build new config list records as needed
 * IN node_names - list of nodes to update
 * IN gres - New gres value
 * RET: SLURM_SUCCESS or error code
 */
static int _update_node_gres(char *node_names, char *gres)
{
	bitstr_t *changed_node_bitmap = NULL, *node_bitmap = NULL, *tmp_bitmap;
	list_itr_t *config_iterator;
	config_record_t *config_ptr, *new_config_ptr, *first_new = NULL;
	node_record_t *node_ptr;
	int rc, rc2, overlap1, overlap2;

	rc = node_name2bitmap(node_names, false, &node_bitmap, NULL);
	if (rc) {
		info("%s: invalid node_name: %s", __func__, node_names);
		return rc;
	}

	/*
	 * For each config_record with one of these nodes,
	 * update it (if all nodes updated) or split it into a new entry
	 */
	config_iterator = list_iterator_create(config_list);
	while ((config_ptr = list_next(config_iterator))) {
		if (config_ptr == first_new)
			break;	/* done with all original records */

		overlap1 = bit_overlap(node_bitmap, config_ptr->node_bitmap);
		if (overlap1 == 0)
			continue;  /* No changes to this config_record */

		/* At least some nodes in this config need to change */
		tmp_bitmap = bit_copy(node_bitmap);
		bit_and(tmp_bitmap, config_ptr->node_bitmap);
		for (int i = 0; (node_ptr = next_node_bitmap(tmp_bitmap, &i));
		     i++) {
			rc2 = gres_node_reconfig(
				node_ptr->name,
				gres, &node_ptr->gres,
				&node_ptr->gres_list,
				slurm_conf.conf_flags & CONF_FLAG_OR,
				node_ptr->cores,
				node_ptr->tot_sockets);
			if (rc2 != SLURM_SUCCESS) {
				bit_clear(tmp_bitmap, i);
				overlap1--;
				if (rc == SLURM_SUCCESS)
					rc = rc2;
			}
			gres_node_state_log(node_ptr->gres_list,
					    node_ptr->name);
		}

		overlap2 = bit_set_count(config_ptr->node_bitmap);
		if (overlap1 == 0) {
			/* No nodes actually changed in this configuration */
			FREE_NULL_BITMAP(tmp_bitmap);
		} else if (overlap1 == overlap2) {
			/* All nodes changes in this configuration */
			xfree(config_ptr->gres);
			if (gres && gres[0])
				config_ptr->gres = xstrdup(gres);
			if (changed_node_bitmap) {
				bit_or(changed_node_bitmap, tmp_bitmap);
				FREE_NULL_BITMAP(tmp_bitmap);
			} else {
				changed_node_bitmap = tmp_bitmap;
				tmp_bitmap = NULL;
			}
		} else {
			/*
			 * Some nodes changes in this configuration.
			 * Split config_record in two.
			 */
			new_config_ptr = _dup_config(config_ptr);
			if (!first_new)
				first_new = new_config_ptr;
			xfree(new_config_ptr->gres);
			if (gres && gres[0])
				new_config_ptr->gres = xstrdup(gres);
			new_config_ptr->node_bitmap = tmp_bitmap;
			new_config_ptr->nodes = bitmap2node_name(tmp_bitmap);
			_update_config_ptr(tmp_bitmap, new_config_ptr);
			if (changed_node_bitmap) {
				bit_or(changed_node_bitmap, tmp_bitmap);
			} else {
				changed_node_bitmap = bit_copy(tmp_bitmap);
			}

			/* Update remaining config_record */
			bit_and_not(config_ptr->node_bitmap, tmp_bitmap);
			xfree(config_ptr->nodes);
			config_ptr->nodes = bitmap2node_name(
						config_ptr->node_bitmap);
			tmp_bitmap = NULL;	/* Nothing left to free */
		}
	}
	list_iterator_destroy(config_iterator);
	FREE_NULL_BITMAP(node_bitmap);

	/* Report changes nodes, may be subset of requested nodes */
	if (changed_node_bitmap) {
		char *change_node_str = bitmap2node_name(changed_node_bitmap);
		info("%s: nodes %s gres set to: %s", __func__,
		     change_node_str, gres);
		FREE_NULL_BITMAP(changed_node_bitmap);
		xfree(change_node_str);
	}

	return rc;
}

/* Reset the config pointer for updated jobs */
static void _update_config_ptr(bitstr_t *bitmap, config_record_t *config_ptr)
{
	node_record_t *node_ptr;

	for (int i = 0; (node_ptr = next_node_bitmap(bitmap, &i)); i++) {
		node_ptr->config_ptr = config_ptr;
	}
}

static void _drain_node(node_record_t *node_ptr, char *reason,
			uint32_t reason_uid)
{
	time_t now = time(NULL);

	xassert(node_ptr);

	if (IS_NODE_DRAIN(node_ptr)) {
		/* state already changed, nothing to do */
		return;
	}

	trigger_node_draining(node_ptr);
	node_ptr->node_state |= NODE_STATE_DRAIN;
	bit_clear(avail_node_bitmap, node_ptr->index);
	info("drain_nodes: node %s state set to DRAIN",
	     node_ptr->name);
	if ((node_ptr->reason == NULL) ||
	    (xstrncmp(node_ptr->reason, "Not responding", 14) == 0)) {
		xfree(node_ptr->reason);
		node_ptr->reason = xstrdup(reason);
		node_ptr->reason_time = now;
		node_ptr->reason_uid = reason_uid;
	}
	if ((node_ptr->run_job_cnt  == 0) &&
	    (node_ptr->comp_job_cnt == 0)) {
		/* no jobs, node is drained */
		trigger_node_drained(node_ptr);
		clusteracct_storage_g_node_down(acct_db_conn,
						node_ptr, now, NULL,
						reason_uid);
	}
}

/*
 * drain_nodes - drain one or more nodes,
 *  no-op for nodes already drained or draining
 * IN nodes - nodes to drain
 * IN reason - reason to drain the nodes
 * RET SLURM_SUCCESS or error code
 * global: node_record_table_ptr - pointer to global node table
 */
extern int drain_nodes(char *nodes, char *reason, uint32_t reason_uid)
{
	int error_code = SLURM_SUCCESS;
	node_record_t *node_ptr;
	char  *this_node_name ;
	hostlist_t *host_list;

	if ((nodes == NULL) || (nodes[0] == '\0')) {
		error ("drain_nodes: invalid node name  %s", nodes);
		return ESLURM_INVALID_NODE_NAME;
	}

	if ( (host_list = hostlist_create (nodes)) == NULL) {
		error ("hostlist_create error on %s: %m", nodes);
		return ESLURM_INVALID_NODE_NAME;
	}

	while ( (this_node_name = hostlist_shift (host_list)) ) {
		if (!(node_ptr = find_node_record(this_node_name))) {
			error_code = ESLURM_INVALID_NODE_NAME;
			error("drain_nodes: node %s does not exist",
			      this_node_name);
			free(this_node_name);
			break;
		}
		free (this_node_name);
		_drain_node(node_ptr, reason, reason_uid);
	}
	last_node_update = time (NULL);

	hostlist_destroy (host_list);

	/*
	 * check all reservations since nodes may have been in a reservation with
	 * floating count of nodes that needs to be updated
	 */
	validate_all_reservations(false, false);

	return error_code;
}
/* Return true if admin request to change node state from old to new is valid */
static bool _valid_node_state_change(uint32_t old, uint32_t new)
{
	uint32_t base_state, node_flags;

	if (old == new)
		return true;

	base_state = old & NODE_STATE_BASE;
	node_flags = old & NODE_STATE_FLAGS;

	switch (new) {
		case NODE_STATE_DOWN:
		case NODE_STATE_DRAIN:
		case NODE_STATE_FAIL:
		case NODE_STATE_NO_RESPOND:
			return true;

		case NODE_STATE_UNDRAIN:
			if (!(node_flags & NODE_STATE_INVALID_REG))
				return true;
			break;

		case NODE_STATE_POWER_DOWN:
		case NODE_STATE_POWER_UP:
		case (NODE_STATE_POWER_DOWN | NODE_STATE_POWER_UP):
		case (NODE_STATE_POWER_UP | NODE_STATE_POWERED_DOWN):
		case (NODE_STATE_POWER_DOWN | NODE_STATE_POWERED_DOWN):
		case (NODE_STATE_POWER_DOWN | NODE_STATE_POWER_DRAIN):
			return true;

		case NODE_RESUME:
			if (node_flags & NODE_STATE_POWERING_DOWN)
				return true;
			if (node_flags & NODE_STATE_INVALID_REG)
				return false;
			if ((base_state == NODE_STATE_DOWN)   ||
			    (base_state == NODE_STATE_FUTURE) ||
			    (node_flags & NODE_STATE_DRAIN)   ||
			    (node_flags & NODE_STATE_FAIL)    ||
			    (node_flags & NODE_STATE_REBOOT_REQUESTED))
				return true;
			break;

		case NODE_STATE_REBOOT_CANCEL:
			if (node_flags & NODE_STATE_REBOOT_REQUESTED)
				return true;
			break;

		case NODE_STATE_FUTURE:
			if ((base_state == NODE_STATE_DOWN) ||
			    (base_state == NODE_STATE_IDLE))
				return true;
			break;

		case NODE_STATE_IDLE:
			if (!(node_flags & NODE_STATE_INVALID_REG) &&
			    !(node_flags & NODE_STATE_REBOOT_ISSUED) &&
			    ((base_state == NODE_STATE_DOWN) ||
			     (base_state == NODE_STATE_IDLE)))
				return true;
			break;

		case NODE_STATE_ALLOCATED:
			if (base_state == NODE_STATE_ALLOCATED)
				return true;
			break;

		default:	/* All others invalid */
			break;
	}

	return false;
}

extern int update_node_record_acct_gather_data(
	acct_gather_node_resp_msg_t *msg)
{
	node_record_t *node_ptr;

	node_ptr = find_node_record(msg->node_name);
	if (node_ptr == NULL)
		return ENOENT;

	memcpy(node_ptr->energy, msg->energy, sizeof(acct_gather_energy_t));

	return SLURM_SUCCESS;
}

/* A node's socket/core configuration has changed could be due to KNL NUMA
 * mode change and reboot. Update this node's config record, splitting an
 * existing record if needed. */
static void _split_node_config(node_record_t *node_ptr,
			       slurm_node_registration_status_msg_t *reg_msg)
{
	config_record_t *config_ptr, *new_config_ptr;

	if (!node_ptr)
		return;
	config_ptr = node_ptr->config_ptr;
	if (!config_ptr)
		return;

	if ((bit_set_count(config_ptr->node_bitmap) > 1) &&
	    bit_test(config_ptr->node_bitmap, node_ptr->index)) {
		new_config_ptr = create_config_record();
		memcpy(new_config_ptr, config_ptr, sizeof(config_record_t));
		new_config_ptr->cpu_spec_list =
			xstrdup(config_ptr->cpu_spec_list);
		new_config_ptr->feature = xstrdup(config_ptr->feature);
		new_config_ptr->gres = xstrdup(config_ptr->gres);
		new_config_ptr->topology_str =
			xstrdup(config_ptr->topology_str);
		bit_clear(config_ptr->node_bitmap, node_ptr->index);
		xfree(config_ptr->nodes);
		config_ptr->nodes = bitmap2node_name(config_ptr->node_bitmap);
		new_config_ptr->node_bitmap = bit_alloc(node_record_count);
		bit_set(new_config_ptr->node_bitmap, node_ptr->index);
		new_config_ptr->nodes = xstrdup(node_ptr->name);
		node_ptr->config_ptr = new_config_ptr;
		config_ptr = new_config_ptr;
	}
	config_ptr->cores = reg_msg->cores;
	config_ptr->tot_sockets = reg_msg->sockets;
}

static void _set_shared_gres_res_cores(gres_node_state_t *gres_ns)
{
	gres_node_state_t *alt_gres_ns;

	alt_gres_ns = gres_ns->alt_gres->gres_data;
	for (int i = 0; i < gres_ns->topo_cnt; i++) {
		if (!gres_ns->topo_res_core_bitmap[i])
			continue;
		for (int j = 0; j < alt_gres_ns->topo_cnt; j++) {
			if (!bit_overlap_any(gres_ns->topo_gres_bitmap[i],
					     alt_gres_ns->topo_gres_bitmap[j]))
				continue;
			FREE_NULL_BITMAP(alt_gres_ns->topo_res_core_bitmap[j]);
			alt_gres_ns->topo_res_core_bitmap[j] =
				bit_copy(gres_ns->topo_res_core_bitmap[i]);
			break;
		}
	}
}

static int _set_gpu_spec(node_record_t *node_ptr, char **reason_down)
{
	static uint32_t gpu_plugin_id = NO_VAL;
	gres_state_t *gres_state_node;
	gres_node_state_t *gres_ns;
	uint32_t res_cnt = node_ptr->res_cores_per_gpu;

	xassert(reason_down);

	xfree(node_ptr->gpu_spec);
	FREE_NULL_BITMAP(node_ptr->gpu_spec_bitmap);

	if (!res_cnt)
		return SLURM_SUCCESS;

	if (gpu_plugin_id == NO_VAL)
		gpu_plugin_id = gres_build_id("gpu");

	if (!(gres_state_node = list_find_first(node_ptr->gres_list,
						gres_find_id,
						&gpu_plugin_id))) {
		/* No GPUs but we thought there were */
		xstrfmtcat(*reason_down, "%sRestrictedCoresPerGPU=%u but no gpus on node %s",
			   *reason_down ? ", " : "", res_cnt, node_ptr->name);
		return ESLURM_RES_CORES_PER_GPU_NO;
	}

	gres_ns = gres_state_node->gres_data;
	if (!gres_ns->topo_cnt || !gres_ns->topo_core_bitmap) {
		xstrfmtcat(*reason_down, "%sRestrictedCoresPerGPU=%u but the gpus given don't have any topology on node %s.",
			   *reason_down ? ", " : "", res_cnt, node_ptr->name);
		return ESLURM_RES_CORES_PER_GPU_TOPO;
	}

	if (!gres_ns->topo_res_core_bitmap)
		gres_ns->topo_res_core_bitmap = xcalloc(gres_ns->topo_cnt,
							 sizeof(bitstr_t *));

	node_ptr->gpu_spec_bitmap = bit_alloc(node_ptr->tot_cores);
	for (int i = 0; i < gres_ns->topo_cnt; i++) {
		int cnt = 0;
		uint32_t this_gpu_res_cnt;
		if (!gres_ns->topo_core_bitmap[i])
			continue;
		FREE_NULL_BITMAP(gres_ns->topo_res_core_bitmap[i]);
		gres_ns->topo_res_core_bitmap[i] =
			bit_alloc(node_ptr->tot_cores);
		this_gpu_res_cnt = res_cnt * gres_ns->topo_gres_cnt_avail[i];
		/* info("%d has %s", i, */
		/*      bit_fmt_full(gres_ns->topo_core_bitmap[i])); */
		for (int j = 0; j < node_ptr->tot_cores; j++) {
			/* Skip general spec cores */
			if (node_ptr->node_spec_bitmap &&
			    !bit_test(node_ptr->node_spec_bitmap, j))
				continue;
			/* Only look at the ones set */
			if (!bit_test(gres_ns->topo_core_bitmap[i], j))
				continue;
			/* Skip any already set */
			if (bit_test(node_ptr->gpu_spec_bitmap, j))
				continue;
			/* info("setting %d", j); */
			bit_set(node_ptr->gpu_spec_bitmap, j);
			bit_set(gres_ns->topo_res_core_bitmap[i], j);
			if (++cnt >= this_gpu_res_cnt)
				break;
		}

		if (cnt != this_gpu_res_cnt) {
			FREE_NULL_BITMAP(node_ptr->gpu_spec_bitmap);
			xstrfmtcat(*reason_down, "%sRestrictedCoresPerGPU: We can't restrict %u core(s) per gpu. GPU %s(%d) doesn't have access to that many unique cores (%d).",
				   *reason_down ? ", " : "",
				   res_cnt, gres_ns->topo_type_name[i], i, cnt);
			return ESLURM_RES_CORES_PER_GPU_UNIQUE;
		}
	}

	if (gres_ns->alt_gres)
		_set_shared_gres_res_cores(gres_ns);

	/*
	 * We want the opposite of the possible cores so
	 * we can do a simple & on it when selecting.
	 */
	/* info("set %s", bit_fmt_full(node_ptr->gpu_spec_bitmap)); */
	node_ptr->gpu_spec = bit_fmt_full(node_ptr->gpu_spec_bitmap);
	bit_not(node_ptr->gpu_spec_bitmap);
	/* info("sending back %s", bit_fmt_full(node_ptr->gpu_spec_bitmap)); */

	return SLURM_SUCCESS;
}

/*
 * validate_node_specs - validate the node's specifications as valid,
 *	if not set state to down, in any case update last_response
 * IN slurm_msg - get node registration message it
 * OUT newly_up - set if node newly brought into service
 * RET 0 if no error, ENOENT if no such node, EINVAL if values too low
 */
extern int validate_node_specs(slurm_msg_t *slurm_msg, bool *newly_up)
{
	int error_code;
	config_record_t *config_ptr;
	node_record_t *node_ptr;
	char *reason_down = NULL;
	char *orig_features = NULL, *orig_features_act = NULL;
	uint32_t node_flags;
	time_t now = time(NULL);
	bool orig_node_avail;
	bool was_invalid_reg, was_powering_up = false, was_powered_down = false;
	static int node_features_cnt = -1;
	int sockets1, sockets2;	/* total sockets on node */
	int cores1, cores2;	/* total cores on node */
	int threads1, threads2;	/* total threads on node */
	static time_t sched_update = 0;
	static double conf_node_reg_mem_percent = -1;

	xassert(verify_lock(CONF_LOCK, READ_LOCK));

	slurm_node_registration_status_msg_t *reg_msg = slurm_msg->data;

	node_ptr = find_node_record(reg_msg->node_name);
	if (node_ptr == NULL)
		return ENOENT;

	debug3("%s: validating nodes %s in state: %s",
	       __func__, reg_msg->node_name,
	       node_state_string(node_ptr->node_state));

	if (sched_update != slurm_conf.last_update) {
		char *tmp_ptr;
		if ((tmp_ptr = conf_get_opt_str(slurm_conf.slurmctld_params,
						"node_reg_mem_percent="))) {
			if (s_p_handle_double(&conf_node_reg_mem_percent,
					      "node_reg_mem_percent",
					      tmp_ptr))
				conf_node_reg_mem_percent = -1;
			sched_update = slurm_conf.last_update;
			xfree(tmp_ptr);
		}
	}

	orig_node_avail = bit_test(avail_node_bitmap, node_ptr->index);

	config_ptr = node_ptr->config_ptr;
	error_code = SLURM_SUCCESS;

	node_ptr->protocol_version = slurm_msg->protocol_version;
	xfree(node_ptr->version);
	node_ptr->version = reg_msg->version;
	reg_msg->version = NULL;

	if (waiting_for_node_boot(node_ptr) ||
	    waiting_for_node_power_down(node_ptr))
		return SLURM_SUCCESS;
	bit_clear(booting_node_bitmap, node_ptr->index);

	if (node_features_cnt == -1)
		node_features_cnt = node_features_g_count();

	if (reg_msg->features_avail || reg_msg->features_active) {
		orig_features = xstrdup(node_ptr->features);
		if (node_ptr->features_act)
			orig_features_act = xstrdup(node_ptr->features_act);
		else
			orig_features_act = xstrdup(node_ptr->features);
	}
	if (reg_msg->features_avail) {
		if (reg_msg->features_active && !node_ptr->features_act) {
			node_ptr->features_act = node_ptr->features;
			node_ptr->features = NULL;
		} else {
			xfree(node_ptr->features);
		}
		node_ptr->features = node_features_g_node_xlate(
					reg_msg->features_avail,
					orig_features, orig_features,
					node_ptr->index);
		/* Only update if there was a change */
		if (xstrcmp(node_ptr->features, orig_features))
			(void) update_node_avail_features(node_ptr->name,
							  node_ptr->features,
							  FEATURE_MODE_IND);
	}
	if (reg_msg->features_active) {
		if (!_valid_reported_active_features(reg_msg->features_active,
						     node_ptr->features_act)) {
			char *active_changeable_features =
				_node_changeable_features(
					node_ptr->features_act);
			debug("Node %s reported active features (%s) are not a super set of node's active changeable features (%s)",
			      reg_msg->node_name,
			      reg_msg->features_active,
			      active_changeable_features);
			error_code  = EINVAL;
			xstrfmtcat(reason_down, "%sReported active features (%s) are not a superset of currently active changeable features (%s)",
				   reason_down ? ", " : "",
				   reg_msg->features_active,
				   active_changeable_features);
			xfree(active_changeable_features);
		} else {
			char *tmp_feature;
			tmp_feature =
				node_features_g_node_xlate(
					reg_msg->features_active,
					orig_features_act, orig_features,
					node_ptr->index);
			xfree(node_ptr->features_act);
			node_ptr->features_act = tmp_feature;
			(void) update_node_active_features(
				node_ptr->name, node_ptr->features_act,
				FEATURE_MODE_IND);
		}
	}
	xfree(orig_features);
	xfree(orig_features_act);

	sockets1 = reg_msg->sockets;
	cores1   = sockets1 * reg_msg->cores;
	threads1 = cores1   * reg_msg->threads;
	if (gres_node_config_unpack(reg_msg->gres_info,
				    node_ptr->name) != SLURM_SUCCESS) {
		error_code = SLURM_ERROR;
		xstrcat(reason_down, "Could not unpack gres data");
	} else if (gres_node_config_validate(node_ptr, reg_msg->threads,
					     reg_msg->cores, reg_msg->sockets,
					     (slurm_conf.conf_flags &
					      CONF_FLAG_OR),
					     &reason_down) != SLURM_SUCCESS) {
		error_code = EINVAL;
		/* reason_down set in function above */
	}
	gres_node_state_log(node_ptr->gres_list, node_ptr->name);

	if (node_ptr->res_cores_per_gpu) {
		/*
		 * We need to make gpu_spec_bitmap now that we know the cores
		 * used per gres.
		 */
		if (_set_gpu_spec(node_ptr, &reason_down))
			error_code = EINVAL;
		/* reason_down set in function above */
	} else {
		FREE_NULL_BITMAP(node_ptr->gpu_spec_bitmap);
	}

	if (!(slurm_conf.conf_flags & CONF_FLAG_OR)) {
		/* sockets1, cores1, and threads1 are set above */
		sockets2 = config_ptr->tot_sockets;
		cores2   = sockets2 * config_ptr->cores;
		threads2 = cores2   * config_ptr->threads;

		if (threads1 < threads2) {
			debug("Node %s has low socket*core*thread count "
			      "(%d < %d)",
			      reg_msg->node_name, threads1, threads2);
			error_code = EINVAL;
			if (reason_down)
				xstrcat(reason_down, ", ");
			xstrcat(reason_down, "Low socket*core*thread count");
		}

		if (reg_msg->cpus < config_ptr->cpus) {
			debug("Node %s has low cpu count (%u < %u)",
			      reg_msg->node_name, reg_msg->cpus,
			      config_ptr->cpus);
			error_code  = EINVAL;
			if (reason_down)
				xstrcat(reason_down, ", ");
			xstrcat(reason_down, "Low CPUs");
		}

		if ((error_code == SLURM_SUCCESS) && running_cons_tres() &&
		    (node_features_cnt > 0) &&
		    (reg_msg->sockets != config_ptr->tot_sockets) &&
		    (reg_msg->cores   != config_ptr->cores) &&
		    ((reg_msg->sockets * reg_msg->cores) ==
		     (config_ptr->tot_sockets * config_ptr->cores))) {
			_split_node_config(node_ptr, reg_msg);
		}
	}
	if (reg_msg->boards > reg_msg->sockets) {
		error("Node %s has more boards than sockets (%u > %u), setting board count to 1",
		      reg_msg->node_name, reg_msg->boards, reg_msg->sockets);
		reg_msg->boards = 1;
	}

	if (!(slurm_conf.conf_flags & CONF_FLAG_OR)) {
		double node_reg_mem_percent;
		if (conf_node_reg_mem_percent == -1) {
			if (IS_NODE_CLOUD(node_ptr))
				node_reg_mem_percent =
					DEFAULT_CLOUD_REG_MEM_PERCENT;
			else
				node_reg_mem_percent =
					DEFAULT_NODE_REG_MEM_PERCENT;
		} else
			node_reg_mem_percent = conf_node_reg_mem_percent;

		if (config_ptr->real_memory &&
		    ((((double)reg_msg->real_memory /
		       config_ptr->real_memory) * 100) <
		     node_reg_mem_percent)) {
			debug("Node %s has low real_memory size (%"PRIu64" / %"PRIu64") < %.2f%%",
			      reg_msg->node_name, reg_msg->real_memory,
			      config_ptr->real_memory, node_reg_mem_percent);
			error_code  = EINVAL;
			if (reason_down)
				xstrcat(reason_down, ", ");
			xstrfmtcat(reason_down, "Low RealMemory (reported:%"PRIu64" < %.2f%% of configured:%"PRIu64")",
				   reg_msg->real_memory, node_reg_mem_percent,
				   config_ptr->real_memory);
		}

		if (reg_msg->tmp_disk < config_ptr->tmp_disk) {
			debug("Node %s has low tmp_disk size (%u < %u)",
			      reg_msg->node_name, reg_msg->tmp_disk,
			      config_ptr->tmp_disk);
			error_code = EINVAL;
			if (reason_down)
				xstrcat(reason_down, ", ");
			xstrcat(reason_down, "Low TmpDisk");
		}
	}

	if (reg_msg->cpu_spec_list) {
		bitstr_t *node_spec_bitmap_old = node_ptr->node_spec_bitmap;
		char *cpu_spec_list_old = node_ptr->cpu_spec_list;

		node_ptr->node_spec_bitmap = NULL;
		node_ptr->cpu_spec_list = reg_msg->cpu_spec_list;
		reg_msg->cpu_spec_list = NULL;	/* Nothing left to free */

		if (build_node_spec_bitmap(node_ptr) != SLURM_SUCCESS)
			error_code = EINVAL;
		else if (!(slurm_conf.task_plugin_param &
			   SLURMD_SPEC_OVERRIDE) &&
			 (!node_spec_bitmap_old ||
			  !bit_equal(node_spec_bitmap_old,
				     node_ptr->node_spec_bitmap))) {
			debug("Node %s has different spec CPUs than expected (%s, %s)",
			      reg_msg->node_name, cpu_spec_list_old,
			      node_ptr->cpu_spec_list);
			error_code = EINVAL;
			if (reason_down)
				xstrcat(reason_down, ", ");
			xstrcat(reason_down, "CoreSpec differ");
		}

		/* Regenerate core spec count and effective cpus */
		if (node_ptr->cpu_spec_list &&
		    (slurm_conf.task_plugin_param & SLURMD_SPEC_OVERRIDE)) {
			if (node_ptr->cpu_spec_list) {
				build_node_spec_bitmap(node_ptr);
				node_conf_convert_cpu_spec_list(node_ptr);
			} else if (node_ptr->core_spec_cnt) {
				node_conf_select_spec_cores(node_ptr);
			}
			node_ptr->cpus_efctv =
				node_ptr->cpus -
				(node_ptr->core_spec_cnt * node_ptr->tpc);
		}

		xfree(cpu_spec_list_old);
		FREE_NULL_BITMAP(node_spec_bitmap_old);
	}

	if ((slurm_conf.task_plugin_param & SLURMD_SPEC_OVERRIDE) &&
	    reg_msg->mem_spec_limit) {
		node_ptr->mem_spec_limit = reg_msg->mem_spec_limit;
	}

	xfree(node_ptr->arch);
	node_ptr->arch = reg_msg->arch;
	reg_msg->arch = NULL;	/* Nothing left to free */

	xfree(node_ptr->os);
	node_ptr->os = reg_msg->os;
	reg_msg->os = NULL;	/* Nothing left to free */

	xfree(node_ptr->parameters);
	node_ptr->parameters = reg_msg->parameters;
	reg_msg->parameters = NULL;

	if (node_ptr->cpu_load != reg_msg->cpu_load) {
		node_ptr->cpu_load = reg_msg->cpu_load;
		node_ptr->cpu_load_time = now;
		last_node_update = now;
	}
	if (node_ptr->free_mem != reg_msg->free_mem) {
		node_ptr->free_mem = reg_msg->free_mem;
		node_ptr->free_mem_time = now;
		last_node_update = now;
	}

	if (IS_NODE_NO_RESPOND(node_ptr) ||
	    IS_NODE_POWERING_UP(node_ptr) ||
	    IS_NODE_POWERING_DOWN(node_ptr) ||
	    IS_NODE_POWERED_DOWN(node_ptr)) {
		was_powered_down = IS_NODE_POWERED_DOWN(node_ptr);

		info("Node %s now responding", node_ptr->name);

		/*
		 * Set last_busy in case that the node came up out of band or
		 * came up after ResumeTimeout so that it can be suspended at a
		 * later point.
		 */
		if (IS_NODE_POWERING_UP(node_ptr) ||
		    IS_NODE_POWERED_DOWN(node_ptr))
			node_ptr->last_busy = now;

		/*
		 * Set last_response if it's expected. Otherwise let it get
		 * marked at "unexpectedly rebooted". Not checked with
		 * IS_NODE_POWERED_DOWN() above to allow ReturnToService !=2
		 * catch nodes [re]booting unexpectedly.
		 */
		if (IS_NODE_POWERING_UP(node_ptr)) {
			node_ptr->last_response = now;
			was_powering_up = true;

			if (IS_NODE_REBOOT_ISSUED(node_ptr) &&
			    (slurm_conf.health_check_node_state &
			     HEALTH_CHECK_REBOOT_ONLY))
				run_health_check_individual(node_ptr);
			node_ptr->node_state &= (~NODE_STATE_REBOOT_ISSUED);
		}

		node_ptr->node_state &= (~NODE_STATE_NO_RESPOND);
		node_ptr->node_state &= (~NODE_STATE_POWERING_UP);
		node_ptr->node_state &= (~NODE_STATE_POWERED_DOWN);
		node_ptr->node_state &= (~NODE_STATE_POWERING_DOWN);
		if (!is_node_in_maint_reservation(node_ptr->index))
			node_ptr->node_state &= (~NODE_STATE_MAINT);

		bit_clear(power_down_node_bitmap, node_ptr->index);
		bit_set(power_up_node_bitmap, node_ptr->index);

		last_node_update = now;

		if (was_powered_down)
			clusteracct_storage_g_node_up(acct_db_conn, node_ptr,
						      now);
	}

	/*
	 * Apply slurmd-supplied fields (extra, instance_id, instance_type,
	 * topology) when slurmd's reported boot_time is later than the
	 * last time we successfully heard from the node. This fires on the
	 * first registration (last_response == 0) and on an actual node
	 * reboot (the same condition the reboot-detection branch below
	 * uses to mark the node "unexpectedly rebooted"). Steady-state
	 * pings and slurmd restarts without an actual reboot have
	 * boot_time < last_response and are skipped, so admin overrides
	 * via scontrol update node ... are not clobbered. last_response is
	 * state-saved, so the gate also survives slurmctld restart.
	 */
	if (node_ptr->boot_time > node_ptr->last_response) {
		bool update_db = false;

		if (reg_msg->extra) {
			data_t *data = NULL;

			if (extra_constraints_enabled() && reg_msg->extra[0] &&
			    serialize_g_string_to_data(&data, reg_msg->extra,
						       strlen(reg_msg->extra),
						       MIME_TYPE_JSON)) {
				info("Failed to decode extra \"%s\" for node %s",
				     reg_msg->extra, node_ptr->name);
			}
			FREE_NULL_DATA(node_ptr->extra_data);
			node_ptr->extra_data = data;

			/*
			 * Always set the extra field from the registration
			 * message, even if decoding failed.
			 */
			xfree(node_ptr->extra);
			if (reg_msg->extra[0]) {
				node_ptr->extra = xstrdup(reg_msg->extra);
				/*
				 * Skip db updates for extra field changes,
				 * otherwise we'll overwhelm it with event
				 * records if someone is updating these
				 * constantly.
				 */
				// update_db = true;
			}
		}

		if (reg_msg->instance_id) {
			xfree(node_ptr->instance_id);
			if (reg_msg->instance_id[0]) {
				node_ptr->instance_id =
					xstrdup(reg_msg->instance_id);
				update_db = true;
			}
		}

		if (reg_msg->instance_type) {
			xfree(node_ptr->instance_type);
			if (reg_msg->instance_type[0]) {
				node_ptr->instance_type =
					xstrdup(reg_msg->instance_type);
				update_db = true;
			}
		}

		if (update_db)
			clusteracct_storage_g_node_update(acct_db_conn,
							  node_ptr);

		if (reg_msg->dynamic_conf &&
		    xstrcasestr(reg_msg->dynamic_conf, "topology=")) {
			int rc = SLURM_SUCCESS;
			char *tmp_conf = NULL;
			slurm_conf_node_t *conf_node = NULL;
			s_p_hashtbl_t *node_hashtbl = NULL;

			if (!xstrncasecmp(reg_msg->dynamic_conf,
					  "NodeName=", 9))
				tmp_conf = xstrdup(reg_msg->dynamic_conf);
			else
				tmp_conf =
					xstrdup_printf("NodeName=%s %s",
						       node_ptr->name,
						       reg_msg->dynamic_conf);

			if (!(conf_node = slurm_conf_parse_nodeline(
				      tmp_conf, &node_hashtbl))) {
				error("Failed to parse dynamic nodeline '%s'",
				      reg_msg->dynamic_conf);
				error_code = EINVAL;
				xstrfmtcat(reason_down,
					   "%sFailed to parse dynamic_conf nodeline",
					   reason_down ? ", " : "");
			} else if (conf_node->topology_str &&
				   xstrcmp(conf_node->topology_str,
					   node_ptr->topology_str) &&
				   ((rc = node_mgr_set_node_topology(
					     node_ptr,
					     conf_node->topology_str)))) {
				error_code = rc;
				xstrfmtcat(reason_down,
					   "%sFailed to set topology '%s'",
					   reason_down ? ", " : "",
					   conf_node->topology_str);
			}
			s_p_hashtbl_destroy(node_hashtbl);
			xfree(tmp_conf);
		}
	}

	was_invalid_reg = IS_NODE_INVALID_REG(node_ptr);
	node_ptr->node_state &= ~NODE_STATE_INVALID_REG;
	node_flags = node_ptr->node_state & NODE_STATE_FLAGS;

	if (error_code) {
		node_ptr->node_state |= NODE_STATE_INVALID_REG;
		if (!was_invalid_reg) {
			error("Setting node %s state to INVAL with reason:%s",
			       reg_msg->node_name, reason_down);

			if (was_powering_up || was_powered_down)
				kill_running_job_by_node_ptr(node_ptr);
		}

		if (!IS_NODE_DOWN(node_ptr)
			&& !IS_NODE_DRAIN(node_ptr)
			&& ! IS_NODE_FAIL(node_ptr)) {
			drain_nodes(reg_msg->node_name, reason_down,
			            slurm_conf.slurm_user_id);
		} else if (xstrcmp(node_ptr->reason, reason_down)) {
			if (was_invalid_reg) {
				error("Setting node %s state to INVAL with reason:%s",
				       reg_msg->node_name, reason_down);
			}
			xfree(node_ptr->reason);
			set_node_reason(node_ptr, reason_down, now);
		}
		last_node_update = time (NULL);
	} else if (reg_msg->status == ESLURMD_PROLOG_FAILED
		   || reg_msg->status == ESLURMD_SETUP_ENVIRONMENT_ERROR) {
		if (!IS_NODE_DRAIN(node_ptr) && !IS_NODE_FAIL(node_ptr)) {
			char *reason;
			error("%s: Prolog or job env setup failure on node %s, "
			      "draining the node",
			      __func__, reg_msg->node_name);
			if (reg_msg->status == ESLURMD_PROLOG_FAILED)
				reason = "Prolog error";
			else
				reason = "Job env setup error";
			drain_nodes(reg_msg->node_name, reason,
			            slurm_conf.slurm_user_id);
			last_node_update = time (NULL);
		}
	} else {
		if (IS_NODE_UNKNOWN(node_ptr) || IS_NODE_FUTURE(node_ptr)) {
			bool was_future = IS_NODE_FUTURE(node_ptr);
			debug("validate_node_specs: node %s registered with "
			      "%u jobs",
			      reg_msg->node_name,reg_msg->job_count);
			if (IS_NODE_FUTURE(node_ptr)) {
				if (IS_NODE_MAINT(node_ptr) &&
				    !is_node_in_maint_reservation(
					    node_ptr->index))
					node_flags &= (~NODE_STATE_MAINT);
				node_flags &= (~NODE_STATE_REBOOT_REQUESTED);
				node_flags &= (~NODE_STATE_REBOOT_ISSUED);
			}
			if (reg_msg->job_count) {
				node_ptr->node_state = NODE_STATE_ALLOCATED |
					node_flags;
			} else {
				node_ptr->node_state = NODE_STATE_IDLE |
					node_flags;
				node_ptr->last_busy = now;
			}
			last_node_update = now;

			/* don't send this on a slurmctld unless needed */
			if (was_future || /* always send FUTURE checkins */
			    (slurmctld_init_db &&
			     !IS_NODE_DRAIN(node_ptr) &&
			     !IS_NODE_FAIL(node_ptr))) {
				/* reason information is handled in
				   clusteracct_storage_g_node_up()
				*/
				clusteracct_storage_g_node_up(
					acct_db_conn, node_ptr, now);
			}
		} else if (IS_NODE_DOWN(node_ptr) &&
			   ((slurm_conf.ret2service == RETURN_TO_SERVICE_ALL) ||
			    IS_NODE_REBOOT_ISSUED(node_ptr) ||
			    ((slurm_conf.ret2service ==
			      RETURN_TO_SERVICE_NON_RESP) &&
			     !xstrcmp(node_ptr->reason, "Not responding") &&
			     (node_ptr->boot_time <
			      node_ptr->last_response)))) {
			if (IS_NODE_REBOOT_ISSUED(node_ptr) &&
			    (slurm_conf.health_check_node_state &
			     HEALTH_CHECK_REBOOT_ONLY))
				run_health_check_individual(node_ptr);
			node_flags &= (~NODE_STATE_REBOOT_ISSUED);
			if (node_ptr->next_state != NO_VAL)
				node_flags &= (~NODE_STATE_DRAIN);

			if ((node_ptr->next_state & NODE_STATE_BASE) ==
			    NODE_STATE_DOWN) {
				node_ptr->node_state = NODE_STATE_DOWN |
						       node_flags;
				set_node_reason(node_ptr, "reboot complete",
						now);
			} else if (reg_msg->job_count) {
				node_ptr->node_state = NODE_STATE_ALLOCATED |
						       node_flags;
			} else {
				node_ptr->node_state = NODE_STATE_IDLE |
						       node_flags;
				node_ptr->last_busy = now;
			}
			node_ptr->next_state = NO_VAL;
			node_ptr->resume_after = 0;
			bit_clear(rs_node_bitmap, node_ptr->index);
			bit_clear(asap_node_bitmap, node_ptr->index);

			info("node %s returned to service",
			     reg_msg->node_name);
			trigger_node_up(node_ptr);
			last_node_update = now;
			if (!IS_NODE_DRAIN(node_ptr)
			    && !IS_NODE_DOWN(node_ptr)
			    && !IS_NODE_FAIL(node_ptr)) {
				/* reason information is handled in
				 * clusteracct_storage_g_node_up() */
				clusteracct_storage_g_node_up(
					acct_db_conn, node_ptr, now);
			}
		} else if (!IS_NODE_DRAINED(node_ptr) &&
			   !IS_NODE_MAINT(node_ptr) &&
			   node_ptr->last_response &&
			   (node_ptr->boot_time > node_ptr->last_response) &&
			   (slurm_conf.ret2service != RETURN_TO_SERVICE_ALL)) {
			if (!node_ptr->reason ||
			    (node_ptr->reason &&
			     (!xstrcmp(node_ptr->reason, "Not responding") ||
			      IS_NODE_REBOOT_REQUESTED(node_ptr)))) {
				xfree(node_ptr->reason);
				node_ptr->reason_time = now;
				node_ptr->reason_uid = slurm_conf.slurm_user_id;
				node_ptr->reason = xstrdup(
					"Node unexpectedly rebooted");
			}
			/* If a reboot was requested, cancel it. */
			if (IS_NODE_REBOOT_REQUESTED(node_ptr)) {
				node_ptr->node_state &=
					(~NODE_STATE_REBOOT_REQUESTED);
				if ((node_ptr->next_state & NODE_STATE_FLAGS) &
				    NODE_STATE_UNDRAIN)
					/*
					 * Not using _undo_reboot_asap() so that
					 * the reason is preserved.
					 */
					node_ptr->node_state &=
						(~NODE_STATE_DRAIN);
				bit_clear(rs_node_bitmap, node_ptr->index);
				bit_clear(asap_node_bitmap, node_ptr->index);
			}
			info("%s: Node %s unexpectedly rebooted boot_time=%ld last response=%ld",
			     __func__, reg_msg->node_name, node_ptr->boot_time,
			     node_ptr->last_response);
			_make_node_down(node_ptr, now);
			kill_running_job_by_node_ptr(node_ptr);
			last_node_update = now;
			reg_msg->job_count = 0;
		} else if (IS_NODE_ALLOCATED(node_ptr) &&
			   (reg_msg->job_count == 0)) { /* job vanished */
			node_ptr->node_state = NODE_STATE_IDLE | node_flags;
			node_ptr->last_busy = now;
			last_node_update = now;
		} else if (IS_NODE_COMPLETING(node_ptr) &&
			   (reg_msg->job_count == 0)) { /* job already done */
			node_ptr->node_state &= (~NODE_STATE_COMPLETING);
			last_node_update = now;
			bit_clear(cg_node_bitmap, node_ptr->index);
		} else if (IS_NODE_IDLE(node_ptr) &&
			   (reg_msg->job_count != 0)) {
			if (node_ptr->run_job_cnt != 0) {
				node_ptr->node_state = NODE_STATE_ALLOCATED |
						       node_flags;
				error("Invalid state for node %s, was IDLE "
			      	      "with %u running jobs",
			      	      node_ptr->name, reg_msg->job_count);
			}
			/*
			 * there must be completing job(s) on this node since
			 * reg_msg->job_count was set (run_job_cnt +
			 * comp_job_cnt) in validate_jobs_on_node()
			 */
			if (node_ptr->comp_job_cnt != 0) {
				node_ptr->node_state |= NODE_STATE_COMPLETING;
				bit_set(cg_node_bitmap, node_ptr->index);
			}
			last_node_update = now;
		}
		if (IS_NODE_IDLE(node_ptr)) {
			node_ptr->owner = NO_VAL;
			xfree(node_ptr->mcs_label);
		}

		_sync_bitmaps(node_ptr, reg_msg->job_count);
	}

	xfree(reason_down);
	if (reg_msg->energy)
		memcpy(node_ptr->energy, reg_msg->energy,
		       sizeof(acct_gather_energy_t));

	node_ptr->last_response = now;
	node_ptr->boot_req_time = (time_t) 0;
	node_ptr->power_save_req_time = (time_t) 0;

	*newly_up = (!orig_node_avail &&
		     bit_test(avail_node_bitmap, node_ptr->index));

	if (IS_NODE_CLOUD(node_ptr) ||
	    IS_NODE_DYNAMIC_FUTURE(node_ptr) ||
	    IS_NODE_DYNAMIC_NORM(node_ptr)) {
		/* Get IP of slurmd */
		char *comm_name = _get_msg_hostname(slurm_msg);

		set_node_comm_name(node_ptr, comm_name, reg_msg->hostname);

		xfree(comm_name);
	}

	if (was_powering_up || was_powered_down)
		log_flag(POWER, "Node %s/%s/%s powered up with instance_id=%s, instance_type=%s",
			 node_ptr->name, node_ptr->node_hostname,
			 node_ptr->comm_name, reg_msg->instance_id,
			 reg_msg->instance_type);

	return error_code;
}

/* Sync idle, share, and avail_node_bitmaps for a given node */
static void _sync_bitmaps(node_record_t *node_ptr, int job_count)
{
	if (job_count == 0) {
		bit_set (idle_node_bitmap, node_ptr->index);
		bit_set (share_node_bitmap, node_ptr->index);
	}
	if (IS_NODE_DOWN(node_ptr) || IS_NODE_DRAIN(node_ptr) ||
	    IS_NODE_FAIL(node_ptr) || IS_NODE_NO_RESPOND(node_ptr))
		bit_clear (avail_node_bitmap, node_ptr->index);
	else
		make_node_avail(node_ptr);
	if (IS_NODE_DOWN(node_ptr))
		bit_clear (up_node_bitmap, node_ptr->index);
	else
		bit_set   (up_node_bitmap, node_ptr->index);
}

static void _node_did_resp(node_record_t *node_ptr)
{
	uint32_t node_flags;
	time_t now = time(NULL);

	if (waiting_for_node_boot(node_ptr) ||
	    waiting_for_node_power_down(node_ptr) ||
	    IS_NODE_FUTURE(node_ptr))
		return;
	node_ptr->last_response = now;
	if (IS_NODE_NO_RESPOND(node_ptr) || IS_NODE_POWERING_UP(node_ptr)) {
		info("Node %s now responding", node_ptr->name);
		node_ptr->node_state &= (~NODE_STATE_NO_RESPOND);
		node_ptr->node_state &= (~NODE_STATE_POWERING_UP);
		if (!is_node_in_maint_reservation(node_ptr->index))
			node_ptr->node_state &= (~NODE_STATE_MAINT);
		last_node_update = now;
	}
	node_flags = node_ptr->node_state & NODE_STATE_FLAGS;
	if (IS_NODE_UNKNOWN(node_ptr)) {
		node_ptr->last_busy = now;
		if (node_ptr->run_job_cnt) {
			node_ptr->node_state = NODE_STATE_ALLOCATED |
					       node_flags;
		} else
			node_ptr->node_state = NODE_STATE_IDLE | node_flags;
		last_node_update = now;
		if (!IS_NODE_DRAIN(node_ptr) && !IS_NODE_FAIL(node_ptr)) {
			clusteracct_storage_g_node_up(acct_db_conn,
						      node_ptr, now);
		}
	}
	if (IS_NODE_DOWN(node_ptr) && !IS_NODE_INVALID_REG(node_ptr) &&
	    ((slurm_conf.ret2service == RETURN_TO_SERVICE_ALL) ||
	     (node_ptr->boot_req_time != 0) ||
	     ((slurm_conf.ret2service == RETURN_TO_SERVICE_NON_RESP) &&
	      !xstrcmp(node_ptr->reason, "Not responding")))) {
		node_ptr->last_busy = now;
		node_ptr->node_state = NODE_STATE_IDLE | node_flags;
		node_ptr->resume_after = 0;
		info("node_did_resp: node %s returned to service",
		     node_ptr->name);
		trigger_node_up(node_ptr);
		last_node_update = now;
		if (!IS_NODE_DRAIN(node_ptr) && !IS_NODE_FAIL(node_ptr)) {
			/* reason information is handled in
			   clusteracct_storage_g_node_up()
			*/
			clusteracct_storage_g_node_up(acct_db_conn,
						      node_ptr, now);
		}
	}
	if (IS_NODE_IDLE(node_ptr) && !IS_NODE_COMPLETING(node_ptr)) {
		bit_set (idle_node_bitmap, node_ptr->index);
		bit_set (share_node_bitmap, node_ptr->index);
	}
	if (IS_NODE_DOWN(node_ptr) ||
	    IS_NODE_DRAIN(node_ptr) ||
	    IS_NODE_FAIL(node_ptr) ||
	    (IS_NODE_POWER_DOWN(node_ptr) && !IS_NODE_ALLOCATED(node_ptr))) {
		bit_clear (avail_node_bitmap, node_ptr->index);
	} else
		bit_set   (avail_node_bitmap, node_ptr->index);
	if (IS_NODE_DOWN(node_ptr))
		bit_clear (up_node_bitmap, node_ptr->index);
	else
		bit_set   (up_node_bitmap, node_ptr->index);
}

/*
 * node_did_resp - record that the specified node is responding
 * IN name - name of the node
 */
void node_did_resp (char *name)
{
	node_record_t *node_ptr;
	node_ptr = find_node_record (name);

	xassert(verify_lock(CONF_LOCK, READ_LOCK));

	if (node_ptr == NULL) {
		error ("node_did_resp unable to find node %s", name);
		return;
	}
	_node_did_resp(node_ptr);
	debug2("node_did_resp %s",name);
}

/*
 * node_not_resp - record that the specified node is not responding
 * IN name - name of the node
 * IN msg_time - time message was sent
 */
void node_not_resp (char *name, time_t msg_time, slurm_msg_type_t resp_type)
{
	node_record_t *node_ptr;

	node_ptr = find_node_record (name);

	if (node_ptr == NULL) {
		error ("node_not_resp unable to find node %s", name);
		return;
	}

	/* If the slurmd on the node responded with something we don't
	 * want to ever set the node down, so mark that the node
	 * responded, but for whatever reason there was a
	 * communication error.  This makes it so we don't mark the
	 * node down if the slurmd really is there (Wrong protocol
	 * version or munge issue or whatever) so we don't kill
	 * any running jobs.  RESPONSE_FORWARD_FAILED means we
	 * couldn't contact the slurmd.
	 * last_response could be in the future if boot in progress.
	 */
	if (resp_type != RESPONSE_FORWARD_FAILED) {
		node_ptr->last_response = MAX(msg_time - 1,
					      node_ptr->last_response);
	}

	if (!IS_NODE_DOWN(node_ptr)) {
		/* Logged by node_no_resp_msg() on periodic basis */
		node_ptr->not_responding = true;
	}

	if (IS_NODE_NO_RESPOND(node_ptr) ||
	    IS_NODE_POWERING_DOWN(node_ptr) ||
	    IS_NODE_POWERED_DOWN(node_ptr))
		return;		/* Already known to be not responding */

	if (node_ptr->last_response >= msg_time) {
		debug("node_not_resp: node %s responded since msg sent",
		      node_ptr->name);
		return;
	}

	node_ptr->node_state |= NODE_STATE_NO_RESPOND;
	last_node_update = time(NULL);
	bit_clear (avail_node_bitmap, node_ptr->index);
}

/* For every node with the "not_responding" flag set, clear the flag
 * and log that the node is not responding using a hostlist expression */
extern void node_no_resp_msg(void)
{
	int i;
	node_record_t *node_ptr;
	char *host_str = NULL;
	hostlist_t *no_resp_hostlist = NULL;

	for (i = 0; (node_ptr = next_node(&i)); i++) {
		if (!node_ptr->not_responding ||
		    IS_NODE_POWERED_DOWN(node_ptr) ||
		    IS_NODE_POWERING_DOWN(node_ptr) ||
		    IS_NODE_POWERING_UP(node_ptr))
			continue;
		if (no_resp_hostlist) {
			(void) hostlist_push_host(no_resp_hostlist,
						  node_ptr->name);
		} else
			no_resp_hostlist = hostlist_create(node_ptr->name);
		node_ptr->not_responding = false;
 	}
	if (no_resp_hostlist) {
		hostlist_uniq(no_resp_hostlist);
		host_str = hostlist_ranged_string_xmalloc(no_resp_hostlist);
		error("Nodes %s not responding", host_str);
		xfree(host_str);
		hostlist_destroy(no_resp_hostlist);
	}
}

/*
 * set_node_down - make the specified compute node's state DOWN and
 *	kill jobs as needed
 * IN name - name of the node
 * IN reason - why the node is DOWN
 */
void set_node_down (char *name, char *reason)
{
	node_record_t *node_ptr;

	node_ptr = find_node_record (name);
	if (node_ptr == NULL) {
		error ("set_node_down unable to find node %s", name);
		return;
	}
	set_node_down_ptr (node_ptr, reason);
}

/*
 * set_node_down_ptr - make the specified compute node's state DOWN and
 *	kill jobs as needed
 * IN node_ptr - node_ptr to the node
 * IN reason - why the node is DOWN
 */
void set_node_down_ptr(node_record_t *node_ptr, char *reason)
{
	time_t now = time(NULL);

	xassert(node_ptr);

	set_node_reason(node_ptr, reason, now);
	_make_node_down(node_ptr, now);
	(void) kill_running_job_by_node_ptr(node_ptr);
	_sync_bitmaps(node_ptr, 0);
}

/*
 * is_node_down - determine if the specified node's state is DOWN
 * IN name - name of the node
 * RET true if node exists and is down, otherwise false
 */
bool is_node_down (char *name)
{
	node_record_t *node_ptr;

	node_ptr = find_node_record (name);
	if (node_ptr == NULL) {
		error ("is_node_down unable to find node %s", name);
		return false;
	}

	if (IS_NODE_DOWN(node_ptr))
		return true;
	return false;
}

/*
 * is_node_resp - determine if the specified node's state is responding
 * IN name - name of the node
 * RET true if node exists and is responding, otherwise false
 */
bool is_node_resp (char *name)
{
	node_record_t *node_ptr;

	node_ptr = find_node_record (name);
	if (node_ptr == NULL) {
		error ("is_node_resp unable to find node %s", name);
		return false;
	}

	if (IS_NODE_NO_RESPOND(node_ptr))
		return false;
	return true;
}

/*
 * msg_to_slurmd - send given msg_type (REQUEST_RECONFIGURE or REQUEST_SHUTDOWN)
 * to every slurmd
 */
void msg_to_slurmd (slurm_msg_type_t msg_type)
{
	int i;
	shutdown_msg_t *shutdown_req;
	agent_arg_t *kill_agent_args;
	node_record_t *node_ptr;

	kill_agent_args = xmalloc (sizeof (agent_arg_t));
	kill_agent_args->msg_type = msg_type;
	kill_agent_args->retry = 0;
	kill_agent_args->hostlist = hostlist_create(NULL);
	if (msg_type == REQUEST_SHUTDOWN) {
 		shutdown_req = xmalloc(sizeof(shutdown_msg_t));
		shutdown_req->options = 0;
		kill_agent_args->msg_args = shutdown_req;
	}

	kill_agent_args->protocol_version = SLURM_PROTOCOL_VERSION;

	for (i = 0; (node_ptr = next_node(&i)); i++) {
		if (IS_NODE_EXTERNAL(node_ptr))
			continue;
		if (IS_NODE_FUTURE(node_ptr))
			continue;
		if (IS_NODE_CLOUD(node_ptr) &&
		    (IS_NODE_POWERED_DOWN(node_ptr) ||
		     IS_NODE_POWERING_DOWN(node_ptr)))
			continue;
		if (kill_agent_args->protocol_version >
		    node_record_table_ptr[node_ptr->index]->protocol_version)
			kill_agent_args->protocol_version =
				node_record_table_ptr[node_ptr->index]->
				protocol_version;
		hostlist_push_host(kill_agent_args->hostlist, node_ptr->name);
		kill_agent_args->node_count++;
	}

	if (kill_agent_args->node_count == 0) {
		hostlist_destroy(kill_agent_args->hostlist);
		xfree (kill_agent_args);
	} else {
		debug ("Spawning agent msg_type=%s", rpc_num2string(msg_type));
		set_agent_arg_r_uid(kill_agent_args, SLURM_AUTH_UID_ANY);
		agent_queue_request(kill_agent_args);
	}
}

/*
 * Specialized version of msg_to_slurmd that handles cross-version issues
 * when running configless.
 *
 * Since the REQUEST_RECONFIGURE message had no body, you could get away with
 * sending under the oldest format of any slurmd attached to the system.
 *
 * For configless, this would mean nothing gets sent to anyone, and those
 * older slurmds get REQUEST_RECONFIGURE_WITH_CONFIG and ignore it.
 *
 * So explicitly split the pool into three groups.
 */
#define RELEVANT_VER 4
extern void push_reconfig_to_slurmd(void)
{
	agent_arg_t *ver_args[RELEVANT_VER] = { 0 }, *curr_args;
	node_record_t *node_ptr;
	int ver;

	ver_args[0] = xmalloc(sizeof(agent_arg_t));
	ver_args[0]->msg_type = REQUEST_RECONFIGURE_WITH_CONFIG;
	ver_args[0]->protocol_version = SLURM_PROTOCOL_VERSION;

	ver_args[1] = xmalloc(sizeof(agent_arg_t));
	ver_args[1]->msg_type = REQUEST_RECONFIGURE_WITH_CONFIG;
	ver_args[1]->protocol_version = SLURM_ONE_BACK_PROTOCOL_VERSION;

	ver_args[2] = xmalloc(sizeof(agent_arg_t));
	ver_args[2]->msg_type = REQUEST_RECONFIGURE_WITH_CONFIG;
	ver_args[2]->protocol_version = SLURM_TWO_BACK_PROTOCOL_VERSION;

	ver_args[3] = xmalloc(sizeof(agent_arg_t));
	ver_args[3]->msg_type = REQUEST_RECONFIGURE_WITH_CONFIG;
	ver_args[3]->protocol_version = SLURM_MIN_PROTOCOL_VERSION;

	for (int i = 0; (node_ptr = next_node(&i)); i++) {
		if (IS_NODE_FUTURE(node_ptr) || IS_NODE_EXTERNAL(node_ptr))
			continue;
		if (IS_NODE_CLOUD(node_ptr) &&
		    (IS_NODE_POWERED_DOWN(node_ptr) ||
		     IS_NODE_POWERING_DOWN(node_ptr)))
			continue;

		for (ver = 0; ver < RELEVANT_VER; ver++) {
			curr_args = ver_args[ver];
			if (node_ptr->protocol_version <
			    curr_args->protocol_version)
				continue;
			if (!curr_args->hostlist) {
				curr_args->hostlist = hostlist_create(NULL);
				curr_args->msg_args = new_config_response(true);
			}
			hostlist_push_host(curr_args->hostlist, node_ptr->name);
			curr_args->node_count++;
			break;
		}
	}

	for (ver = 0; ver < RELEVANT_VER; ver++) {
		/* This movement is needed to prevent a stack smash */
		curr_args = ver_args[ver];
		ver_args[ver] = NULL;
		if (!curr_args->node_count) {
			xfree(curr_args);
			continue;
		}

		debug("Spawning agent msg_type=%s version=%u",
		      rpc_num2string(curr_args->msg_type),
		      curr_args->protocol_version);
		set_agent_arg_r_uid(curr_args, SLURM_AUTH_UID_ANY);
		agent_queue_request(curr_args);
	}
}


/*
 * make_node_alloc - flag specified node as allocated to a job
 * IN node_ptr - pointer to node being allocated
 * IN job_ptr  - pointer to job that is starting
 */
extern void make_node_alloc(node_record_t *node_ptr, job_record_t *job_ptr)
{
	uint32_t node_flags;

	(node_ptr->run_job_cnt)++;
	bit_clear(idle_node_bitmap, node_ptr->index);
	if (job_ptr->details && (job_ptr->details->share_res == 0)) {
		bit_clear(share_node_bitmap, node_ptr->index);
		(node_ptr->no_share_job_cnt)++;
	}

	if ((job_ptr->details &&
	     (job_ptr->details->whole_node & WHOLE_NODE_USER)) ||
	    (job_ptr->part_ptr &&
	     (job_ptr->part_ptr->flags & PART_FLAG_EXCLUSIVE_USER))) {
		node_ptr->owner_job_cnt++;
		node_ptr->owner = job_ptr->user_id;
	}

	if (slurm_mcs_get_select(job_ptr) == 1) {
		xfree(node_ptr->mcs_label);
		node_ptr->mcs_label = xstrdup(job_ptr->mcs_label);
	}

	node_flags = node_ptr->node_state & NODE_STATE_FLAGS;
	node_ptr->node_state = NODE_STATE_ALLOCATED | node_flags;
	xfree(node_ptr->reason);
	node_ptr->reason_time = 0;
	node_ptr->reason_uid = NO_VAL;

	if (job_ptr && job_ptr->part_ptr &&
	    (job_ptr->part_ptr->flags & PART_FLAG_PDOI)) {
		node_ptr->node_state |= NODE_STATE_POWER_DOWN;
	}

	last_node_update = time(NULL);
}

/* make_node_avail - flag specified node as available */
extern void make_node_avail(node_record_t *node_ptr)
{
	if (IS_NODE_POWER_DOWN(node_ptr) || IS_NODE_POWERING_DOWN(node_ptr))
		return;
	bit_set(avail_node_bitmap, node_ptr->index);

	/*
	 * If we are in the middle of a backfill cycle, this bitmap is
	 * used (when bf_continue is enabled) to avoid scheduling lower
	 * priority jobs on to newly available resources.
	 */
	bit_set(bf_ignore_node_bitmap, node_ptr->index);
}

extern void node_mgr_make_node_blocked(job_record_t *job_ptr, bool set)
{
	bitstr_t *tmp_bitmap;
	node_record_t *node_ptr;

	if (!IS_JOB_WHOLE_TOPO(job_ptr))
		return;

	if (!job_ptr->job_resrcs || !job_ptr->job_resrcs->node_bitmap)
		return;

	tmp_bitmap = bit_copy(job_ptr->job_resrcs->node_bitmap);
	topology_g_whole_topo(tmp_bitmap, job_ptr->part_ptr->topology_idx);
	bit_and_not(tmp_bitmap, job_ptr->job_resrcs->node_bitmap);

	for (int i = 0; (node_ptr = next_node_bitmap(tmp_bitmap, &i)); i++) {
		if (set)
			node_ptr->node_state |= NODE_STATE_BLOCKED;
		else
			node_ptr->node_state &= (~NODE_STATE_BLOCKED);
	}

	FREE_NULL_BITMAP(tmp_bitmap);
}

/* make_node_comp - flag specified node as completing a job
 * IN node_ptr - pointer to node marked for completion of job
 * IN job_ptr - pointer to job that is completing
 * IN suspended - true if job was previously suspended
 */
extern void make_node_comp(node_record_t *node_ptr, job_record_t *job_ptr,
			   bool suspended)
{
	uint32_t node_flags;
	time_t now = time(NULL);

	xassert(node_ptr);
	if (suspended) {
		if (node_ptr->sus_job_cnt) {
			(node_ptr->sus_job_cnt)--;
		} else {
			error("%s: %pJ node %s sus_job_cnt underflow", __func__,
			      job_ptr, node_ptr->name);
		}
	} else {
		if (node_ptr->run_job_cnt) {
			(node_ptr->run_job_cnt)--;
		} else {
			error("%s: %pJ node %s run_job_cnt underflow", __func__,
			      job_ptr, node_ptr->name);
		}
		if (job_ptr->details && (job_ptr->details->share_res == 0)) {
			if (node_ptr->no_share_job_cnt) {
				(node_ptr->no_share_job_cnt)--;
			} else {
				error("%s: %pJ node %s no_share_job_cnt underflow",
				      __func__, job_ptr, node_ptr->name);
			}
			if (node_ptr->no_share_job_cnt == 0)
				bit_set(share_node_bitmap, node_ptr->index);
		}
	}

	/* Sync up conditionals with deallocate_nodes() */
	if (!IS_NODE_DOWN(node_ptr) &&
	    !IS_NODE_POWERED_DOWN(node_ptr) &&
	    !IS_NODE_POWERING_UP(node_ptr)) {
		/* Don't verify RPC if node in DOWN or POWER_UP state */
		(node_ptr->comp_job_cnt)++;
		node_ptr->node_state |= NODE_STATE_COMPLETING;
		bit_set(cg_node_bitmap, node_ptr->index);
	}
	node_flags = node_ptr->node_state & NODE_STATE_FLAGS;

	if (!node_ptr->run_job_cnt && !node_ptr->comp_job_cnt) {
		node_ptr->last_busy = now;
		bit_set(idle_node_bitmap, node_ptr->index);
	}
	if (IS_NODE_DRAIN(node_ptr) || IS_NODE_FAIL(node_ptr)) {
		trigger_node_draining(node_ptr);
		if (!node_ptr->run_job_cnt && !node_ptr->comp_job_cnt) {
			trigger_node_drained(node_ptr);
			clusteracct_storage_g_node_down(
				acct_db_conn,
				node_ptr, now, NULL,
				slurm_conf.slurm_user_id);
		}
	}

	if (IS_NODE_DOWN(node_ptr)) {
		debug3("%s: Node %s being left DOWN", __func__, node_ptr->name);
	} else if (node_ptr->run_job_cnt)
		node_ptr->node_state = NODE_STATE_ALLOCATED | node_flags;
	else {
		node_ptr->node_state = NODE_STATE_IDLE | node_flags;
		node_ptr->last_busy = now;
	}
	last_node_update = now;
}

/*
 * Reset the statistics of a node that is powered down or downed unexpectedly
 * so that client applications do not show erroneous values.
 */
extern void node_mgr_reset_node_stats(node_record_t *node_ptr)
{
	xassert(node_ptr);
	xassert(node_ptr->energy);

	node_ptr->cpu_load = 0;
	memset(node_ptr->energy, 0, sizeof(acct_gather_energy_t));
}

/*
 * Subset of _make_node_down() except for marking node down, trigger and
 * accounting update.
 */
static void _make_node_unavail(node_record_t *node_ptr)
{
	xassert(node_ptr);

	node_ptr->node_state &= (~NODE_STATE_COMPLETING);
	bit_clear(avail_node_bitmap, node_ptr->index);
	bit_clear(cg_node_bitmap, node_ptr->index);
	bit_set(idle_node_bitmap, node_ptr->index);
	bit_set(share_node_bitmap, node_ptr->index);
	bit_clear(up_node_bitmap, node_ptr->index);
}

/* _make_node_down - flag specified node as down */
static void _make_node_down(node_record_t *node_ptr, time_t event_time)
{
	uint32_t node_flags;

	xassert(node_ptr);

	_make_node_unavail(node_ptr);
	node_mgr_reset_node_stats(node_ptr);
	node_flags = node_ptr->node_state & NODE_STATE_FLAGS;
	node_ptr->node_state = NODE_STATE_DOWN | node_flags;
	node_ptr->owner = NO_VAL;
	xfree(node_ptr->mcs_label);
	trigger_node_down(node_ptr);
	last_node_update = time (NULL);
	clusteracct_storage_g_node_down(acct_db_conn,
					node_ptr, event_time, NULL,
					node_ptr->reason_uid);
	/*
	 * check all reservations since node may have been in a reservation with
	 * floating count of nodes that needs to be updated
	 */
	validate_all_reservations(false, false);
}

/*
 * make_node_idle - flag specified node as having finished with a job
 * IN node_ptr - pointer to node reporting job completion
 * IN job_ptr - pointer to job that just completed or NULL if not applicable
 */
void make_node_idle(node_record_t *node_ptr, job_record_t *job_ptr)
{
	uint32_t node_flags;
	time_t now = time(NULL);
	bitstr_t *node_bitmap = NULL;

	if (job_ptr) {
		if (job_ptr->node_bitmap_cg)
			node_bitmap = job_ptr->node_bitmap_cg;
		else
			node_bitmap = job_ptr->node_bitmap;
	}

	log_flag(TRACE_JOBS, "%s: enter %pJ", __func__, job_ptr);

	xassert(node_ptr);
	if (node_bitmap && (bit_test(node_bitmap, node_ptr->index))) {
		/* Not a replay */
		last_job_update = now;
		bit_clear(node_bitmap, node_ptr->index);

		if (!IS_JOB_FINISHED(job_ptr))
			job_update_tres_cnt(job_ptr, node_ptr->index);

		if (job_ptr->node_cnt) {
			/*
			 * Clean up the JOB_COMPLETING flag
			 * only if there is not the slurmctld
			 * epilog running, otherwise wait
			 * when it terminates then this
			 * function will be invoked.
			 */
			job_ptr->node_cnt--;
			cleanup_completing(job_ptr, false);
		} else if ((job_ptr->total_cpus == 0) &&
			   (job_ptr->total_nodes == 0)) {
			/* Job resized to zero nodes (expanded another job) */
		} else {
			error("%s: %pJ node_cnt underflow", __func__, job_ptr);
		}

		if (IS_JOB_SUSPENDED(job_ptr)) {
			/* Remove node from suspended job */
			if (node_ptr->sus_job_cnt)
				(node_ptr->sus_job_cnt)--;
			else
				error("%s: %pJ node %s sus_job_cnt underflow",
				      __func__, job_ptr, node_ptr->name);
		} else if (IS_JOB_RUNNING(job_ptr)) {
			/* Remove node from running job */
			if (node_ptr->run_job_cnt)
				(node_ptr->run_job_cnt)--;
			else
				error("%s: %pJ node %s run_job_cnt underflow",
				      __func__, job_ptr, node_ptr->name);
		} else {
			if (IS_JOB_EXPEDITING(job_ptr)) {
				debug("%s: deferring decrement of comp_job_cnt on %s",
				      __func__, node_ptr->name);
			} else if (node_ptr->comp_job_cnt) {
				(node_ptr->comp_job_cnt)--;
			} else if (IS_NODE_DOWN(node_ptr)) {
				/* We were not expecting this response,
				 * ignore it */
			} else {
				error("%s: %pJ node %s comp_job_cnt underflow",
				      __func__, job_ptr, node_ptr->name);
			}
			if (node_ptr->comp_job_cnt > 0)
				goto fini;	/* More jobs completing */
		}
	}

	if (node_ptr->comp_job_cnt == 0) {
		node_ptr->node_state &= (~NODE_STATE_COMPLETING);
		bit_clear(cg_node_bitmap, node_ptr->index);
		if (IS_NODE_IDLE(node_ptr)) {
			node_ptr->owner = NO_VAL;
			xfree(node_ptr->mcs_label);
		}
	}

	node_flags = node_ptr->node_state & NODE_STATE_FLAGS;
	if (IS_NODE_DOWN(node_ptr) || IS_NODE_FUTURE(node_ptr)) {
		debug3("%s: %pJ node %s being left %s",
		       __func__, job_ptr, node_ptr->name,
		       node_state_base_string(node_ptr->node_state));
		goto fini;
	}
	bit_set(up_node_bitmap, node_ptr->index);

	if (IS_NODE_DRAIN(node_ptr) || IS_NODE_FAIL(node_ptr) ||
	    IS_NODE_NO_RESPOND(node_ptr))
		bit_clear(avail_node_bitmap, node_ptr->index);
	else
		make_node_avail(node_ptr);

	if (IS_NODE_DRAIN(node_ptr) || IS_NODE_FAIL(node_ptr)) {
		trigger_node_draining(node_ptr);
		if (!node_ptr->run_job_cnt && !node_ptr->comp_job_cnt) {
			node_ptr->node_state = NODE_STATE_IDLE | node_flags;
			bit_set(idle_node_bitmap, node_ptr->index);
			debug3("%s: %pJ node %s is DRAINED",
			       __func__, job_ptr, node_ptr->name);
			node_ptr->last_busy = now;
			trigger_node_drained(node_ptr);
			if (!IS_NODE_REBOOT_REQUESTED(node_ptr) &&
			    !IS_NODE_REBOOT_ISSUED(node_ptr))
				clusteracct_storage_g_node_down(
					acct_db_conn, node_ptr, now,
					NULL, slurm_conf.slurm_user_id);
		}
	} else if (node_ptr->run_job_cnt) {
		node_ptr->node_state = NODE_STATE_ALLOCATED | node_flags;
		if (!IS_NODE_NO_RESPOND(node_ptr) &&
		     !IS_NODE_FAIL(node_ptr) && !IS_NODE_DRAIN(node_ptr))
			make_node_avail(node_ptr);
	} else {
		node_ptr->node_state = NODE_STATE_IDLE | node_flags;
		if (!IS_NODE_NO_RESPOND(node_ptr) &&
		     !IS_NODE_FAIL(node_ptr) && !IS_NODE_DRAIN(node_ptr))
			make_node_avail(node_ptr);
		if (!IS_NODE_NO_RESPOND(node_ptr) &&
		    !IS_NODE_COMPLETING(node_ptr))
			bit_set(idle_node_bitmap, node_ptr->index);
		node_ptr->last_busy = now;
	}

	if (IS_NODE_IDLE(node_ptr) && IS_NODE_POWER_DOWN(node_ptr)) {
		/*
		 * Now that the node is idle and is to be powered off, remove
		 * from the avail_node_bitmap to prevent jobs being scheduled on
		 * the node before it power's off.
		 */
		bit_clear(avail_node_bitmap, node_ptr->index);
	}

fini:
	if (job_ptr &&
	    ((job_ptr->details &&
	      (job_ptr->details->whole_node & WHOLE_NODE_USER)) ||
	     (job_ptr->part_ptr &&
	      (job_ptr->part_ptr->flags & PART_FLAG_EXCLUSIVE_USER)))) {
		if (node_ptr->owner_job_cnt == 0) {
			error("%s: node_ptr->owner_job_cnt underflow",
			      __func__);
		} else if (--node_ptr->owner_job_cnt == 0) {
			node_ptr->owner = NO_VAL;
			xfree(node_ptr->mcs_label);
		}
	}
	last_node_update = now;
}

extern int send_nodes_to_accounting(time_t event_time)
{
	int rc = SLURM_SUCCESS, i = 0;
	node_record_t *node_ptr = NULL;
	char *reason = NULL;
	slurmctld_lock_t node_read_lock = {
		.node = READ_LOCK,
	};

 	lock_slurmctld(node_read_lock);
	/* send nodes not in 'up' state */
	for (i = 0; (node_ptr = next_node(&i)); i++) {
		if (!node_ptr->name)
			continue;
		if (node_ptr->reason)
			reason = node_ptr->reason;
		else
			reason = "First Registration";
		if (IS_NODE_DRAIN(node_ptr) ||
		    IS_NODE_FAIL(node_ptr) ||
		    IS_NODE_DOWN(node_ptr) ||
		    IS_NODE_FUTURE(node_ptr) ||
		    (IS_NODE_CLOUD(node_ptr) &&
		     IS_NODE_POWERED_DOWN(node_ptr)))
			rc = clusteracct_storage_g_node_down(
				acct_db_conn,
				node_ptr, event_time,
				reason,
				slurm_conf.slurm_user_id);
		if (rc == SLURM_ERROR)
			break;
	}
	unlock_slurmctld(node_read_lock);
	return rc;
}

/* node_fini - free all memory associated with node records */
extern void node_fini (void)
{
	node_features_free_lists();
	FREE_NULL_BITMAP(asap_node_bitmap);
	FREE_NULL_BITMAP(avail_node_bitmap);
	FREE_NULL_BITMAP(bf_ignore_node_bitmap);
	FREE_NULL_BITMAP(booting_node_bitmap);
	FREE_NULL_BITMAP(cg_node_bitmap);
	FREE_NULL_BITMAP(cloud_node_bitmap);
	FREE_NULL_BITMAP(external_node_bitmap);
	FREE_NULL_BITMAP(future_node_bitmap);
	FREE_NULL_BITMAP(idle_node_bitmap);
	FREE_NULL_BITMAP(power_down_node_bitmap);
	FREE_NULL_BITMAP(power_up_node_bitmap);
	FREE_NULL_BITMAP(share_node_bitmap);
	FREE_NULL_BITMAP(up_node_bitmap);
	FREE_NULL_BITMAP(rs_node_bitmap);
	node_fini2();
}

/* Reset a node's CPU load value */
extern void reset_node_load(char *node_name, uint32_t cpu_load)
{
	node_record_t *node_ptr;

	node_ptr = find_node_record(node_name);
	if (node_ptr) {
		time_t now = time(NULL);
		node_ptr->cpu_load = cpu_load;
		node_ptr->cpu_load_time = now;
		last_node_update = now;
	} else
		error("reset_node_load unable to find node %s", node_name);
}

/* Reset a node's free memory value */
extern void reset_node_free_mem(char *node_name, uint64_t free_mem)
{
	node_record_t *node_ptr;

	node_ptr = find_node_record(node_name);
	if (node_ptr) {
		time_t now = time(NULL);
		node_ptr->free_mem = free_mem;
		node_ptr->free_mem_time = now;
		last_node_update = now;
	} else
		error("reset_node_free_mem unable to find node %s", node_name);
}


/*
 * Check for node timed events
 *
 * Such as:
 * reboots - If the node hasn't booted by ResumeTimeout, mark the node as down.
 * resume_after - Resume a down|drain node after resume_after time.
 */
extern void check_node_timers(void)
{
	int i;
	node_record_t *node_ptr;
	time_t now = time(NULL);
	hostlist_t *resume_hostlist = NULL;

	for (i = 0; (node_ptr = next_node(&i)); i++) {
		if (node_ptr->resume_after && (now > node_ptr->resume_after)) {
			/* Fire resume, reset the time */
			node_ptr->resume_after = 0;

			if (!resume_hostlist)
				resume_hostlist = hostlist_create(NULL);

			hostlist_push_host(resume_hostlist, node_ptr->name);
		}
	}

	if (resume_hostlist) {
		char *host_str;
		update_node_msg_t *resume_msg = NULL;

		hostlist_uniq(resume_hostlist);
		host_str = hostlist_ranged_string_xmalloc(resume_hostlist);
		hostlist_destroy(resume_hostlist);
		debug("Issuing resume request for nodes %s", host_str);

		resume_msg = xmalloc(sizeof(*resume_msg));
		slurm_init_update_node_msg(resume_msg);
		resume_msg->node_state = NODE_RESUME;
		resume_msg->node_names = host_str;

		update_node(resume_msg, 0);

		slurm_free_update_node_msg(resume_msg);

		/* Back the node changes up */
		schedule_node_save();
	}
}

extern bool waiting_for_node_boot(node_record_t *node_ptr)
{
	xassert(node_ptr);

	if ((IS_NODE_POWERING_UP(node_ptr) ||
	     IS_NODE_REBOOT_ISSUED(node_ptr)) &&
	    (node_ptr->boot_time < node_ptr->boot_req_time)) {
		debug("Still waiting for boot of node %s", node_ptr->name);
		return true;
	}

	return false;
}

extern bool waiting_for_node_power_down(node_record_t *node_ptr)
{
	xassert(node_ptr);

	if (IS_NODE_POWERING_DOWN(node_ptr) &&
	    node_ptr->power_save_req_time &&
	    (node_ptr->boot_time <
	     (node_ptr->power_save_req_time + slurm_conf.suspend_timeout))) {
		debug("Still waiting for node '%s' to power off",
		      node_ptr->name);
		return true;
	}

	return false;
}

extern void set_node_comm_name(node_record_t *node_ptr, char *comm_name,
			       char *hostname)
{
	xfree(node_ptr->comm_name);
	node_ptr->comm_name = xstrdup(comm_name ? comm_name : hostname);

	xfree(node_ptr->node_hostname);
	node_ptr->node_hostname = xstrdup(hostname);

	slurm_reset_alias(node_ptr->name,
			  node_ptr->comm_name,
			  node_ptr->node_hostname);
}

static int _foreach_build_part_bitmap(void *x, void *arg)
{
	build_part_bitmap(x);
	return SLURM_SUCCESS;
}

static void _update_parts()
{
	/* scan partition table and identify nodes in each */
	list_for_each(part_list, _foreach_build_part_bitmap, NULL);
	set_partition_tres(false);
}

static int _build_node_callback(char *alias, char *hostname, char *address,
				char *bcast_address, uint16_t port,
				int state_val, slurm_conf_node_t *conf_node,
				config_record_t *config_ptr)
{
	int rc = SLURM_SUCCESS;
	node_record_t *node_ptr = NULL;

	if ((rc = add_node_record(alias, config_ptr, &node_ptr)))
		goto fini;

	if ((state_val != NO_VAL) &&
	    (state_val != NODE_STATE_UNKNOWN))
		node_ptr->node_state = state_val;
	node_ptr->last_response = (time_t) 0;
	node_ptr->comm_name = xstrdup(address);
	node_ptr->cpu_bind  = conf_node->cpu_bind;
	node_ptr->node_hostname = xstrdup(hostname);
	node_ptr->bcast_address = xstrdup(bcast_address);
	node_ptr->port = port;
	node_ptr->reason = xstrdup(conf_node->reason);

	node_ptr->node_state |= NODE_STATE_DYNAMIC_NORM;

	slurm_conf_add_node(node_ptr);

	if (config_ptr->feature) {
		node_ptr->features = xstrdup(config_ptr->feature);
		node_ptr->features_act = xstrdup(config_ptr->feature);
	}

	if (node_ptr->topology_str && topology_g_add_rm_node(node_ptr)) {
		rc = ESLURM_REQUESTED_TOPO_CONFIG_UNAVAILABLE;
		goto fini;
	}

	bit_clear(power_up_node_bitmap, node_ptr->index);
	if (IS_NODE_FUTURE(node_ptr)) {
		bit_set(future_node_bitmap, node_ptr->index);
	} else if (IS_NODE_CLOUD(node_ptr) || IS_NODE_EXTERNAL(node_ptr)) {
		make_node_idle(node_ptr, NULL);
		if (IS_NODE_CLOUD(node_ptr)) {
			bit_set(cloud_node_bitmap, node_ptr->index);
			bit_set(power_down_node_bitmap, node_ptr->index);
		} else {
			bit_set(external_node_bitmap, node_ptr->index);
		}

		if (conf_node->gres_conf) {
			gres_add_dynamic_gres(conf_node->gres_conf,
					      node_ptr->name);
		} else if ((rc = gres_g_node_config_load(node_ptr->config_ptr
								 ->cpus,
							 node_ptr->name,
							 node_ptr->gres_list,
							 NULL, NULL))) {
			goto fini;
		}

		rc = gres_node_config_validate(node_ptr,
					       node_ptr->config_ptr->threads,
					       node_ptr->config_ptr->cores,
					       node_ptr->config_ptr
						       ->tot_sockets,
					       (slurm_conf.conf_flags &
						CONF_FLAG_OR),
					       NULL);
	}

fini:
	if (rc && node_ptr)
		_delete_node_ptr(node_ptr);

	return rc;
}

extern void consolidate_config_list(bool is_locked, bool force)
{
	config_record_t *curr_rec;
	list_itr_t *iter;
	slurmctld_lock_t node_write_lock = { .node = WRITE_LOCK };

	if (is_locked)
		xassert(verify_lock(NODE_LOCK, WRITE_LOCK));

	if (force || _get_config_list_update()) {
		if (!is_locked)
			lock_slurmctld(node_write_lock);
		slurm_mutex_lock(&config_list_update_mutex);

		config_list_update = false;

		/* Use list iterator because we are changing the list */
		iter = list_iterator_create(config_list);
		while ((curr_rec = list_next(iter))) {
			_combine_dup_config_records(curr_rec);
		}
		list_iterator_destroy(iter);

		slurm_mutex_unlock(&config_list_update_mutex);
		if (!is_locked)
			unlock_slurmctld(node_write_lock);
	}
}

extern int create_nodes(update_node_msg_t *msg, char **err_msg)
{
	char *nodeline = msg->extra;
	int state_val, rc = SLURM_SUCCESS;
	slurm_conf_node_t *conf_node;
	config_record_t *config_ptr;
	s_p_hashtbl_t *node_hashtbl = NULL;
	slurmctld_lock_t write_lock = {
		.conf = READ_LOCK,
		.job = WRITE_LOCK,
		.node = WRITE_LOCK,
		.part = WRITE_LOCK,
		.select_node = WRITE_LOCK,
	};

	xassert(nodeline);
	xassert(err_msg);

	if (!xstrstr(slurm_conf.select_type, "cons_tres")) {
		*err_msg = xstrdup("Node creation only compatible with select/cons_tres");
		error("%s", *err_msg);
		return ESLURM_ACCESS_DENIED;
	}

	lock_slurmctld(write_lock);

	if (!(conf_node = slurm_conf_parse_nodeline(nodeline, &node_hashtbl))) {
		*err_msg = xstrdup_printf("Failed to parse nodeline '%s'",
					  nodeline);
		error("%s", *err_msg);
		rc = SLURM_ERROR;
		goto fini;
	}

	/* copy this so upstream logging messages are more detailed */
	xfree(msg->node_names);
	msg->node_names = xstrdup(conf_node->nodenames);

	if ((rc = _validate_nodes_vs_nodeset(conf_node->nodenames))
	    != SLURM_SUCCESS)
		goto fini;

	state_val = state_str2int(conf_node->state, conf_node->nodenames);
	if ((state_val == NO_VAL) ||
	    ((state_val != NODE_STATE_FUTURE) &&
	     !(state_val & NODE_STATE_CLOUD) &&
	     !(state_val & NODE_STATE_EXTERNAL))) {
		*err_msg = xstrdup("Only State=FUTURE, CLOUD, or EXTERNAL allowed for nodes created by scontrol");
		error("%s", *err_msg);
		rc = ESLURM_INVALID_NODE_STATE;
		goto fini;
	}

	config_ptr = config_record_from_conf_node(conf_node,
						  slurmctld_tres_cnt);
	config_ptr->node_bitmap = bit_alloc(node_record_count);

	if ((rc = expand_nodeline_info(conf_node, config_ptr, err_msg,
				       _build_node_callback))) {
		error("Failed to create a node in '%s': %s",
		      conf_node->nodenames, *err_msg);
		goto fini;
	}

	if (config_ptr->feature) {
		node_features_update_list(avail_feature_list,
					  config_ptr->feature,
					  config_ptr->node_bitmap);
		node_features_update_list(active_feature_list,
					  config_ptr->feature,
					  config_ptr->node_bitmap);
	}

	_queue_consolidate_config_list();

	set_cluster_tres(false);
	_update_parts();
	power_save_set_timeouts(NULL);
	power_save_exc_setup();
	select_g_reconfigure();

fini:
	s_p_hashtbl_destroy(node_hashtbl);
	unlock_slurmctld(write_lock);

	if (rc == SLURM_SUCCESS) {
		/* Must be called outside of locks */
		clusteracct_storage_g_cluster_tres(
			acct_db_conn, NULL, NULL, 0, SLURM_PROTOCOL_VERSION);
	}

	return rc;
}

extern int create_dynamic_reg_node(slurm_msg_t *msg)
{
	config_record_t *config_ptr;
	node_record_t *node_ptr;
	int state_val = NODE_STATE_UNKNOWN, rc;
	s_p_hashtbl_t *node_hashtbl = NULL;
	slurm_conf_node_t *conf_node = NULL;
	slurm_node_registration_status_msg_t *reg_msg = msg->data;

	xassert(verify_lock(JOB_LOCK, WRITE_LOCK));
	xassert(verify_lock(NODE_LOCK, WRITE_LOCK));
	xassert(verify_lock(PART_LOCK, WRITE_LOCK));

	if (!xstrstr(slurm_conf.select_type, "cons_tres")) {
		error("Node creation only compatible with select/cons_tres");
		return ESLURM_ACCESS_DENIED;
	}

	if (reg_msg->dynamic_conf) {
		if (!(conf_node =
		      slurm_conf_parse_nodeline(reg_msg->dynamic_conf,
						&node_hashtbl))) {
			s_p_hashtbl_destroy(node_hashtbl);
			error("Failed to parse dynamic nodeline '%s'",
			      reg_msg->dynamic_conf);
			return SLURM_ERROR;
		}

		if (_validate_nodes_vs_nodeset(conf_node->nodenames) !=
		    SLURM_SUCCESS) {
			s_p_hashtbl_destroy(node_hashtbl);
			return SLURM_ERROR;
		}

		config_ptr = config_record_from_conf_node(conf_node,
							  slurmctld_tres_cnt);
		if (conf_node->state)
			state_val = state_str2int(conf_node->state,
						  conf_node->nodenames);
	} else {
		config_ptr = create_config_record();
		config_ptr->boards = reg_msg->boards;
		config_ptr->cores = reg_msg->cores;
		config_ptr->cpus = reg_msg->cpus;
		config_ptr->nodes = xstrdup(reg_msg->node_name);
		config_ptr->real_memory = reg_msg->real_memory;
		config_ptr->threads = reg_msg->threads;
		config_ptr->tmp_disk = reg_msg->tmp_disk;
		config_ptr->tot_sockets = reg_msg->sockets;
	}

	config_ptr->node_bitmap = bit_alloc(node_record_count);

	if ((rc = add_node_record(reg_msg->node_name, config_ptr, &node_ptr))) {
		error("%s (%s)", slurm_strerror(rc), reg_msg->node_name);
		list_delete_ptr(config_list, config_ptr);
		s_p_hashtbl_destroy(node_hashtbl);
		return SLURM_ERROR;
	}

	if (conf_node && conf_node->port_str)
		node_ptr->port = strtol(conf_node->port_str, NULL, 10);

	/*
	 * Always resolve comm_name in slurmctld as hostname may resolve
	 * differently here than on the compute node that sent the RPC.
	 */
	xfree(node_ptr->comm_name);
	if (!(node_ptr->comm_name = _get_msg_hostname(msg)))
		node_ptr->comm_name = xstrdup(reg_msg->hostname);
	xfree(node_ptr->node_hostname);
	node_ptr->node_hostname = xstrdup(reg_msg->hostname);
	slurm_conf_add_node(node_ptr);
	bit_set(power_up_node_bitmap, node_ptr->index);

	node_ptr->features = xstrdup(node_ptr->config_ptr->feature);
	node_features_update_list(avail_feature_list, node_ptr->features,
				  config_ptr->node_bitmap);
	node_ptr->features_act = xstrdup(node_ptr->config_ptr->feature);
	node_features_update_list(active_feature_list, node_ptr->features_act,
				  config_ptr->node_bitmap);

	if (node_ptr->topology_str && topology_g_add_rm_node(node_ptr)) {
		error("%s Invalid node topology specified %s ignored",
		      __func__, node_ptr->topology_str);
		xfree(node_ptr->topology_str);
		topology_g_add_rm_node(node_ptr);
	}

	_queue_consolidate_config_list();

	/* Handle DOWN and DRAIN, otherwise make the node idle */
	if ((state_val == NODE_STATE_DOWN) ||
	    (state_val & NODE_STATE_DRAIN)) {
		time_t now = time(NULL);
		if (conf_node && conf_node->reason)
			set_node_reason(node_ptr, conf_node->reason, now);
		_make_node_down(node_ptr, now);
		node_ptr->node_state = state_val;
	} else
		make_node_idle(node_ptr, NULL);

	node_ptr->node_state |= NODE_STATE_DYNAMIC_NORM;

	set_cluster_tres(false);
	_update_parts();
	power_save_set_timeouts(NULL);
	power_save_exc_setup();
	select_g_reconfigure();

	s_p_hashtbl_destroy(node_hashtbl);

	return SLURM_SUCCESS;
}

static void _remove_node_from_features(node_record_t *node_ptr)
{
	bitstr_t *node_bitmap = bit_alloc(node_record_count);
	bit_set(node_bitmap, node_ptr->index);
	node_features_update_list(avail_feature_list, NULL, node_bitmap);
	node_features_update_list(active_feature_list, NULL, node_bitmap);
	FREE_NULL_BITMAP(node_bitmap);
}

/*
 * Remove from all global bitmaps
 *
 * Sync with bitmaps in _init_bitmaps()
 */
static void _remove_node_from_all_bitmaps(node_record_t *node_ptr)
{
	bit_clear(asap_node_bitmap, node_ptr->index);
	bit_clear(avail_node_bitmap, node_ptr->index);
	bit_clear(bf_ignore_node_bitmap, node_ptr->index);
	bit_clear(booting_node_bitmap, node_ptr->index);
	bit_clear(cg_node_bitmap, node_ptr->index);
	bit_clear(cloud_node_bitmap, node_ptr->index);
	bit_clear(external_node_bitmap, node_ptr->index);
	bit_clear(future_node_bitmap, node_ptr->index);
	bit_clear(idle_node_bitmap, node_ptr->index);
	bit_clear(power_down_node_bitmap, node_ptr->index);
	bit_clear(power_up_node_bitmap, node_ptr->index);
	bit_clear(rs_node_bitmap, node_ptr->index);
	bit_clear(share_node_bitmap, node_ptr->index);
	bit_clear(up_node_bitmap, node_ptr->index);
}

/*
 * Has to be in slurmctld code for locking.
 */
static int _delete_node_ptr(node_record_t *node_ptr)
{
	xassert(node_ptr);

	if (!IS_NODE_DYNAMIC_NORM(node_ptr) && !IS_NODE_EXTERNAL(node_ptr)) {
		error("Can't delete non-dynamic node '%s'.", node_ptr->name);
		return ESLURM_INVALID_NODE_STATE;
	}
	if (IS_NODE_ALLOCATED(node_ptr) ||
	    IS_NODE_COMPLETING(node_ptr)) {
		error("Node '%s' can't be delete because it's still in use.",
		      node_ptr->name);
		return ESLURM_NODES_BUSY;
	}
	if (node_ptr->node_state & NODE_STATE_RES) {
		error("Node '%s' can't be delete because it's in a reservation.",
		      node_ptr->name);
		return ESLURM_NODES_BUSY;
	}

	xfree(node_ptr->power_action_name);
	xfree(node_ptr->topology_orig_str);
	xfree(node_ptr->topology_str);
	topology_g_add_rm_node(node_ptr);

	_remove_node_from_all_bitmaps(node_ptr);
	_remove_node_from_features(node_ptr);
	gres_node_remove(node_ptr);

	xhash_pop_str(node_hash_table, node_ptr->name);
	slurm_conf_remove_node(node_ptr->name);
	delete_node_record(node_ptr);

	return SLURM_SUCCESS;
}

static int _delete_node(char *name)
{
	node_record_t *node_ptr;

	node_ptr = find_node_record(name);
	if (!node_ptr) {
		error("Unable to find node %s to delete", name);
		return ESLURM_INVALID_NODE_NAME;
	}
	return _delete_node_ptr(node_ptr);
}

extern int delete_nodes(char *names, char **err_msg)
{
	char *node_name;
	hostlist_t *to_delete;
	bool one_success = false;
	int ret_rc = SLURM_SUCCESS;
	hostlist_t *error_hostlist = NULL;
	slurmctld_lock_t write_lock = {
		.job = WRITE_LOCK,
		.node = WRITE_LOCK,
		.part = WRITE_LOCK,
		.conf = READ_LOCK,
		.select_node = WRITE_LOCK,
	};

	xassert(err_msg);

	if (!xstrstr(slurm_conf.select_type, "cons_tres")) {
		*err_msg = xstrdup("Node deletion only compatible with select/cons_tres");
		error("%s", *err_msg);
		return ESLURM_ACCESS_DENIED;
	}

	lock_slurmctld(write_lock);

	if (!(to_delete = nodespec_to_hostlist(names, true, NULL))) {
		ret_rc = ESLURM_INVALID_NODE_NAME;
		goto cleanup;
	}
	if (!hostlist_count(to_delete)) {
		info("%s: expansion of node specification '%s' resulted in zero nodes",
		     __func__, names);
		ret_rc = ESLURM_INVALID_NODE_NAME;
		goto cleanup;
	}

	while ((node_name = hostlist_shift(to_delete))) {
		int rc;
		if ((rc = _delete_node(node_name))) {
			error("failed to delete node '%s'", node_name);
			if (!error_hostlist)
				error_hostlist = hostlist_create(node_name);
			else
				hostlist_push_host(error_hostlist, node_name);
		} else
			one_success = true;
		ret_rc |= rc;
		free(node_name);
	}

	if (one_success) {
		set_cluster_tres(false);
		_update_parts();
		select_g_reconfigure();
		power_save_exc_setup();
	}
	if (error_hostlist) {
		char *nodes = hostlist_ranged_string_xmalloc(error_hostlist);
		*err_msg = xstrdup_printf("failed to delete nodes %s", nodes);
		xfree(nodes);
		FREE_NULL_HOSTLIST(error_hostlist);
	}

cleanup:
	unlock_slurmctld(write_lock);
	if (one_success) {
		/* Must be called outside of locks */
		clusteracct_storage_g_cluster_tres(
			acct_db_conn, NULL, NULL, 0, SLURM_PROTOCOL_VERSION);
	}

	FREE_NULL_HOSTLIST(to_delete);

	return ret_rc;
}

extern void set_node_reason(node_record_t *node_ptr,
			    char *message,
			    time_t time)
{
	xassert(verify_lock(NODE_LOCK, WRITE_LOCK));
	xassert(node_ptr);

	if (message && message[0]) {
		if (node_ptr->reason) {
			char *tmp;
			tmp = xstrdup(" : ");
			xstrcat(tmp, message);
			if (!xstrstr(node_ptr->reason, tmp))
				xstrfmtcat(node_ptr->reason, " : %s", message);
			xfree(tmp);
		} else {
			node_ptr->reason = xstrdup(message);
		}
		node_ptr->reason_time = time;
		node_ptr->reason_uid = slurm_conf.slurm_user_id;
	} else {
		xfree(node_ptr->reason);
		node_ptr->reason_time = 0;
		node_ptr->reason_uid = NO_VAL;
	}
}

extern int node_mgr_set_node_topology(node_record_t *node_ptr,
				      char *new_topology_str)
{
	int rc = SLURM_SUCCESS;
	char *topology_str_old;
	bool from_config = false;
	bool stored_orig = false;

	if (!node_ptr->topology_str && !node_ptr->topology_orig_str) {
		node_ptr->topology_orig_str =
			topology_g_get_topology_str(node_ptr);
		stored_orig = true;
	}

	if (node_ptr->topology_str) {
		topology_str_old = node_ptr->topology_str;
		node_ptr->topology_str = NULL;
	} else {
		if (stored_orig)
			topology_str_old = xstrdup(node_ptr->topology_orig_str);
		else
			topology_str_old =
				topology_g_get_topology_str(node_ptr);
		from_config = true;
	}

	if (*new_topology_str) {
		node_ptr->topology_str = xstrdup(new_topology_str);
	}

	if (topology_g_add_rm_node(node_ptr)) {
		info("Invalid node topology specified %s",
		     node_ptr->topology_str);
		xfree(node_ptr->topology_str);
		node_ptr->topology_str = topology_str_old;
		topology_g_add_rm_node(node_ptr);
		rc = ESLURM_REQUESTED_TOPO_CONFIG_UNAVAILABLE;
		if (from_config)
			xfree(node_ptr->topology_str);
		if (stored_orig)
			xfree(node_ptr->topology_orig_str);
	} else {
		xfree(topology_str_old);
	}

	return rc;
}
