Merge branch 't23618-docs-slurmd-z' into 'master'

See merge request SchedMD/dev/slurm!2056
diff --git a/CHANGELOG/slurm-25.05.md b/CHANGELOG/slurm-25.05.md
index 623fe7c..2ecb91e 100644
--- a/CHANGELOG/slurm-25.05.md
+++ b/CHANGELOG/slurm-25.05.md
@@ -1,3 +1,30 @@
+## Changes in 25.05.3
+
+* slurmctld.service - Set LimitMEMLOCK=infinity by default to avoid slurmctld crashes due to default for locked memory being too low.
+* slurmdbd.service - Set LimitMEMLOCK=infinity by default to avoid slurmdbd crashes due to default for locked memory being too low.
+* slurmrestd.service - Set LimitMEMLOCK=infinity by default to avoid slurmrestd crashes due to default for locked memory being too low.
+* Fix a segfault in the slurmctld caused by invalid core affinity for GPUs on a node.
+* Fix a node not being set to the invalid state when GPU core affinity is invalid.
+* A cluster will start the MaxJobCount of jobs and not one less.
+* Allow QOS usage to be purged and optionally archived as part of a Usage purge and optional archive.
+* Fix slurmctld crash caused by accessing job_desc.assoc_qos in job_submit.lua for an association that doesn't exist.
+* Fix slurmctld segfault when SIGUSR2 is received early and jobcomp plugin is enabled.
+* Fix use-cases incorrectly rejecting job requests when MaxCPUsPer[Socket|Node] applied and CPUSpecList/CoreSpecCount configured.
+* tls/s2n - Fix heterogeneous jobs failing to run in a TLS enabled environment.
+* sbatch - Fix a regression where SLURM_NETWORK would not be exported for non-Cray systems when using --network.
+* REGEX_REPLACE() was not supported before MySQL 8.0.4 and MariaDB 10, and the regex syntax used previously was not supported for both MySQL and MariaDB (not all POSIX syntax is supported in both)
+* fatal() if the SQL server does not support REGEXP_REPLACE(). This was introduced in MySQL 8.0.4 or MariaDB 10.0.5.
+* Pass environment variables to container when using Apptainer/Singularity OCI runtimes.
+* slurmscriptd,slurmstepd - Fix use-after-free issue with the "ident" string when logging to syslog.
+* Fix bug where the backfill scheduler changed the specified --time of a job and incorrectly reset it to --time-min.
+* Prevent healthy nodes being marked as unresponsive due to forwarding message timeouts increasing as the tree is traversed. The issue occurred if Slurm was running with a mix of 24.05- and 24.11+ slurmds. This only fixes 25.05+ slurmds.
+* Fix crash while using the wckeys rest endpoint.
+* Fix cases of job updates incorrectly rejected when specifying modifications on fields unrelated to tasks computation (i.e. changing JobName).
+* slurmrestd - Prevent triggering a fatal abort when parasing a non-empty group id string by replacing it with an error. This affects all endpoints with request bodies containing openapi_meta_client group field. It also affects the following endpoints: 'GET /slurmdb/v0.0.4[1-3]/jobs' 'POST /slurm/v0.0.4[1-3]/job/submit' 'POST /slurm/v0.0.4[1-3]/job/{job_id}' 'POST /slurm/v0.0.4[1-3]/job/allocate'
+* slurmrestd - Fix memory leak that happened when submitting a request body containing the meta.plugin.accounting_storage field.
+* slurmrestd - Fix memory leak that happened when submitting a request body containing the "warnings", "errors", or "meta" field. This affects the following endpoints: 'POST /slurmdb/v0.0.4*/qos'
+* slurmctld - Fix how gres with cores or a type defined are selected to prevent jobs not using reservations from being allocated reserved gres and vice versa.
+
 ## Changes in 25.05.2
 
 * sbatch - Fix case where --get-user-env and some --export flags could make a job fail and get requeued+held if Slurm's installation path was too long.
diff --git a/slurm/slurm.h b/slurm/slurm.h
index 6eb360e..3a132b1 100644
--- a/slurm/slurm.h
+++ b/slurm/slurm.h
@@ -1940,6 +1940,8 @@
 	char *std_in;		/* pathname of job's stdin file */
 	char *std_out;		/* pathname of job's stdout file */
 	uint16_t segment_size;	/* requested segment size */
+	char *submit_line;	/* full submit line of the slurm command used to
+				 * submit this job */
 	time_t submit_time;	/* time of job submission */
 	time_t suspend_time;	/* time job last suspended or resumed */
 	char *system_comment;	/* slurmctld's arbitrary comment */
diff --git a/src/common/http.c b/src/common/http.c
index 73e03c8..cd68e92 100644
--- a/src/common/http.c
+++ b/src/common/http.c
@@ -411,3 +411,50 @@
 	dst->query = xstrdup(src->query);
 	dst->fragment = xstrdup(src->fragment);
 }
+
+extern void free_http_header(http_header_t *header)
+{
+	xassert(header->magic == HTTP_HEADER_MAGIC);
+	xfree(header->name);
+	xfree(header->value);
+	header->magic = ~HTTP_HEADER_MAGIC;
+	xfree(header);
+}
+
+/* find operator against http_header_t */
+static int _http_header_find_key(void *x, void *y)
+{
+	const http_header_t *entry = x;
+	const char *key = y;
+
+	xassert(entry->name);
+	xassert(entry->magic == HTTP_HEADER_MAGIC);
+
+	if (key == NULL)
+		return 0;
+
+	/* case insensitive compare per rfc2616:4.2 */
+	if (entry->name && !xstrcasecmp(entry->name, key))
+		return 1;
+	else
+		return 0;
+}
+
+extern const char *find_http_header(list_t *headers, const char *name)
+{
+	http_header_t *header = NULL;
+
+	if (!headers || !name)
+		return NULL;
+
+	header = (http_header_t *) list_find_first(headers,
+						   _http_header_find_key,
+						   (void *) name);
+
+	if (header) {
+		xassert(header->magic == HTTP_HEADER_MAGIC);
+		return header->value;
+	}
+
+	return NULL;
+}
diff --git a/src/common/http.h b/src/common/http.h
index 8705a2a..1d2aa4b 100644
--- a/src/common/http.h
+++ b/src/common/http.h
@@ -209,4 +209,23 @@
 /* Copy all members in URL */
 extern void url_copy_members(url_t *dst, const url_t *src);
 
+#define HTTP_HEADER_MAGIC 0x1aaffbe2
+
+/* HTTP header */
+typedef struct {
+	int magic; /* HTTP_HEADER_MAGIC */
+	char *name;
+	char *value;
+} http_header_t;
+
+/* Free http header and contents */
+extern void free_http_header(http_header_t *header);
+
+/* find http header from header list
+ * IN headers - list_t of http_header_t*
+ * IN name - name of header to find
+ * RET ptr to header value or NULL if not found
+ */
+extern const char *find_http_header(list_t *headers, const char *name);
+
 #endif /* SLURM_HTTP_H */
diff --git a/src/common/slurm_protocol_defs.c b/src/common/slurm_protocol_defs.c
index 2acebb4..a75faba 100644
--- a/src/common/slurm_protocol_defs.c
+++ b/src/common/slurm_protocol_defs.c
@@ -1859,6 +1859,7 @@
 		xfree(job->std_err);
 		xfree(job->std_in);
 		xfree(job->std_out);
+		xfree(job->submit_line);
 		xfree(job->system_comment);
 		xfree(job->tres_alloc_str);
 		xfree(job->tres_bind);
diff --git a/src/common/slurm_protocol_pack.c b/src/common/slurm_protocol_pack.c
index a4a47c9..1034731 100644
--- a/src/common/slurm_protocol_pack.c
+++ b/src/common/slurm_protocol_pack.c
@@ -3317,7 +3317,214 @@
 	uint32_t uint32_tmp;
 	bool need_unpack = false;
 
-	if (protocol_version >= SLURM_25_05_PROTOCOL_VERSION) {
+	if (protocol_version >= SLURM_25_11_PROTOCOL_VERSION) {
+		/* job_record_pack_common */
+		safe_unpackstr(&job->account, buffer);
+		safe_unpackstr(&job->admin_comment, buffer);
+		safe_unpackstr(&job->alloc_node, buffer);
+		safe_unpack32(&job->alloc_sid, buffer);
+		safe_unpack32(&job->array_job_id, buffer);
+		safe_unpack32(&job->array_task_id, buffer);
+		safe_unpack32(&job->assoc_id, buffer);
+
+		safe_unpackstr(&job->batch_features, buffer);
+		safe_unpack16(&job->batch_flag, buffer);
+		safe_unpackstr(&job->batch_host, buffer);
+		safe_unpack64(&job->bitflags, buffer);
+		safe_unpackstr(&job->burst_buffer, buffer);
+		safe_unpackstr(&job->burst_buffer_state, buffer);
+		safe_unpackdouble(&job->billable_tres, buffer);
+
+		safe_unpackstr(&job->comment, buffer);
+		safe_unpackstr(&job->container, buffer);
+		safe_unpackstr(&job->container_id, buffer);
+		safe_unpackstr(&job->cpus_per_tres, buffer);
+
+		safe_unpack_time(&job->deadline, buffer);
+		safe_unpack32(&job->delay_boot, buffer);
+		safe_unpack32(&job->derived_ec, buffer);
+
+		safe_unpack32(&job->exit_code, buffer);
+		safe_unpackstr(&job->extra, buffer);
+
+		safe_unpackstr(&job->failed_node, buffer);
+		/* job_record_pack_fed_details */
+		safe_unpackbool(&need_unpack, buffer);
+		if (need_unpack) {
+			safe_unpackstr(&job->fed_origin_str, buffer);
+			safe_unpack64(&job->fed_siblings_active, buffer);
+			safe_unpackstr(&job->fed_siblings_active_str, buffer);
+			safe_unpack64(&job->fed_siblings_viable, buffer);
+			safe_unpackstr(&job->fed_siblings_viable_str, buffer);
+		}
+		/*******************************/
+
+		safe_unpackstr(&job->gres_total, buffer);
+		safe_unpack32(&job->group_id, buffer);
+
+		safe_unpack32(&job->het_job_id, buffer);
+		safe_unpackstr(&job->het_job_id_set, buffer);
+		safe_unpack32(&job->het_job_offset, buffer);
+
+		safe_unpack32(&job->job_id, buffer);
+		safe_unpack32(&job->job_state, buffer);
+
+		safe_unpack_time(&job->last_sched_eval, buffer);
+		safe_unpackstr(&job->licenses, buffer);
+		safe_unpackstr(&job->licenses_allocated, buffer);
+
+		safe_unpack16(&job->mail_type, buffer);
+		safe_unpackstr(&job->mail_user, buffer);
+		safe_unpackstr(&job->mcs_label, buffer);
+		safe_unpackstr(&job->mem_per_tres, buffer);
+
+		safe_unpackstr(&job->name, buffer);
+		safe_unpackstr(&job->network, buffer);
+
+		safe_unpack_time(&job->preempt_time, buffer);
+		safe_unpack_time(&job->pre_sus_time, buffer);
+		safe_unpack32(&job->priority, buffer);
+		safe_unpack32(&job->profile, buffer);
+
+		safe_unpack8(&job->reboot, buffer);
+		safe_unpack32(&job->req_switch, buffer);
+		safe_unpack_time(&job->resize_time, buffer);
+		safe_unpack16(&job->restart_cnt, buffer);
+		safe_unpackstr(&job->resv_name, buffer);
+		safe_unpackstr(&job->resv_ports, buffer);
+
+		safe_unpackstr(&job->selinux_context, buffer);
+		safe_unpack32(&job->site_factor, buffer);
+		safe_unpack16(&job->start_protocol_ver, buffer);
+		safe_unpackstr(&job->state_desc, buffer);
+		safe_unpack32(&job->state_reason, buffer);
+		safe_unpack_time(&job->suspend_time, buffer);
+		safe_unpackstr(&job->system_comment, buffer);
+
+		safe_unpack32(&job->time_min, buffer);
+		safe_unpackstr(&job->tres_bind, buffer);
+		safe_unpackstr(&job->tres_alloc_str, buffer);
+		safe_unpackstr(&job->tres_req_str, buffer);
+		safe_unpackstr(&job->tres_freq, buffer);
+		safe_unpackstr(&job->tres_per_job, buffer);
+		safe_unpackstr(&job->tres_per_node, buffer);
+		safe_unpackstr(&job->tres_per_socket, buffer);
+		safe_unpackstr(&job->tres_per_task, buffer);
+
+		safe_unpack32(&job->user_id, buffer);
+		safe_unpackstr(&job->user_name, buffer);
+
+		safe_unpack32(&job->wait4switch, buffer);
+		safe_unpackstr(&job->wckey, buffer);
+		/**************************************/
+
+
+		/* The array_task_str value is stored in slurmctld and passed
+		 * here in hex format for best scalability. Its format needs
+		 * to be converted to human readable form by the client. */
+		safe_unpackstr(&job->array_task_str, buffer);
+		safe_unpack32(&job->array_max_tasks, buffer);
+		xlate_array_task_str(&job->array_task_str, job->array_max_tasks,
+				     &job->array_bitmap);
+
+		safe_unpack32(&job->time_limit, buffer);
+
+		safe_unpack_time(&job->start_time, buffer);
+		safe_unpack_time(&job->end_time, buffer);
+		safe_unpack32_array(&job->priority_array, &uint32_tmp, buffer);
+		safe_unpackstr(&job->priority_array_names, buffer);
+		safe_unpackstr(&job->cluster, buffer);
+		safe_unpackstr(&job->nodes, buffer);
+		safe_unpackstr(&job->sched_nodes, buffer);
+		safe_unpackstr(&job->partition, buffer);
+		safe_unpackstr(&job->qos, buffer);
+		safe_unpack_time(&job->preemptable_time, buffer);
+
+		if (unpack_job_resources(&job->job_resrcs, buffer,
+					 protocol_version))
+			goto unpack_error;
+		safe_unpackstr_array(&job->gres_detail_str,
+				     &job->gres_detail_cnt, buffer);
+
+		unpack_bit_str_hex_as_inx(&job->node_inx, buffer);
+
+		/*** unpack default job details ***/
+		safe_unpackbool(&need_unpack, buffer);
+		if (!need_unpack) {
+			safe_unpack32(&job->num_cpus, buffer);
+			safe_unpack32(&job->num_nodes, buffer);
+			safe_unpack32(&job->nice, buffer);
+		} else {
+			/* job_record_pack_details_common */
+			safe_unpack_time(&job->accrue_time, buffer);
+			safe_unpack_time(&job->eligible_time, buffer);
+			safe_unpackstr(&job->cluster_features, buffer);
+			safe_unpack32(&job->cpu_freq_gov, buffer);
+			safe_unpack32(&job->cpu_freq_max, buffer);
+			safe_unpack32(&job->cpu_freq_min, buffer);
+			safe_unpackstr(&job->dependency, buffer);
+			unpack_bit_str_hex_as_fmt_str(&job->job_size_str,
+						      buffer);
+			safe_unpack32(&job->nice, buffer);
+			safe_unpack16(&job->ntasks_per_node, buffer);
+			safe_unpack16(&job->ntasks_per_tres, buffer);
+			safe_unpack16(&job->requeue, buffer);
+			safe_unpack16(&job->segment_size, buffer);
+			safe_unpack_time(&job->submit_time, buffer);
+			safe_unpackstr(&job->work_dir, buffer);
+			/**********************************/
+
+			safe_unpackstr(&job->features, buffer);
+			safe_unpackstr(&job->prefer, buffer);
+			safe_unpackstr(&job->command, buffer);
+			safe_unpackstr(&job->submit_line, buffer);
+
+			safe_unpack32(&job->num_cpus, buffer);
+			safe_unpack32(&job->max_cpus, buffer);
+			safe_unpack32(&job->num_nodes, buffer);
+			safe_unpack32(&job->max_nodes, buffer);
+			safe_unpack32(&job->num_tasks, buffer);
+
+			safe_unpack16(&job->shared, buffer);
+
+			safe_unpackstr(&job->cronspec, buffer);
+		}
+
+		/*** unpack pending job details ***/
+		safe_unpack16(&job->contiguous, buffer);
+		safe_unpack16(&job->core_spec, buffer);
+		safe_unpack16(&job->cpus_per_task, buffer);
+		safe_unpack16(&job->pn_min_cpus, buffer);
+
+		safe_unpack64(&job->pn_min_memory, buffer);
+		safe_unpack32(&job->pn_min_tmp_disk, buffer);
+		safe_unpack16(&job->oom_kill_step, buffer);
+		safe_unpackstr(&job->req_nodes, buffer);
+
+		unpack_bit_str_hex_as_inx(&job->req_node_inx, buffer);
+
+		safe_unpackstr(&job->exc_nodes, buffer);
+
+		unpack_bit_str_hex_as_inx(&job->exc_node_inx, buffer);
+
+		safe_unpackstr(&job->std_err, buffer);
+		safe_unpackstr(&job->std_in, buffer);
+		safe_unpackstr(&job->std_out, buffer);
+
+		if (unpack_multi_core_data(&mc_ptr, buffer, protocol_version))
+			goto unpack_error;
+		if (mc_ptr) {
+			job->boards_per_node = mc_ptr->boards_per_node;
+			job->sockets_per_board = mc_ptr->sockets_per_board;
+			job->sockets_per_node = mc_ptr->sockets_per_node;
+			job->cores_per_socket = mc_ptr->cores_per_socket;
+			job->threads_per_core = mc_ptr->threads_per_core;
+			job->ntasks_per_board = mc_ptr->ntasks_per_board;
+			job->ntasks_per_socket = mc_ptr->ntasks_per_socket;
+			job->ntasks_per_core = mc_ptr->ntasks_per_core;
+			xfree(mc_ptr);
+		}
+	} else if (protocol_version >= SLURM_25_05_PROTOCOL_VERSION) {
 		/* job_record_pack_common */
 		safe_unpackstr(&job->account, buffer);
 		safe_unpackstr(&job->admin_comment, buffer);
diff --git a/src/conmgr/con.c b/src/conmgr/con.c
index fdaaa44..c06f301 100644
--- a/src/conmgr/con.c
+++ b/src/conmgr/con.c
@@ -1931,8 +1931,7 @@
 	xassert(con->refs >= 0);
 
 	ref->magic = ~MAGIC_CON_MGR_FD_REF;
-	xfree(ref);
-	*ref_ptr = NULL;
+	xfree((*ref_ptr));
 }
 
 extern void conmgr_fd_free_ref(conmgr_fd_ref_t **ref_ptr)
diff --git a/src/conmgr/conmgr.h b/src/conmgr/conmgr.h
index abd7549..a582b46 100644
--- a/src/conmgr/conmgr.h
+++ b/src/conmgr/conmgr.h
@@ -426,7 +426,7 @@
 extern int conmgr_queue_send_fd(conmgr_fd_t *con, int fd);
 
 /*
- * Write binary data to connection (from callback).
+ * Write binary data to connection
  * NOTE: type=CON_TYPE_RAW only
  * IN con connection manager connection struct
  * IN buffer pointer to buffer
@@ -437,6 +437,17 @@
 				   const size_t bytes);
 
 /*
+ * Copy and write binary data to connection
+ * NOTE: type=CON_TYPE_RAW only
+ * IN ref reference to connection
+ * IN buffer pointer to buffer
+ * IN bytes number of bytes in buffer to write
+ * RET SLURM_SUCCESS or error
+ */
+extern int conmgr_con_queue_write_data(conmgr_fd_ref_t *ref, const void *buffer,
+				       const size_t bytes);
+
+/*
  * Write packed msg to connection (from callback).
  * NOTE: type=CON_TYPE_RPC only
  * IN con conmgr connection ptr
diff --git a/src/conmgr/io.c b/src/conmgr/io.c
index ba0bcd6..890d914 100644
--- a/src/conmgr/io.c
+++ b/src/conmgr/io.c
@@ -422,24 +422,33 @@
 	con->in->size = size;
 }
 
-extern int conmgr_queue_write_data(conmgr_fd_t *con, const void *buffer,
-				   const size_t bytes)
+/* Copy buffer into new buf_t */
+static buf_t *_buf_clone(const void *buffer, const size_t bytes)
 {
-	buf_t *buf;
+	buf_t *buf = NULL;
 
-	xassert(con->magic == MAGIC_CON_MGR_FD);
-
-	/* Ignore empty write requests */
-	if (!bytes)
-		return SLURM_SUCCESS;
+	xassert(bytes > 0);
 
 	buf = init_buf(bytes);
 
-	/* TODO: would be nice to avoid this copy */
 	memmove(get_buf_data(buf), buffer, bytes);
 
-	log_flag(NET, "%s: [%s] write of %zu bytes queued",
-		 __func__, con->name, bytes);
+	return buf;
+}
+
+/*
+ * Append buffer to output queue
+ * WARNING: caller must hold mgr.mutex lock
+ * IN con - connection to queue outgoing buffer
+ * IN buf - buffer to queue as output (takes ownership)
+ * RET SLURM_SUCCESS or error
+ */
+static int _append_output(conmgr_fd_t *con, buf_t *buf)
+{
+	xassert(con->magic == MAGIC_CON_MGR_FD);
+
+	log_flag(NET, "%s: [%s] write of %u bytes queued",
+		 __func__, con->name, get_buf_offset(buf));
 
 	log_flag_hex(NET_RAW, get_buf_data(buf), get_buf_offset(buf),
 		     "%s: queuing up write", __func__);
@@ -449,12 +458,54 @@
 	if (con_flag(con, FLAG_WATCH_WRITE_TIMEOUT))
 		con->last_write = timespec_now();
 
-	slurm_mutex_lock(&mgr.mutex);
 	EVENT_SIGNAL(&mgr.watch_sleep);
-	slurm_mutex_unlock(&mgr.mutex);
+
 	return SLURM_SUCCESS;
 }
 
+static int _write_data(conmgr_fd_t *con, const void *buffer, const size_t bytes)
+{
+	int rc = EINVAL;
+	buf_t *buf = NULL;
+
+	xassert(con->magic == MAGIC_CON_MGR_FD);
+
+	/* Ignore empty write requests */
+	if (!bytes)
+		return SLURM_SUCCESS;
+
+	/* TODO: would be nice to avoid this copy */
+	buf = _buf_clone(buffer, bytes);
+
+	slurm_mutex_lock(&mgr.mutex);
+	rc = _append_output(con, buf);
+	slurm_mutex_unlock(&mgr.mutex);
+
+	return rc;
+}
+
+extern int conmgr_queue_write_data(conmgr_fd_t *con, const void *buffer,
+				   const size_t bytes)
+{
+	xassert(con->magic == MAGIC_CON_MGR_FD);
+	xassert(buffer || !bytes);
+
+	return _write_data(con, buffer, bytes);
+}
+
+extern int conmgr_con_queue_write_data(conmgr_fd_ref_t *ref, const void *buffer,
+				       const size_t bytes)
+{
+	if (!ref)
+		return EINVAL;
+
+	xassert(ref->magic == MAGIC_CON_MGR_FD_REF);
+	xassert(ref->con->magic == MAGIC_CON_MGR_FD);
+	xassert(buffer || !bytes);
+
+	return _write_data(ref->con, buffer, bytes);
+}
+
 static int _get_input_buffer(const conmgr_fd_t *con, const void **data_ptr,
 			     size_t *bytes_ptr)
 {
diff --git a/src/plugins/data_parser/v0.0.44/parsers.c b/src/plugins/data_parser/v0.0.44/parsers.c
index cd0fd9a..cd0c9ac 100644
--- a/src/plugins/data_parser/v0.0.44/parsers.c
+++ b/src/plugins/data_parser/v0.0.44/parsers.c
@@ -8480,6 +8480,7 @@
 	add_cparse(JOB_INFO_STDOUT_EXP, "stdout_expanded", "Job stdout with expanded fields"),
 	add_cparse(JOB_INFO_STDERR_EXP, "stderr_expanded", "Job stderr with expanded fields"),
 	add_parse(TIMESTAMP_NO_VAL, submit_time, "submit_time", "Time when the job was submitted (UNIX timestamp)"),
+	add_parse(STRING, submit_line, "submit_line", "Job submit line (e.g. 'sbatch -N3 job.sh job_arg'"),
 	add_parse(TIMESTAMP_NO_VAL, suspend_time, "suspend_time", "Time the job was last suspended or resumed (UNIX timestamp)"),
 	add_parse(STRING, system_comment, "system_comment", "Arbitrary comment from slurmctld"),
 	add_parse(UINT32_NO_VAL, time_limit, "time_limit", "Maximum run time in minutes"),
diff --git a/src/scontrol/info_job.c b/src/scontrol/info_job.c
index 6142152..f6c41fe 100644
--- a/src/scontrol/info_job.c
+++ b/src/scontrol/info_job.c
@@ -727,6 +727,10 @@
 	xstrcat(out, line_end);
 
 	/****** Line 22 ******/
+	xstrfmtcat(out, "SubmitLine=%s", job_ptr->submit_line);
+	xstrcat(out, line_end);
+
+	/****** Line 23 ******/
 	xstrfmtcat(out, "WorkDir=%s", job_ptr->work_dir);
 	xstrcat(out, line_end);
 
diff --git a/src/slurmctld/job_mgr.c b/src/slurmctld/job_mgr.c
index 8e48d3a..e4dcffb 100644
--- a/src/slurmctld/job_mgr.c
+++ b/src/slurmctld/job_mgr.c
@@ -10359,13 +10359,6 @@
 	assoc_mgr_lock_t locks = { .qos = READ_LOCK };
 	xassert(!has_qos_lock || verify_assoc_lock(QOS_LOCK, READ_LOCK));
 
-	/*
-	 * NOTE: There are nested pack blocks in
-	 * job_record_pack_details_common() and
-	 * job_record_pack_details_common(). Bump this protocol block when
-	 * bumping the blocks in these functions to help keep symmetry between
-	 * pack and unpacks.
-	 */
 	if (protocol_version >= SLURM_25_05_PROTOCOL_VERSION) {
 		job_record_pack_common(dump_job_ptr, false, buffer,
 				       protocol_version);
@@ -10953,7 +10946,141 @@
 	} else
 		_find_node_config(&max_cpu_cnt, &max_core_cnt);
 
-	if (protocol_version >= SLURM_24_11_PROTOCOL_VERSION) {
+	if (protocol_version >= SLURM_25_11_PROTOCOL_VERSION) {
+		if (!detail_ptr) {
+			packbool(false, buffer);
+
+			if (job_ptr->total_cpus)
+				pack32(job_ptr->total_cpus, buffer);
+			else
+				pack32(job_ptr->cpu_cnt, buffer);
+
+			pack32(job_ptr->node_cnt, buffer);
+			pack32(NICE_OFFSET, buffer); /* Best guess */
+			return;
+		}
+		packbool(true, buffer);
+		job_record_pack_details_common(detail_ptr, buffer,
+					       protocol_version);
+
+		if (!IS_JOB_PENDING(job_ptr)) {
+			packstr(detail_ptr->features_use, buffer);
+			packnull(buffer);
+		} else {
+			packstr(detail_ptr->features, buffer);
+			packstr(detail_ptr->prefer, buffer);
+		}
+
+		if (detail_ptr->argv)
+			packstr(detail_ptr->argv[0], buffer);
+		else
+			packnull(buffer);
+		packstr(detail_ptr->submit_line, buffer);
+
+		if (IS_JOB_COMPLETING(job_ptr) && job_ptr->cpu_cnt) {
+			pack32(job_ptr->cpu_cnt, buffer);
+			pack32((uint32_t) 0, buffer);
+		} else if (job_ptr->total_cpus &&
+			   !IS_JOB_PENDING(job_ptr)) {
+			/* If job is PENDING ignore total_cpus,
+			 * which may have been set by previous run
+			 * followed by job requeue. */
+			pack32(job_ptr->total_cpus, buffer);
+			pack32((uint32_t) 0, buffer);
+		} else {
+			pack32(detail_ptr->min_cpus, buffer);
+			if (detail_ptr->max_cpus != NO_VAL)
+				pack32(detail_ptr->max_cpus, buffer);
+			else
+				pack32((uint32_t) 0, buffer);
+		}
+
+		if (IS_JOB_COMPLETING(job_ptr) && job_ptr->node_cnt) {
+			pack32(job_ptr->node_cnt, buffer);
+			pack32((uint32_t) 0, buffer);
+		} else if (job_ptr->total_nodes) {
+			pack32(job_ptr->total_nodes, buffer);
+			pack32((uint32_t) 0, buffer);
+		} else if (job_ptr->node_cnt_wag) {
+			/* This should catch everything else, but
+			 * just in case this is 0 (startup or
+			 * whatever) we will keep the rest of
+			 * this if statement around.
+			 */
+			pack32(job_ptr->node_cnt_wag, buffer);
+			pack32((uint32_t) detail_ptr->max_nodes,
+			       buffer);
+		} else if (detail_ptr->ntasks_per_node) {
+			/* min_nodes based upon task count and ntasks
+			 * per node */
+			uint32_t min_nodes;
+			min_nodes = detail_ptr->num_tasks /
+				detail_ptr->ntasks_per_node;
+			min_nodes = MAX(min_nodes,
+					detail_ptr->min_nodes);
+			pack32(min_nodes, buffer);
+			pack32(detail_ptr->max_nodes, buffer);
+		} else if (detail_ptr->cpus_per_task > 1) {
+			/* min_nodes based upon task count and cpus
+			 * per task */
+			uint32_t ntasks_per_node, min_nodes;
+			ntasks_per_node = max_cpu_cnt /
+				detail_ptr->cpus_per_task;
+			ntasks_per_node = MAX(ntasks_per_node, 1);
+			min_nodes = detail_ptr->num_tasks /
+				ntasks_per_node;
+			min_nodes = MAX(min_nodes,
+					detail_ptr->min_nodes);
+			pack32(min_nodes, buffer);
+			pack32(detail_ptr->max_nodes, buffer);
+		} else if (detail_ptr->mc_ptr &&
+			   detail_ptr->mc_ptr->ntasks_per_core &&
+			   (detail_ptr->mc_ptr->ntasks_per_core
+			    != INFINITE16)) {
+			/* min_nodes based upon task count and ntasks
+			 * per core */
+			uint32_t min_cores, min_nodes;
+			min_cores = ROUNDUP(detail_ptr->num_tasks,
+					    detail_ptr->mc_ptr->
+					    ntasks_per_core);
+			min_nodes = ROUNDUP(min_cores, max_core_cnt);
+			min_nodes = MAX(min_nodes,
+					detail_ptr->min_nodes);
+			pack32(min_nodes, buffer);
+			pack32(detail_ptr->max_nodes, buffer);
+		} else {
+			/* min_nodes based upon task count only */
+			uint32_t min_nodes;
+			uint32_t max_nodes;
+
+			min_nodes = ROUNDUP(detail_ptr->num_tasks,
+					    max_cpu_cnt);
+			min_nodes = MAX(min_nodes,
+					detail_ptr->min_nodes);
+			max_nodes = MAX(min_nodes,
+					detail_ptr->max_nodes);
+			pack32(min_nodes, buffer);
+			pack32(max_nodes, buffer);
+		}
+		if (detail_ptr->num_tasks)
+			pack32(detail_ptr->num_tasks, buffer);
+		else if (IS_JOB_PENDING(job_ptr))
+			pack32(detail_ptr->min_nodes, buffer);
+		else if (job_ptr->tres_alloc_cnt)
+			pack32((uint32_t)
+			       job_ptr->tres_alloc_cnt[TRES_ARRAY_NODE],
+			       buffer);
+		else
+			pack32(NO_VAL, buffer);
+
+		pack16(shared, buffer);
+
+		if (detail_ptr->crontab_entry)
+			packstr(detail_ptr->crontab_entry->cronspec,
+				buffer);
+		else
+			packnull(buffer);
+	} else if (protocol_version >= SLURM_24_11_PROTOCOL_VERSION) {
 		if (!detail_ptr) {
 			packbool(false, buffer);
 
diff --git a/src/slurmrestd/http.c b/src/slurmrestd/http.c
index 666dc3a..e4915f4 100644
--- a/src/slurmrestd/http.c
+++ b/src/slurmrestd/http.c
@@ -50,6 +50,8 @@
 #include "src/common/xmalloc.h"
 #include "src/common/xstring.h"
 
+#include "src/conmgr/conmgr.h"
+
 #include "src/interfaces/http_parser.h"
 
 #include "src/slurmrestd/http.h"
@@ -59,10 +61,8 @@
 #define MAGIC 0xDFAFFEEF
 #define MAX_BODY_BYTES 52428800 /* 50MB */
 
-#define MAGIC_REQUEST_T 0xdbadaaaf
 /* Data to handed around by http_parser to call backs */
 typedef struct {
-	int magic;
 	/* Requested URL */
 	url_t url;
 	/* Request HTTP method */
@@ -76,8 +76,6 @@
 	/* RFC7230-6.1 "Connection: Close" */
 	bool connection_close;
 	int expect; /* RFC7231-5.1.1 expect requested */
-	/* Connection context */
-	http_context_t *context;
 	/* Body of request (may be NULL) */
 	char *body;
 	/* if provided: expected body length to process or 0 */
@@ -93,6 +91,22 @@
 	} http_version;
 } request_t;
 
+typedef struct http_context_s {
+	int magic; /* MAGIC */
+	/* reference to assigned connection */
+	conmgr_fd_ref_t *ref;
+	/* assigned connection */
+	conmgr_fd_t *con;
+	/* Authentication context (auth_context_type_t) */
+	void *auth;
+	/* callback to call on each HTTP request */
+	on_http_request_t on_http_request;
+	/* http parser plugin state */
+	http_parser_state_t *parser;
+	/* http request_t */
+	request_t request;
+} http_context_t;
+
 /* default keep_alive value which appears to be implementation specific */
 static int DEFAULT_KEEP_ALIVE = 5; //default to 5s to match apache2
 
@@ -110,13 +124,6 @@
 	return ESLURM_HTTP_UNSUPPORTED_VERSION;
 }
 
-extern void free_http_header(http_header_entry_t *header)
-{
-	xfree(header->name);
-	xfree(header->value);
-	xfree(header);
-}
-
 static void _free_http_header(void *header)
 {
 	free_http_header(header);
@@ -124,16 +131,14 @@
 
 static void _request_init(http_context_t *context)
 {
-	request_t *request = context->request;
+	request_t *request = &context->request;
 
 	xassert(request);
 	xassert(context->magic == MAGIC);
 
 	*request = (request_t) {
-		.magic = MAGIC_REQUEST_T,
 		.url = URL_INITIALIZER,
 		.method = HTTP_REQUEST_INVALID,
-		.context = context,
 		.keep_alive = -1,
 	};
 
@@ -142,11 +147,9 @@
 
 static void _request_free_members(http_context_t *context)
 {
-	request_t *request = context->request;
+	request_t *request = &context->request;
 
 	xassert(context->magic == MAGIC);
-	xassert(request->magic == MAGIC_REQUEST_T);
-	xassert(request->context == context);
 
 	url_free_members(&request->url);
 	FREE_NULL_LIST(request->headers);
@@ -170,12 +173,11 @@
 
 static int _on_request(const http_parser_request_t *req, void *arg)
 {
-	request_t *request = arg;
-	http_context_t *context = request->context;
+	http_context_t *context = arg;
+	request_t *request = &context->request;
 	int rc = EINVAL;
 
 	xassert(context->magic == MAGIC);
-	xassert(request->magic == MAGIC_REQUEST_T);
 
 	request->http_version.major = req->http_version.major;
 	request->http_version.minor = req->http_version.minor;
@@ -228,12 +230,11 @@
 
 static int _on_header(const http_parser_header_t *header, void *arg)
 {
-	request_t *request = arg;
-	http_context_t *context = request->context;
-	http_header_entry_t *entry = NULL;
+	http_context_t *context = arg;
+	request_t *request = &context->request;
+	http_header_t *entry = NULL;
 
 	xassert(context->magic == MAGIC);
-	xassert(request->magic == MAGIC_REQUEST_T);
 
 	log_flag(NET, "%s: [%s] Header: %s Value: %s",
 		 __func__, conmgr_con_get_name(context->ref), header->name,
@@ -241,6 +242,7 @@
 
 	/* Add copy to list of headers */
 	entry = xmalloc(sizeof(*entry));
+	entry->magic = HTTP_HEADER_MAGIC;
 	entry->name = xstrdup(header->name);
 	entry->value = xstrdup(header->value);
 	list_append(request->headers, entry);
@@ -264,8 +266,7 @@
 			request->keep_alive = ibuffer;
 		} else {
 			error("%s: [%s] invalid Keep-Alive value %s",
-			      __func__,
-			      conmgr_fd_get_name(request->context->con),
+			      __func__, conmgr_con_get_name(context->ref),
 			      header->value);
 			return _send_reject(
 				context, HTTP_STATUS_CODE_ERROR_NOT_ACCEPTABLE,
@@ -319,11 +320,10 @@
 
 static int _on_headers_complete(void *arg)
 {
-	request_t *request = arg;
-	http_context_t *context = request->context;
+	http_context_t *context = arg;
+	request_t *request = &context->request;
 
 	xassert(context->magic == MAGIC);
-	xassert(request->magic == MAGIC_REQUEST_T);
 
 	if ((request->http_version.major == 1) &&
 	    (request->http_version.minor == 0)) {
@@ -355,13 +355,14 @@
 	if (request->expect) {
 		int rc = EINVAL;
 		send_http_response_args_t args = {
-			.con = context->con,
 			.http_major = request->http_version.major,
 			.http_minor = request->http_version.minor,
 			.status_code = request->expect,
 			.body_length = 0,
 		};
 
+		args.con = conmgr_fd_get_ref(context->ref);
+
 		if ((rc = send_http_response(&args)))
 			return rc;
 	}
@@ -371,13 +372,12 @@
 
 static int _on_content(const http_parser_content_t *content, void *arg)
 {
-	request_t *request = arg;
-	http_context_t *context = request->context;
+	http_context_t *context = arg;
+	request_t *request = &context->request;
 	const void *at = get_buf_data(content->buffer);
 	const size_t length = get_buf_offset(content->buffer);
 
 	xassert(context->magic == MAGIC);
-	xassert(request->magic == MAGIC_REQUEST_T);
 
 	log_flag_hex(NET_RAW, at, length, "%s: [%s] received HTTP content",
 	       __func__, conmgr_con_get_name(context->ref));
@@ -456,7 +456,7 @@
 static int _write_fmt_header(conmgr_fd_t *con, const char *name,
 			     const char *value)
 {
-	const char *buffer = _fmt_header(name, value);
+	char *buffer = _fmt_header(name, value);
 	int rc = conmgr_queue_write_data(con, buffer, strlen(buffer));
 	xfree(buffer);
 	return rc;
@@ -474,7 +474,13 @@
 	return xstrdup_printf("%s: %zu" CRLF, name, value);
 }
 
-extern int send_http_connection_close(http_context_t *ctxt)
+/*
+ * Send HTTP close notification header
+ *	Warns the client that we are about to close the connection.
+ * IN ctxt - connection context
+ * RET SLURM_SUCCESS or error
+ */
+static int _send_http_connection_close(http_context_t *ctxt)
 {
 	return _write_fmt_header(ctxt->con, "Connection", "Close");
 }
@@ -520,8 +526,9 @@
 	/* send along any requested headers */
 	if (args->headers) {
 		list_itr_t *itr = list_iterator_create(args->headers);
-		http_header_entry_t *header = NULL;
+		http_header_t *header = NULL;
 		while ((header = list_next(itr))) {
+			xassert(header->magic == HTTP_HEADER_MAGIC);
 			if ((rc = _write_fmt_header(args->con, header->name,
 						    header->value)))
 				break;
@@ -578,10 +585,8 @@
 static int _send_reject(http_context_t *context, http_status_code_t status_code,
 			slurm_err_t error_number)
 {
-	request_t *request = context->request;
-	xassert(request->magic == MAGIC_REQUEST_T);
+	request_t *request = &context->request;
 	send_http_response_args_t args = {
-		.con = request->context->con,
 		.http_major = request->http_version.major,
 		.http_minor = request->http_version.minor,
 		.status_code = status_code,
@@ -591,6 +596,8 @@
 
 	xassert(context->magic == MAGIC);
 
+	args.con = conmgr_fd_get_ref(context->ref);
+
 	/* If we don't have a requested client version, default to 0.9 */
 	if ((args.http_major == 0) && (args.http_minor == 0))
 		args.http_minor = 9;
@@ -602,10 +609,11 @@
 	if (request->connection_close ||
 	    _valid_http_version(request->http_version.major,
 				request->http_version.minor))
-		send_http_connection_close(request->context);
+		_send_http_connection_close(context);
 
 	/* ensure connection gets closed */
-	(void) conmgr_queue_close_fd(request->context->con);
+	conmgr_con_queue_close_free(&context->ref);
+	context->con = NULL;
 
 	/* reset connection to avoid any possible auth inheritance */
 	_request_reset(context);
@@ -613,10 +621,10 @@
 	return error_number;
 }
 
-static int _on_message_complete_request(request_t *request)
+static int _on_message_complete_request(http_context_t *context)
 {
 	int rc = EINVAL;
-	http_context_t *context = request->context;
+	request_t *request = &context->request;
 	on_http_request_args_t args = {
 		.method = request->method,
 		.headers = request->headers,
@@ -633,24 +641,28 @@
 	};
 
 	xassert(context->magic == MAGIC);
-	xassert(request->magic == MAGIC_REQUEST_T);
 
-	if ((rc = context->on_http_request(&args)))
+	if (!(args.con = conmgr_con_link(context->ref)) ||
+	    !(args.name = conmgr_con_get_name(args.con))) {
+		rc = SLURM_COMMUNICATIONS_MISSING_SOCKET_ERROR;
+		log_flag(NET, "%s: connection missing: %s",
+			 __func__, slurm_strerror(rc));
+	} else if ((rc = context->on_http_request(&args)))
 		log_flag(NET, "%s: [%s] on_http_request rejected: %s",
 			 __func__, conmgr_con_get_name(context->ref),
 			 slurm_strerror(rc));
 
+	conmgr_fd_free_ref(&args.con);
 	return rc;
 }
 
 static int _on_content_complete(void *arg)
 {
-	request_t *request = arg;
-	http_context_t *context = request->context;
+	http_context_t *context = arg;
+	request_t *request = &context->request;
 	int rc = EINVAL;
 
 	xassert(context->magic == MAGIC);
-	xassert(request->magic == MAGIC_REQUEST_T);
 
 	if ((request->expected_body_length > 0) &&
 	    (request->expected_body_length != request->body_length)) {
@@ -661,7 +673,7 @@
 				    ESLURM_HTTP_INVALID_CONTENT_LENGTH);
 	}
 
-	if ((rc = _on_message_complete_request(request)))
+	if ((rc = _on_message_complete_request(context)))
 		return rc;
 
 	if (request->keep_alive) {
@@ -673,7 +685,7 @@
 	if (request->connection_close) {
 		/* Notify client that this connection will be closed now */
 		if (request->connection_close)
-			send_http_connection_close(context);
+			_send_http_connection_close(context);
 
 		conmgr_con_queue_close_free(&context->ref);
 		context->con = NULL;
@@ -695,19 +707,15 @@
 		.on_content_complete = _on_content_complete,
 	};
 	int rc = SLURM_SUCCESS;
-	request_t *request = context->request;
 	ssize_t bytes_parsed = -1;
 	buf_t *buffer = NULL;
 
 	xassert(context->magic == MAGIC);
-	xassert(context->con);
-	xassert(context->ref);
-	xassert(request->magic == MAGIC_REQUEST_T);
-	xassert(request->context == context);
+	xassert(conmgr_fd_get_ref(context->ref) == context->con);
 
 	if (!context->parser &&
 	    (rc = http_parser_g_new_parse_request(
-		     conmgr_con_get_name(context->ref), &callbacks, request,
+		     conmgr_con_get_name(context->ref), &callbacks, context,
 		     &context->parser))) {
 		log_flag(NET, "%s: [%s] Creating new HTTP parser failed: %s",
 			 __func__, conmgr_con_get_name(context->ref),
@@ -754,59 +762,16 @@
 	return rc;
 }
 
-static http_context_t *_http_context_new(void)
-{
-	http_context_t *context = xmalloc(sizeof(*context));
-	context->magic = MAGIC;
-	return context;
-}
-
-/* find operator against http_header_entry_t */
-static int _http_header_find_key(void *x, void *y)
-{
-	http_header_entry_t *entry = (http_header_entry_t *)x;
-	const char *key = (const char *)y;
-	xassert(entry->name);
-
-	if (key == NULL)
-		return 0;
-	/* case insensitive compare per rfc2616:4.2 */
-	if (entry->name && !xstrcasecmp(entry->name, key))
-		return 1;
-	else
-		return 0;
-}
-
-extern const char *find_http_header(list_t *headers, const char *name)
-{
-	http_header_entry_t *header = NULL;
-
-	if (!headers || !name)
-		return NULL;
-
-	header = (http_header_entry_t *)list_find_first(
-		headers, _http_header_find_key, (void *)name);
-
-	if (header)
-		return header->value;
-	else
-		return NULL;
-}
-
 extern http_context_t *setup_http_context(conmgr_fd_t *con,
 					  on_http_request_t on_http_request)
 {
-	http_context_t *context = _http_context_new();
+	http_context_t *context = xmalloc(sizeof(*context));
 
-	xassert(context->magic == MAGIC);
-	xassert(!context->con);
-	xassert(!context->request);
+	context->magic = MAGIC;
 	context->con = con;
 	context->ref = conmgr_fd_new_ref(con);
 	context->on_http_request = on_http_request;
 
-	/* Must use type since context->request is void ptr */
-	context->request = xmalloc(sizeof(request_t));
 	_request_init(context);
 
 	return context;
@@ -815,7 +780,6 @@
 extern void on_http_connection_finish(conmgr_fd_t *con, void *ctxt)
 {
 	http_context_t *context = (http_context_t *) ctxt;
-	request_t *request = NULL;
 
 	if (!context)
 		return;
@@ -824,19 +788,51 @@
 	http_parser_g_free_parse_request(&context->parser);
 
 	/* release request */
-	request = context->request;
-	xassert(request->magic == MAGIC_REQUEST_T);
 	_request_free_members(context);
-	request->magic = ~MAGIC_REQUEST_T;
-	xfree(context->request);
 
 	/* auth should have been released long before now */
 	xassert(!context->auth);
 	FREE_NULL_REST_AUTH(context->auth);
 
+	xassert(conmgr_fd_get_ref(context->ref) == context->con);
 	conmgr_fd_free_ref(&context->ref);
 	context->con = NULL;
 
 	context->magic = ~MAGIC;
 	xfree(context);
 }
+
+extern void *http_context_get_auth(http_context_t *context)
+{
+	if (!context)
+		return NULL;
+
+	xassert(context->magic == MAGIC);
+
+	return context->auth;
+}
+
+extern void *http_context_set_auth(http_context_t *context, void *auth)
+{
+	void *old = NULL;
+
+	if (!context)
+		return auth;
+
+	xassert(context->magic == MAGIC);
+
+	old = context->auth;
+	context->auth = auth;
+
+	return old;
+}
+
+extern void http_context_free_null_auth(http_context_t *context)
+{
+	if (!context)
+		return;
+
+	xassert(context->magic == MAGIC);
+
+	FREE_NULL_REST_AUTH(context->auth);
+}
diff --git a/src/slurmrestd/http.h b/src/slurmrestd/http.h
index c219807..ec2aee7 100644
--- a/src/slurmrestd/http.h
+++ b/src/slurmrestd/http.h
@@ -43,7 +43,6 @@
 #include "src/common/list.h"
 
 #include "src/conmgr/conmgr.h"
-#include "src/interfaces/http_parser.h"
 
 struct on_http_request_args_s;
 typedef struct on_http_request_args_s on_http_request_args_t;
@@ -58,28 +57,17 @@
  */
 typedef int (*on_http_request_t)(on_http_request_args_t *args);
 
-typedef struct {
-	int magic;
-	/* reference to assigned connection */
-	conmgr_fd_ref_t *ref;
-	/* assigned connection */
-	conmgr_fd_t *con;
-	/* Authentication context (auth_context_type_t) */
-	void *auth;
-	/* callback to call on each HTTP request */
-	on_http_request_t on_http_request;
-	/* http parser plugin state */
-	http_parser_state_t *parser;
-	/* http request_t */
-	void *request;
-} http_context_t;
+/* Opaque connection context */
+typedef struct http_context_s http_context_t;
 
 typedef struct on_http_request_args_s {
 	const http_request_method_t method; /* HTTP request method */
-	list_t *headers; /* list of http_header_entry_t from client */
+	list_t *headers; /* list_t of http_header_t* from client */
 	const char *path; /* requested URL path (may be NULL) */
 	const char *query; /* requested URL query (may be NULL) */
 	http_context_t *context; /* calling context (do not xfree) */
+	conmgr_fd_ref_t *con; /* reference to connection */
+	const char *name; /* connection name */
 	uint16_t http_major; /* HTTP major version */
 	uint16_t http_minor; /* HTTP minor version */
 	const char *content_type; /* header content-type */
@@ -89,19 +77,6 @@
 	const char *body_encoding; /* body encoding type or NULL */
 } on_http_request_args_t;
 
-typedef struct {
-	char *name;
-	char *value;
-} http_header_entry_t;
-extern void free_http_header(http_header_entry_t *);
-
-/* find http header from header list
- * IN headers List of http_header_entry_t
- * IN name name of header to find
- * RET ptr to header value or NULL if not found
- */
-extern const char *find_http_header(list_t *headers, const char *name);
-
 /*
  * Call back for new connection to setup HTTP
  *
@@ -124,21 +99,14 @@
 	uint16_t http_major; /* HTTP major version */
 	uint16_t http_minor; /* HTTP minor version */
 	http_status_code_t status_code; /* HTTP status code to send */
-	list_t *headers; /* list of http_header_entry_t to send (can be empty) */
+	/* list of http_header_entry_t to send (can be empty) */
+	list_t *headers; /* list_t of http_header_t* from client */
 	const char *body; /* body to send or NULL */
 	size_t body_length; /* bytes in body to send or 0 */
 	const char *body_encoding; /* body encoding type or NULL */
 } send_http_response_args_t;
 
 /*
- * Send HTTP close notification.
- * 	Warns the client that we are about to close the connection.
- * IN args arguments of response
- * RET SLURM_SUCCESS or error
- */
-extern int send_http_connection_close(http_context_t *ctxt);
-
-/*
  * Send HTTP response
  * IN args arguments of response
  * RET SLURM_SUCCESS or error
@@ -161,4 +129,25 @@
  */
 extern void on_http_connection_finish(conmgr_fd_t *con, void *ctxt);
 
+/*
+ * Get (arbitrary) auth pointer from context
+ * IN context - connection context
+ * RET auth pointer or NULL
+ */
+extern void *http_context_get_auth(http_context_t *context);
+
+/*
+ * Set and Get (arbitrary) auth pointer from context
+ * IN context - connection context
+ * IN auth - (arbitrary) auth pointer to set into context
+ * RET Prior auth pointer or auth arg if context==NULL
+ */
+extern void *http_context_set_auth(http_context_t *context, void *auth);
+
+/*
+ * Release and NULL auth pointer from context
+ * IN context - connection context
+ */
+extern void http_context_free_null_auth(http_context_t *context);
+
 #endif /* SLURMRESTD_HTTP_H */
diff --git a/src/slurmrestd/operations.c b/src/slurmrestd/operations.c
index 4dfb9d5..b030a2a 100644
--- a/src/slurmrestd/operations.c
+++ b/src/slurmrestd/operations.c
@@ -46,6 +46,7 @@
 #include "src/common/xstring.h"
 #include "src/interfaces/serializer.h"
 
+#include "src/slurmrestd/http.h"
 #include "src/slurmrestd/operations.h"
 #include "src/slurmrestd/rest_auth.h"
 
@@ -76,11 +77,6 @@
 	float q; /* quality factor (priority) */
 } http_header_accept_t;
 
-static const char *_name(const on_http_request_args_t *args)
-{
-	return conmgr_fd_get_name(args->context->con);
-}
-
 static void _check_path_magic(const path_t *path)
 {
 	xassert(path->magic == PATH_MAGIC);
@@ -225,13 +221,12 @@
 	return rc;
 }
 
-static int _operations_router_reject(const on_http_request_args_t *args,
+static int _operations_router_reject(on_http_request_args_t *args,
 				     const char *err,
 				     http_status_code_t err_code,
 				     const char *body_encoding)
 {
 	send_http_response_args_t send_args = {
-		.con = args->context->con,
 		.headers = list_create(NULL),
 		.http_major = args->http_major,
 		.http_minor = args->http_minor,
@@ -240,18 +235,21 @@
 		.body_encoding = (body_encoding ? body_encoding : "text/plain"),
 		.body_length = (err ? strlen(err) : 0),
 	};
-	http_header_entry_t close = {
+	http_header_t close = {
+		.magic = HTTP_HEADER_MAGIC,
 		.name = "Connection",
 		.value = "Close",
 	};
 
+	send_args.con = conmgr_fd_get_ref(args->con);
+
 	/* Always warn that connection will be closed after the body is sent */
 	list_append(send_args.headers, &close);
 
 	(void) send_http_response(&send_args);
 
 	/* close connection on error */
-	conmgr_queue_close_fd(args->context->con);
+	conmgr_queue_close_fd(send_args.con);
 
 	FREE_NULL_LIST(send_args.headers);
 
@@ -408,7 +406,7 @@
 		*read_mime = MIME_TYPE_URL_ENCODED;
 
 		debug4("%s: [%s] did not provide a known content type header. Assuming URL encoded.",
-		       __func__, _name(args));
+		       __func__, args->name);
 	}
 
 	if (args->accept) {
@@ -419,17 +417,17 @@
 			xassert(ptr->magic == MAGIC_HEADER_ACCEPT);
 
 			debug4("%s: [%s] accepts %s with q=%f",
-			       __func__, _name(args), ptr->type, ptr->q);
+			       __func__, args->name, ptr->type, ptr->q);
 
 			if ((*write_mime = resolve_mime_type(ptr->type,
 							     plugin_ptr))) {
 				debug4("%s: [%s] found accepts %s=%s with q=%f",
-				       __func__, _name(args), ptr->type,
+				       __func__, args->name, ptr->type,
 				       *write_mime, ptr->q);
 				break;
 			} else {
 				debug4("%s: [%s] rejecting accepts %s with q=%f",
-				       __func__, _name(args), ptr->type,
+				       __func__, args->name, ptr->type,
 				       ptr->q);
 			}
 		}
@@ -437,7 +435,7 @@
 		FREE_NULL_LIST(accept);
 	} else {
 		debug3("%s: [%s] Accept header not specified. Defaulting to JSON.",
-		       __func__, _name(args));
+		       __func__, args->name);
 		*write_mime = MIME_TYPE_JSON;
 	}
 
@@ -478,14 +476,14 @@
 		 * requests.
 		 */
 		debug("%s: [%s] Overriding content type from %s to %s for %s",
-		      __func__, _name(args), *read_mime, MIME_TYPE_URL_ENCODED,
+		      __func__, args->name, *read_mime, MIME_TYPE_URL_ENCODED,
 		      get_http_method_string(args->method));
 
 		*read_mime = MIME_TYPE_URL_ENCODED;
 	}
 
 	debug3("%s: [%s] mime read: %s write: %s",
-	       __func__, _name(args), *read_mime, *write_mime);
+	       __func__, args->name, *read_mime, *write_mime);
 
 	return SLURM_SUCCESS;
 }
@@ -500,22 +498,24 @@
 	data_t *resp = data_new();
 	char *body = NULL;
 	http_status_code_t e;
+	void *auth = NULL;
 
 	xassert(op_path);
 	debug3("%s: [%s] BEGIN: calling ctxt handler: 0x%"PRIXPTR"[%d] for path: %s",
-	       __func__, _name(args), (uintptr_t) op_path->callback,
+	       __func__, args->name, (uintptr_t) op_path->callback,
 	       callback_tag, args->path);
 
-	rc = wrap_openapi_ctxt_callback(_name(args), args->method, params,
-					query, callback_tag, resp,
-					args->context->auth, parser, op_path,
-					meta);
+	auth = http_context_set_auth(args->context, NULL);
+
+	rc = wrap_openapi_ctxt_callback(args->name, args->method, params, query,
+					callback_tag, resp, auth, parser,
+					op_path, meta);
 
 	/*
 	 * Clear auth context after callback is complete. Client has to provide
 	 * full auth for every request already.
 	 */
-	FREE_NULL_REST_AUTH(args->context->auth);
+	FREE_NULL_REST_AUTH(auth);
 
 	if (data_get_type(resp) != DATA_TYPE_NULL) {
 		int rc2;
@@ -539,11 +539,11 @@
 		 *
 		 */
 		send_http_response_args_t send_args = {
-			.con = args->context->con,
 			.http_major = args->http_major,
 			.http_minor = args->http_minor,
 			.status_code = HTTP_STATUS_CODE_REDIRECT_NOT_MODIFIED,
 		};
+		send_args.con = conmgr_fd_get_ref(args->con);
 		e = send_args.status_code;
 		rc = send_http_response(&send_args);
 	} else if (rc && (rc != ESLURM_REST_EMPTY_RESULT)) {
@@ -580,7 +580,6 @@
 		rc = _operations_router_reject(args, body, e, write_mime);
 	} else {
 		send_http_response_args_t send_args = {
-			.con = args->context->con,
 			.http_major = args->http_major,
 			.http_minor = args->http_minor,
 			.status_code = HTTP_STATUS_CODE_SUCCESS_OK,
@@ -588,6 +587,8 @@
 			.body_length = 0,
 		};
 
+		send_args.con = conmgr_fd_get_ref(args->con);
+
 		if (body) {
 			send_args.body = body;
 			send_args.body_length = strlen(body);
@@ -599,7 +600,7 @@
 	}
 
 	debug3("%s: [%s] END: calling handler: (0x%"PRIXPTR") callback_tag %d for path: %s rc[%d]=%s status[%d]=%s",
-	       __func__, _name(args), (uintptr_t) op_path->callback,
+	       __func__, args->name, (uintptr_t) op_path->callback,
 	       callback_tag, args->path, rc, slurm_strerror(rc), e,
 	       get_http_status_code_string(e));
 
@@ -621,12 +622,12 @@
 	data_parser_t *parser = NULL;
 
 	info("%s: [%s] %s %s",
-	     __func__, _name(args), get_http_method_string(args->method),
+	     __func__, args->name, get_http_method_string(args->method),
 	     args->path);
 
 	if ((rc = rest_authenticate_http_request(args))) {
 		error("%s: [%s] authentication failed: %s",
-		      __func__, _name(args), slurm_strerror(rc));
+		      __func__, args->name, slurm_strerror(rc));
 		_operations_router_reject(args, "Authentication failure",
 					  HTTP_STATUS_CODE_ERROR_UNAUTHORIZED,
 					  NULL);
@@ -653,7 +654,7 @@
 	slurm_rwlock_unlock(&paths_lock);
 
 	debug5("%s: [%s] found callback handler: (0x%"PRIXPTR") callback_tag=%d path=%s parser=%s",
-	       __func__, _name(args), (uintptr_t) path->op_path->callback,
+	       __func__, args->name, (uintptr_t) path->op_path->callback,
 	       callback_tag, args->path,
 	       (parser ? data_parser_get_plugin(parser) : ""));
 
@@ -671,7 +672,7 @@
 	FREE_NULL_DATA(params);
 
 	/* always clear the auth context */
-	FREE_NULL_REST_AUTH(args->context->auth);
+	http_context_free_null_auth(args->context);
 
 	return rc;
 }
diff --git a/src/slurmrestd/plugins/auth/jwt/jwt.c b/src/slurmrestd/plugins/auth/jwt/jwt.c
index 7811b2e..2990a57 100644
--- a/src/slurmrestd/plugins/auth/jwt/jwt.c
+++ b/src/slurmrestd/plugins/auth/jwt/jwt.c
@@ -91,12 +91,12 @@
 					  rest_auth_context_t *ctxt)
 {
 	plugin_data_t *data;
-	const char *key, *user_name, *bearer, *name;
+	const char *key, *user_name, *bearer;
+	const char *name = args->name;
 
 	key = find_http_header(args->headers, HTTP_HEADER_USER_TOKEN);
 	bearer = find_http_header(args->headers, HTTP_HEADER_AUTH);
 	user_name = find_http_header(args->headers, HTTP_HEADER_USER_NAME);
-	name = conmgr_fd_get_name(args->context->con);
 
 	if (!key && !user_name && !bearer) {
 		debug3("%s: [%s] skipping token authentication",
diff --git a/src/slurmrestd/plugins/auth/local/local.c b/src/slurmrestd/plugins/auth/local/local.c
index f24f413..73084f6 100644
--- a/src/slurmrestd/plugins/auth/local/local.c
+++ b/src/slurmrestd/plugins/auth/local/local.c
@@ -134,15 +134,16 @@
 			const char *header_user_name)
 {
 	int rc;
-	const char *name = conmgr_fd_get_name(args->context->con);
+	const char *name = args->name;
+	conmgr_fd_t *con = conmgr_fd_get_ref(args->con);
 	uid_t cred_uid;
 	gid_t cred_gid;
 	pid_t cred_pid;
 
 	xassert(!ctxt->user_name);
 
-	if ((rc = conmgr_get_fd_auth_creds(args->context->con, &cred_uid,
-					   &cred_gid, &cred_pid))) {
+	if ((rc = conmgr_get_fd_auth_creds(con, &cred_uid, &cred_gid,
+					   &cred_pid))) {
 		/* socket may be remote, local auth doesn't apply */
 		debug("%s: [%s] unable to get socket ownership: %s",
 		      __func__, name, slurm_strerror(rc));
@@ -245,11 +246,11 @@
 	struct stat status = { 0 };
 	const char *header_user_name = find_http_header(args->headers,
 							HTTP_HEADER_USER_NAME);
-	const conmgr_fd_status_t cstatus =
-		conmgr_fd_get_status(args->context->con);
-	const int input_fd = conmgr_fd_get_input_fd(args->context->con);
-	const int output_fd = conmgr_fd_get_output_fd(args->context->con);
-	const char *name = conmgr_fd_get_name(args->context->con);
+	conmgr_fd_t *con = conmgr_fd_get_ref(args->con);
+	const conmgr_fd_status_t cstatus = conmgr_fd_get_status(con);
+	const int input_fd = conmgr_fd_get_input_fd(con);
+	const int output_fd = conmgr_fd_get_output_fd(con);
+	const char *name = args->name;
 
 	xassert(!ctxt->user_name);
 
diff --git a/src/slurmrestd/rest_auth.c b/src/slurmrestd/rest_auth.c
index 3148edf..3b1c379 100644
--- a/src/slurmrestd/rest_auth.c
+++ b/src/slurmrestd/rest_auth.c
@@ -164,18 +164,13 @@
 extern int rest_authenticate_http_request(on_http_request_args_t *args)
 {
 	int rc = ESLURM_AUTH_CRED_INVALID;
-	rest_auth_context_t *context =
-		(rest_auth_context_t *) args->context->auth;
-
-	if (context) {
-		fatal("%s: authentication context already set for connection: %s",
-		      __func__, conmgr_fd_get_name(args->context->con));
-	}
-
-	args->context->auth = context = rest_auth_g_new();
+	rest_auth_context_t *context = rest_auth_g_new();
 
 	_check_magic(context);
 
+	if (http_context_set_auth(args->context, context))
+		fatal_abort("authentication context already set for connection");
+
 	/* continue if already authenticated via plugin */
 	if (context->plugin_id)
 		return rest_auth_g_apply(context);
@@ -194,7 +189,10 @@
 			break;
 	}
 
-	FREE_NULL_REST_AUTH(args->context->auth);
+	if (http_context_set_auth(args->context, NULL) != context)
+		fatal_abort("authentication context unexpectedly changed");
+
+	FREE_NULL_REST_AUTH(context);
 	return rc;
 }
 
diff --git a/testsuite/python/lib/atf.py b/testsuite/python/lib/atf.py
index 13915e9..95ba0a2 100644
--- a/testsuite/python/lib/atf.py
+++ b/testsuite/python/lib/atf.py
@@ -1286,22 +1286,23 @@
     return tuple(int(part) if part.isdigit() else 0 for part in version_str.split("."))
 
 
-def require_version(version, component="sbin/slurmctld", slurm_prefix=""):
+def require_version(version, component="sbin/slurmctld", slurm_prefix="", reason=None):
     """Checks if the component is at least the required version, or skips.
 
     Args:
         version (tuple): The tuple representing the version.
         component (string): The bin/ or sbin/ component of Slurm to check.
         slurm_prefix (string): The path where the component is. By default the defined in testsuite.conf.
+        reason (string): The reason why the version of the component is required.
 
     Returns:
         A tuple representing the version. E.g. (25.05.0).
     """
     component_version = get_version(component, slurm_prefix)
     if component_version < version:
-        pytest.skip(
-            f"The version of {component} is {component_version}, required is {version}"
-        )
+        if not reason:
+            reason = f"The version of {component} is {component_version}, required is {version}"
+        pytest.skip(reason)
 
 
 def request_slurmrestd(request):
diff --git a/testsuite/python/tests/test_123_5.py b/testsuite/python/tests/test_123_5.py
index 7f85081..9b20de8 100644
--- a/testsuite/python/tests/test_123_5.py
+++ b/testsuite/python/tests/test_123_5.py
@@ -16,7 +16,12 @@
 def setup():
     global local_cluster_name
 
-    atf.require_accounting()
+    atf.require_version(
+        (25, 11),
+        "bin/scontrol",
+        reason="Creating reservations with qos= added in scontrol 25.11",
+    )
+    atf.require_accounting(True)
     atf.require_slurm_running()
     atf.require_config_parameter_includes("AccountingStorageEnforce", "qos")
     local_cluster_name = atf.get_config_parameter("ClusterName")
diff --git a/testsuite/python/tests/test_123_6.py b/testsuite/python/tests/test_123_6.py
index b0f126e..1f940c8 100644
--- a/testsuite/python/tests/test_123_6.py
+++ b/testsuite/python/tests/test_123_6.py
@@ -16,6 +16,12 @@
 def setup():
     global local_cluster_name
 
+    atf.require_version(
+        (25, 11),
+        "bin/scontrol",
+        reason="Creating reservations with allowedpartition= added in scontrol 25.11",
+    )
+    atf.require_accounting(True)
     atf.require_slurm_running()
     local_cluster_name = atf.get_config_parameter("ClusterName")