diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 00000000000..1bcd969c943 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,24 @@ +name: slurm C/C++ build + +on: + push: + branches: [20.11.ug, 22.05.ug, 24.05.ug] + pull_request: + branches: [20.11.ug, 22.05.ug, 24.05.ug] + +jobs: + build: + runs-on: ubuntu-20.04 + + steps: + - uses: actions/checkout@v2 + - name: Install deps + run: sudo apt-get install -y libmunge-dev + - name: configure + run: ./configure --enable-multiple-slurmd --prefix=/tmp/slurm/ + - name: make + run: make -j + - name: make check + run: make -j check + - name: make install + run: make -j install diff --git a/META b/META index d83af4e1674..c3441b69275 100644 --- a/META +++ b/META @@ -7,8 +7,8 @@ Name: slurm Major: 24 Minor: 05 - Micro: 4 - Version: 24.05.4 + Micro: 5 + Version: 24.05.5 Release: 1 ## diff --git a/NEWS b/NEWS index a6152e0a41a..4ce1c8bf55e 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,9 @@ This file describes changes in recent versions of Slurm. It primarily documents those changes that are of interest to users and administrators. +* Changes in Slurm 24.05.6 +========================== + * Changes in Slurm 24.05.5 ========================== -- Fix issue signaling cron jobs resulting in unintended requeues. @@ -14,6 +17,25 @@ documents those changes that are of interest to users and administrators. removal of a dynamic node. -- gpu/nvml - Attempt loading libnvidia-ml.so.1 as a fallback for failure in loading libnvidia-ml.so. + -- slurmrestd - Fix populating non-required object fields of objects as '{}' in + JSON/YAML instead of 'null' causing compiled OpenAPI clients to reject + the response to 'GET /slurm/v0.0.40/jobs' due to validation failure of + '.jobs[].job_resources'. + -- Fix sstat/sattach protocol errors for steps on higher version slurmd's + (regressions since 20.11.0rc1 and 16.05.1rc1 respectively). + -- slurmd - Avoid a crash when starting slurmd version 24.05 with + SlurmdSpoolDir files that have been upgraded to a newer major version of + Slurm. Log warnings instead. + -- Fix race condition in stepmgr step completion handling. + -- Fix slurmctld segfault with stepmgr and MpiParams when running a job array. + -- Fix requeued jobs keeping their priority until the decay thread happens. + -- slurmctld - Fix crash and possible split brain issue if the + backup controller handles an scontrol reconfigure while in control + before the primary resumes operation. + -- Fix stepmgr not getting dynamic node addrs from the controller + -- stepmgr - avoid "Unexpected missing socket" errors. + -- Fix `scontrol show steps` with dynamic stepmgr + -- Support IPv6 in configless mode. * Changes in Slurm 24.05.4 ========================== diff --git a/bdist_rpm.sh b/bdist_rpm.sh new file mode 100755 index 00000000000..294a641273b --- /dev/null +++ b/bdist_rpm.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +set -e +set -x + + +./build.sh diff --git a/build.sh b/build.sh new file mode 100755 index 00000000000..9e4b715b3c0 --- /dev/null +++ b/build.sh @@ -0,0 +1,150 @@ +#!/bin/bash + +set -e # exit whenever command in pipeline fails +set -x # print commands as they are executed + +VERSION=`grep "Version:.*[0-9]" slurm.spec | tr -s " " | awk '{print $2;}'` +RELEASE=`grep "%define rel.*[-1-9]" slurm.spec | tr -s " " | awk '{print $3}'` + +if [ "${RELEASE}" != "1" ]; then + SUFFIX=${VERSION}-${RELEASE} +else + SUFFIX=${VERSION} +fi + +GITTAG=$(git log --format=%ct.%h -1) + +SCRIPT=$(readlink -f "${BASH_SOURCE[0]}") +ORIGIN=$(dirname "$SCRIPT") + +# which version to download from github +SLURM_VERSION=${VERSION:-24.05.3} +UPSTREAM_REL=${UPSTREAM_REL:-1} + +# which release should be used for our RPMs +OUR_RELEASE=${RELEASE:-1} + +# NVML +# allow _empty_ version, which is used in pipeline + +if grep "release 8.8" /etc/redhat-release; then + NVIDIA_DRIVER=${NVIDIA_DRIVER-555.42.06} + NVDRV_NVML_PKG="nvidia-driver-NVML${NVIDIA_DRIVER:+-$NVIDIA_DRIVER}" + CUDA_VERSION=${CUDA_VERSION:-12.6} + CUDA_NVML_PKG="cuda-nvml-devel-${CUDA_VERSION//./-}" +elif grep "release 9.4" /etc/redhat-release; then + NVIDIA_DRIVER=${NVIDIA_DRIVER-555.42.06} + NVDRV_NVML_PKG="nvidia-driver-NVML${NVIDIA_DRIVER:+-$NVIDIA_DRIVER}" + CUDA_VERSION=${CUDA_VERSION:-12.6} + CUDA_NVML_PKG="cuda-nvml-devel-${CUDA_VERSION//./-}" +fi + +# Prepare directory structure +rm -Rf $ORIGIN/rpmbuild/ $ORIGIN/dist/ +mkdir -p $ORIGIN/rpmbuild/{BUILD,RPMS,SRPMS,SOURCES} $ORIGIN/dist +echo "Building source tarball" + +# archive git repo +echo "archive suffix: ${SUFFIX}" +git archive --format=tar.gz -o "rpmbuild/SOURCES/slurm-${SUFFIX}.tar.gz" --prefix="slurm-${SUFFIX}/" HEAD + +cp slurm.spec "SPECS" + +# Patch sources +#for src_patch in $ORIGIN/src-patches/*.patch; do + #echo "Patching $src_patch" + #git am $src_patch +#done + +# Patch spec file +#for spec_patch in $ORIGIN/spec-patches/*.patch; do + #echo "Patching $spec_patch" + #patch -p1 -b -i $spec_patch +#done + +# Install dependencies +# see https://slurm.schedmd.com/quickstart_admin.html#build_install + +echo "Installing specfile requires" +# this goes first because it might install undesired stuff related to `--with` options +sudo dnf -y builddep slurm.spec + +# dependecy versions + +if grep "release 8.8" /etc/redhat-release; then + UCX_VERSION="1.13.1-2.el8.x86_64" + PMIX_VERSION=">= 4.2.6" + HWLOC_VERSION=">= 2.2.0-3" +elif grep "release 9.2" /etc/redhat-release; then + UCX_VERSION="1.13.1-2.el9.x86_64" + PMIX_VERSION=">= 4.2.7" + HWLOC_VERSION=">= 2.4.1-5" +elif grep "release 9.4" /etc/redhat-release; then + UCX_VERSION="1.15.0-2.el9.x86_64" + PMIX_VERSION=">= 4.2.7" + HWLOC_VERSION=">= 2.4.1-5" +else + echo "unsupported OS release" + exit 1 +fi + +echo "Installing dependencies" +# - features: basic +sudo dnf -y install lua-devel mariadb-devel lz4-devel +# - features: authentication (MUNGE: yes, JWT: yes, PAM: yes) +sudo dnf -y install munge-devel libjwt-devel pam-devel +# - features: slurmrestd +sudo dnf -y install http-parser-devel json-c-devel libyaml-devel +# - features: Nvidia NVML +sudo dnf -y autoremove cuda-nvml-* nvidia-driver-NVML-* nvidia-driver* libnvidia-ml* +sudo dnf -y install "$CUDA_NVML_PKG" "$NVDRV_NVML_PKG" "nvidia-driver-devel" +# - plugins: MPI +sudo dnf -y install pmix "pmix-devel ${PMIX_VERSION}" "ucx-devel-${UCX_VERSION}" +# - plugins: cgroup/v2 +# see https://slurm.schedmd.com/cgroup_v2.html +sudo dnf -y install kernel-headers dbus-devel +# - plugins: task/cgroup, task/affinity +sudo dnf -y install "hwloc-devel ${HWLOC_VERSION}" numactl-devel +# - plugins: acct_gather_profile/hdf5 +sudo dnf -y install hdf5-devel + +# Build defines +RPM_DEFINES=( --define "gittag ${GITTAG}" --define "_topdir $ORIGIN/rpmbuild" ) + +# Build options +SLURM_BUILDOPTS=( --with slurmrestd --without debug ) +# basic features +SLURM_BUILDOPTS+=( --with lua --with mysql --with x11 ) +# plugins +SLURM_BUILDOPTS+=( --with numa --with hwloc --with pmix --with ucx ) +# authentication +SLURM_BUILDOPTS+=( --with pam --with jwt ) + +echo "Running rpmbuild (without nvml)" +rpmbuild -ba "${RPM_DEFINES[@]}" "${SLURM_BUILDOPTS[@]}" --without nvml \ + slurm.spec 2>&1 | tee rpmbuild-without-nvml.out + + +echo "Doing rpm rebuild (without nvml)" +for rpm in $ORIGIN/rpmbuild/RPMS/x86_64/slurm-*$SUFFIX*.rpm ; do + rpmrebuild --release=${OUR_RELEASE}.${GITTAG}$(rpm -E '%dist').nogpu.ug -d $ORIGIN/dist -p $rpm +done + + +echo "Running rpmbuild (with nvml)" +RPM_DEFINES+=( --define "_cuda_version $CUDA_VERSION" ) +rpmbuild -ba "${RPM_DEFINES[@]}" "${SLURM_BUILDOPTS[@]}" --with nvml \ + slurm.spec 2>&1 | tee rpmbuild-with-nvml.out + +echo "Doing rpm rebuild (with nvml)" +for rpm in $ORIGIN/rpmbuild/RPMS/x86_64/slurm-*$SUFFIX*.rpm ; do + rpmrebuild --release=${OUR_RELEASE}.${GITTAG}$(rpm -E '%dist').ug -d $ORIGIN/dist -p $rpm +done + +# strip out torque binaries/wrapper from slurm-torque +rpmrebuild -d $ORIGIN/dist --change-spec-files="sed '/\(pbsnodes\|mpiexec\|bin\/q.\+\)/d'" -p $ORIGIN/dist/x86_64/slurm-torque-*-${OUR_RELEASE}.${GITTAG}$(rpm -E '%dist').nogpu.ug*.rpm +rpmrebuild -d $ORIGIN/dist --change-spec-files="sed '/\(pbsnodes\|mpiexec\|bin\/q.\+\)/d'" -p $ORIGIN/dist/x86_64/slurm-torque-*-${OUR_RELEASE}.${GITTAG}$(rpm -E '%dist').ug.*.rpm + +# get the RPMs out of the subdirectories +find rpmbuild -type f -name "*.rpm" -exec rm {} ";" +find $ORIGIN/dist/ -type f -name '*.rpm' -print0 | xargs -0 -I{} mv {} rpmbuild/RPMS/x86_64/ diff --git a/contribs/pam_slurm_adopt/pam_slurm_adopt.c b/contribs/pam_slurm_adopt/pam_slurm_adopt.c index 336c01fa616..de6766819ae 100644 --- a/contribs/pam_slurm_adopt/pam_slurm_adopt.c +++ b/contribs/pam_slurm_adopt/pam_slurm_adopt.c @@ -75,6 +75,8 @@ typedef enum { CALLERID_ACTION_ALLOW, CALLERID_ACTION_IGNORE, CALLERID_ACTION_DENY, + CALLERID_ACTION_ADOPT_AND_CHECK, + CALLERID_ACTION_ONLY_CHECK, } callerid_action_t; /* module options */ @@ -88,6 +90,7 @@ static struct { callerid_action_t action_unknown; callerid_action_t action_adopt_failure; callerid_action_t action_generic_failure; + callerid_action_t action_adopt; log_level_t log_level; char *node_name; bool disable_x11; @@ -103,6 +106,7 @@ static void _init_opts(void) opts.action_unknown = CALLERID_ACTION_NEWEST; opts.action_adopt_failure = CALLERID_ACTION_ALLOW; opts.action_generic_failure = CALLERID_ACTION_IGNORE; + opts.action_adopt = CALLERID_ACTION_ADOPT_AND_CHECK; opts.log_level = LOG_LEVEL_INFO; opts.node_name = NULL; opts.disable_x11 = false; @@ -429,9 +433,15 @@ static int _action_unknown(pam_handle_t *pamh, struct passwd *pwd, list_t *steps rc = _indeterminate_multiple(pamh, steps, pwd->pw_uid, &stepd); if (rc == PAM_SUCCESS) { info("action_unknown: Picked job %u", stepd->step_id.job_id); - if (_adopt_process(pamh, getpid(), stepd) == SLURM_SUCCESS) { - return PAM_SUCCESS; - } + if (opts.action_adopt == CALLERID_ACTION_ADOPT_AND_CHECK) { + if (_adopt_process(pamh, getpid(), stepd) == SLURM_SUCCESS) { + return PAM_SUCCESS; + } + if (opts.action_adopt_failure == CALLERID_ACTION_ALLOW) + return PAM_SUCCESS; + else + return PAM_PERM_DENIED; + } if (opts.action_adopt_failure == CALLERID_ACTION_ALLOW) return PAM_SUCCESS; else @@ -536,24 +546,27 @@ static int _try_rpc(pam_handle_t *pamh, struct passwd *pwd) /* Ask the slurmd at the source IP address about this connection */ rc = _rpc_network_callerid(&conn, pwd->pw_name, &job_id); if (rc == SLURM_SUCCESS) { - step_loc_t stepd; - memset(&stepd, 0, sizeof(stepd)); - /* We only need the step_id struct needed to be filled in here - all the rest isn't needed for the adopt. - */ - stepd.step_id.job_id = job_id; - stepd.step_id.step_id = SLURM_EXTERN_CONT; - stepd.step_id.step_het_comp = NO_VAL; - - /* Adopt the process. If the adoption succeeds, return SUCCESS. - * If not, maybe the adoption failed because the user hopped - * into one node and was adopted into a job there that isn't on - * our node here. In that case we got a bad jobid so we'll fall - * through to the next action */ - if (_adopt_process(pamh, getpid(), &stepd) == SLURM_SUCCESS) - return PAM_SUCCESS; - else - return PAM_IGNORE; + if (opts.action_adopt == CALLERID_ACTION_ADOPT_AND_CHECK) { + step_loc_t stepd; + memset(&stepd, 0, sizeof(stepd)); + /* We only need the step_id struct needed to be filled in here + all the rest isn't needed for the adopt. + */ + stepd.step_id.job_id = job_id; + stepd.step_id.step_id = SLURM_EXTERN_CONT; + stepd.step_id.step_het_comp = NO_VAL; + + /* Adopt the process. If the adoption succeeds, return SUCCESS. + * If not, maybe the adoption failed because the user hopped + * into one node and was adopted into a job there that isn't on + * our node here. In that case we got a bad jobid so we'll fall + * through to the next action */ + if (_adopt_process(pamh, getpid(), &stepd) == SLURM_SUCCESS) + return PAM_SUCCESS; + else + return PAM_IGNORE; + } + return PAM_SUCCESS; } info("From %s port %d as %s: unable to determine source job", @@ -645,6 +658,10 @@ static void _parse_opts(pam_handle_t *pamh, int argc, const char **argv) "unrecognized action_unknown=%s, setting to 'newest'", v); } + } else if (!xstrncasecmp(*argv, "action_adopt=", 13)){ + v = (char *)(13 + *argv); + if (!xstrncasecmp(v, "check_only", 10)) + opts.action_adopt = CALLERID_ACTION_ONLY_CHECK; } else if (!xstrncasecmp(*argv,"action_generic_failure=",23)) { v = (char *)(23 + *argv); if (!xstrncasecmp(v, "allow", 5)) @@ -747,9 +764,8 @@ static int check_pam_service(pam_handle_t *pamh) * 3) Pick a job semi-randomly (default) or skip the adoption (if * configured) */ -PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags - __attribute__((unused)), int argc, const char **argv) -{ +PAM_EXTERN int _adopt_and_or_check(pam_handle_t *pamh, int flags + __attribute__((unused)), int argc, const char **argv) { int retval = PAM_IGNORE, rc = PAM_IGNORE, slurmrc, bufsize, user_jobs; char *user_name; list_t *steps = NULL; @@ -876,20 +892,24 @@ PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags if (opts.single_job_skip_rpc) { info("Connection by user %s: user has only one job %u", user_name, stepd->step_id.job_id); - slurmrc = _adopt_process(pamh, getpid(), stepd); - /* If adoption into the only job fails, it is time to - * exit. Return code is based on the - * action_adopt_failure setting */ - if (slurmrc == SLURM_SUCCESS || - (opts.action_adopt_failure == - CALLERID_ACTION_ALLOW)) + if (opts.action_adopt == CALLERID_ACTION_ADOPT_AND_CHECK) { + slurmrc = _adopt_process(pamh, getpid(), stepd); + /* If adoption into the only job fails, it is time to + * exit. Return code is based on the + * action_adopt_failure setting */ + if (slurmrc == SLURM_SUCCESS || + (opts.action_adopt_failure == + CALLERID_ACTION_ALLOW)) + rc = PAM_SUCCESS; + else { + send_user_msg(pamh, "Access denied by " + PAM_MODULE_NAME + ": failed to adopt process into cgroup, denying access because action_adopt_failure=deny"); + rc = PAM_PERM_DENIED; + } + } else { rc = PAM_SUCCESS; - else { - send_user_msg(pamh, "Access denied by " - PAM_MODULE_NAME - ": failed to adopt process into cgroup, denying access because action_adopt_failure=deny"); - rc = PAM_PERM_DENIED; - } + } goto cleanup; } } else { @@ -916,6 +936,33 @@ PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags return rc; } +/* Take control of the session, to avoid other pam modules doing the + * same and change e.g., cgroups. + */ +PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags + __attribute__((unused)), int argc, const char **argv) +{ + return _adopt_and_or_check(pamh, flags, argc, argv); +} + +/* Close the session. Always succeeds, we do not need to do anything here. + */ +PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags + __attribute__((unused)), int argc, const char **argv) +{ + return PAM_SUCCESS; +} + + +/* Implementation for the pam_acct_mgmt API call. + */ +PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags + __attribute__((unused)), int argc, const char **argv) +{ + return _adopt_and_or_check(pamh, flags, argc, argv); +} + + #ifdef PAM_STATIC struct pam_module _pam_slurm_adopt_modstruct = { PAM_MODULE_NAME, diff --git a/debian/changelog b/debian/changelog index 01eb1252b30..8bb6b7e6c24 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,4 +1,4 @@ -slurm-smd (24.05.4-1) UNRELEASED; urgency=medium +slurm-smd (24.05.5-1) UNRELEASED; urgency=medium * Initial release. diff --git a/doc/html/containers.shtml b/doc/html/containers.shtml index fcbb184fa97..0f5ee7aef6c 100644 --- a/doc/html/containers.shtml +++ b/doc/html/containers.shtml @@ -78,7 +78,11 @@ job or any given plugin).

Prerequisites

The host kernel must be configured to allow user land containers:

-
$ sudo sysctl -w kernel.unprivileged_userns_clone=1
+
+sudo sysctl -w kernel.unprivileged_userns_clone=1
+sudo sysctl -w kernel.apparmor_restrict_unprivileged_unconfined=0
+sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
+

Docker also provides a tool to verify the kernel configuration:

$ dockerd-rootless-setuptool.sh check --force
@@ -353,6 +357,62 @@ exit $rc
 

+

Handling multiple runtimes + +

+ +

If you wish to accommodate multiple runtimes in your environment, +it is possible to do so with a bit of extra setup. This section outlines one +possible way to do so:

+ +
    +
  1. Create a generic oci.conf that calls a wrapper script +
    +IgnoreFileConfigJson=true
    +RunTimeRun="/opt/slurm-oci/run %b %m %u %U %n %j %s %t %@"
    +RunTimeKill="kill -s SIGTERM %p"
    +RunTimeDelete="kill -s SIGKILL %p"
    +
    +
  2. +
  3. Create the wrapper script to check for user-specific run configuration +(e.g., /opt/slurm-oci/run) +
    +#!/bin/bash
    +if [[ -e ~/.slurm-oci-run ]]; then
    +	~/.slurm-oci-run "$@"
    +else
    +	/opt/slurm-oci/slurm-oci-run-default "$@"
    +fi
    +
    +
  4. +
  5. Create a generic run configuration to use as the default +(e.g., /opt/slurm-oci/slurm-oci-run-default) +
    +#!/bin/bash --login
    +# Parse
    +CONTAINER="$1"
    +SPOOL_DIR="$2"
    +USER_NAME="$3"
    +USER_ID="$4"
    +NODE_NAME="$5"
    +JOB_ID="$6"
    +STEP_ID="$7"
    +TASK_ID="$8"
    +shift 8 # subsequent arguments are the command to run in the container
    +# Run
    +apptainer run --bind /var/spool --containall "$CONTAINER" "$@"
    +
    +
  6. +
  7. Add executable permissions to both scripts +
    chmod +x /opt/slurm-oci/run /opt/slurm-oci/slurm-oci-run-default
    +
  8. +
+ +

Once this is done, users may create a script at '~/.slurm-oci-run' if +they wish to customize the container run process, such as using a different +container runtime. Users should model this file after the default +'/opt/slurm-oci/slurm-oci-run-default'

+

Testing OCI runtime outside of Slurm

@@ -458,11 +518,16 @@ scrun being isolated from the network and not being able to communicate with the Slurm controller. The container is run by Slurm on the compute nodes which makes having Docker setup a network isolation layer ineffective for the container. -
  • docker exec
    command is not supported.
  • -
  • docker compose
    command is not supported.
  • -
  • docker pause
    command is not supported.
  • -
  • docker unpause
    command is not supported.
  • -
  • docker swarm
    command is not supported.
  • +
  • docker exec command is not supported.
  • +
  • docker swarm command is not supported.
  • +
  • docker compose/docker-compose command is not + supported.
  • +
  • docker pause command is not supported.
  • +
  • docker unpause command is not supported.
  • +
  • docker swarm command is not supported.
  • +
  • All docker commands are not supported inside of containers.
  • +
  • Docker API is + not supported inside of containers.
  • Setup procedure

    @@ -580,9 +645,16 @@ configuration.
  • All containers must use host networking
  • -
  • podman exec
    command is not supported.
  • -
  • podman kube
    command is not supported.
  • -
  • podman pod
    command is not supported.
  • +
  • podman exec command is not supported.
  • +
  • podman-compose command is not supported, due to only being + partially implemented. Some compositions may work but each container + may be run on different nodes. The network for all containers must be + the network_mode: host device.
  • +
  • podman kube command is not supported.
  • +
  • podman pod command is not supported.
  • +
  • podman farm command is not supported.
  • +
  • All podman commands are not supported inside of containers.
  • +
  • Podman REST API is not supported inside of containers.
  • Setup procedure

    @@ -875,6 +947,6 @@ Overview slides of Sarus are
    -

    Last modified 08 October 2024

    +

    Last modified 27 November 2024

    diff --git a/doc/html/faq.shtml b/doc/html/faq.shtml index 9f7d09449e2..4d53e124d4f 100644 --- a/doc/html/faq.shtml +++ b/doc/html/faq.shtml @@ -1231,9 +1231,9 @@ that node may be rendered unusable, but no other harm will result.

    Do I need to maintain synchronized clocks on the cluster?
    -In general, yes. Having inconsistent clocks may cause nodes to -be unusable. Slurm log files should contain references to -expired credentials. For example:

    +In general, yes. Having inconsistent clocks may cause nodes to be unusable and +generate errors in Slurm log files regarding expired credentials. For example: +

     error: Munge decode failed: Expired credential
     ENCODED: Wed May 12 12:34:56 2008
    @@ -2438,6 +2438,6 @@ dset TV::parallel_configs {
     }
     !-->
     
    -

    Last modified 07 November 2024

    +

    Last modified 19 November 2024

    diff --git a/doc/html/rest_api.shtml b/doc/html/rest_api.shtml index 3e29da52719..f540d0c64dd 100644 --- a/doc/html/rest_api.shtml +++ b/doc/html/rest_api.shtml @@ -3,7 +3,7 @@
    API to access and control Slurm
    More information: https://www.schedmd.com/
    Contact Info: sales@schedmd.com
    -
    Version: Slurm-24.05.4&openapi/slurmdbd&openapi/slurmctld
    +
    Version: Slurm-24.05.5&openapi/slurmdbd&openapi/slurmctld
    BasePath:
    Apache 2.0
    https://www.apache.org/licenses/LICENSE-2.0.html
    @@ -6169,7 +6169,7 @@
    Query Parameter — CSV QOS list default: null
    format (optional)
    -
    Query Parameter — CSV format list default: null
    id (optional)
    +
    Query Parameter — Ignored; process JSON manually to control output format default: null
    id (optional)
    Query Parameter — CSV id list default: null
    only_defaults (optional)
    @@ -6297,7 +6297,7 @@
    Query Parameter — CSV QOS list default: null
    format (optional)
    -
    Query Parameter — CSV format list default: null
    id (optional)
    +
    Query Parameter — Ignored; process JSON manually to control output format default: null
    id (optional)
    Query Parameter — CSV id list default: null
    only_defaults (optional)
    @@ -6433,7 +6433,7 @@
    Query Parameter — Query flags default: null
    format (optional)
    -
    Query Parameter — CSV format list default: null
    rpc_version (optional)
    +
    Query Parameter — Ignored; process JSON manually to control output format default: null
    rpc_version (optional)
    Query Parameter — CSV RPC version list default: null
    usage_end (optional)
    @@ -7132,7 +7132,7 @@
    Query Parameter — CSV QOS list default: null
    format (optional)
    -
    Query Parameter — CSV format list default: null
    id (optional)
    +
    Query Parameter — Ignored; process JSON manually to control output format default: null
    id (optional)
    Query Parameter — CSV id list default: null
    only_defaults (optional)
    @@ -7640,7 +7640,7 @@
    Query Parameter — CSV QOS list default: null
    format (optional)
    -
    Query Parameter — CSV format list default: null
    id (optional)
    +
    Query Parameter — Ignored; process JSON manually to control output format default: null
    id (optional)
    Query Parameter — CSV id list default: null
    only_defaults (optional)
    @@ -8156,7 +8156,7 @@
    Query Parameter — Query flags default: null
    format (optional)
    -
    Query Parameter — CSV format list default: null
    rpc_version (optional)
    +
    Query Parameter — Ignored; process JSON manually to control output format default: null
    rpc_version (optional)
    Query Parameter — CSV RPC version list default: null
    usage_end (optional)
    @@ -9960,7 +9960,7 @@
    Query Parameter — CSV extra list default: null
    format (optional)
    -
    Query Parameter — CSV format list default: null
    instance_id (optional)
    +
    Query Parameter — Ignored; process JSON manually to control output format default: null
    instance_id (optional)
    Query Parameter — CSV instance_id list default: null
    instance_type (optional)
    @@ -10088,7 +10088,7 @@
    Query Parameter — CSV extra list default: null
    format (optional)
    -
    Query Parameter — CSV format list default: null
    instance_id (optional)
    +
    Query Parameter — Ignored; process JSON manually to control output format default: null
    instance_id (optional)
    Query Parameter — CSV instance_id list default: null
    instance_type (optional)
    @@ -11488,7 +11488,7 @@
    Query Parameter — Include job environment default: null
    format (optional)
    -
    Query Parameter — CSV format list default: null
    groups (optional)
    +
    Query Parameter — Ignored; process JSON manually to control output format default: null
    groups (optional)
    Query Parameter — CSV group list default: null
    job_name (optional)
    @@ -12758,7 +12758,7 @@
    Query Parameter — CSV QOS id list default: null
    format (optional)
    -
    Query Parameter — CSV format list default: null
    name (optional)
    +
    Query Parameter — Ignored; process JSON manually to control output format default: null
    name (optional)
    Query Parameter — CSV QOS name list default: null
    preempt_mode (optional)
    @@ -14740,7 +14740,7 @@
    Query Parameter — CSV cluster name list default: null
    format (optional)
    -
    Query Parameter — CSV format name list default: null
    id (optional)
    +
    Query Parameter — Ignored; process JSON manually to control output format default: null
    id (optional)
    Query Parameter — CSV id list default: null
    name (optional)
    @@ -15436,7 +15436,7 @@
    Query Parameter — CSV QOS id list default: null
    format (optional)
    -
    Query Parameter — CSV format list default: null
    name (optional)
    +
    Query Parameter — Ignored; process JSON manually to control output format default: null
    name (optional)
    Query Parameter — CSV QOS name list default: null
    preempt_mode (optional)
    @@ -15859,7 +15859,7 @@
    Query Parameter — CSV cluster name list default: null
    format (optional)
    -
    Query Parameter — CSV format name list default: null
    id (optional)
    +
    Query Parameter — Ignored; process JSON manually to control output format default: null
    id (optional)
    Query Parameter — CSV id list default: null
    name (optional)
    diff --git a/doc/man/man1/srun.1 b/doc/man/man1/srun.1 index 3f6b128bc9f..fc2ff9d03f3 100644 --- a/doc/man/man1/srun.1 +++ b/doc/man/man1/srun.1 @@ -1,4 +1,4 @@ -.TH srun "1" "Slurm Commands" "October 2024" "Slurm Commands" +.TH srun "1" "Slurm Commands" "November 2024" "Slurm Commands" .SH "NAME" srun \- Run parallel jobs @@ -4647,6 +4647,35 @@ This behavior can be changed by adding \fBSelectTypeParameters=CR_Pack_Nodes\fR to your slurm.conf. The logic to pack nodes will allow job steps to start on a single node without having to explicitly request a single node. +.TP +\fBExample 11:\fR +This example demonstrates that adding the \fB\-\-exclusive\fR flag to job +allocation requests can give different results based on whether you also +request a certain number of tasks. + +Requesting exclusive access with no additional requirements will allow the +process to access all the CPUs on the allocated node. +.nf +$ srun \-l \-\-exclusive bash \-c 'grep Cpus_allowed_list /proc/self/status' +0: Cpus_allowed_list: 0\-23 +.fi + +Adding a request for a certain number of tasks will cause each task to only +have access to a single CPU. +.nf +$ srun \-l \-\-exclusive \-n2 bash \-c 'grep Cpus_allowed_list /proc/self/status' +0: Cpus_allowed_list: 0 +1: Cpus_allowed_list: 12 +.fi + +You can define the number of CPUs per task if you want to give them access to +more than one CPU. +.nf +$ srun \-l \-\-exclusive \-n2 \-\-cpus\-per\-task=12 bash \-c 'grep Cpus_allowed_list /proc/self/status' +0: Cpus_allowed_list: 0\-5,12\-17 +1: Cpus_allowed_list: 6\-11,18\-23 +.fi + .SH "COPYING" Copyright (C) 2006\-2007 The Regents of the University of California. Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER). diff --git a/doc/man/man5/slurm.conf.5 b/doc/man/man5/slurm.conf.5 index 165c7e2a6e4..98ba99e476f 100644 --- a/doc/man/man5/slurm.conf.5 +++ b/doc/man/man5/slurm.conf.5 @@ -2719,8 +2719,10 @@ plugin. .TP \fBreorder_count=#\fR Specify how many attempts should be made in reordering preemptable jobs to -minimize the count of jobs preempted. +minimize the total number of jobs that will be preempted. The default value is 1. High values may adversely impact performance. +Changes to the order of jobs on these attempts can be enabled with +\fBstrict_order\fR. The logic to support this option is only available in the select/cons_tres plugin. .IP @@ -2735,10 +2737,11 @@ otherwise a SIGTERM will be sent to the tasks. .TP \fBstrict_order\fR -If set, then execute extra logic in an attempt to preempt only the lowest -priority jobs. -It may be desirable to set this configuration parameter when there are multiple -priorities of preemptable jobs. +When reordering preemptable jobs, place the most recently tested job at the +front of the list since we are certain that it actually added resources needed +by the new job. This ensures that with enough reorder attempts, the minimum +possible number of jobs will be preempted. +See also \fBreorder_count=#\fR. The logic to support this option is only available in the select/cons_tres plugin. .IP @@ -4152,6 +4155,13 @@ Enable job steps that span heterogeneous job allocations. The default value. .IP +.TP +\fBenable_job_state_cache\fR +Enables an independent cache of job state details within slurmctld. This allows +processing of `\fBsqueue\fR \-\-only\-job\-state` and replaced RPCs with minimal +impact on other slurmctld operations. +.IP + .TP \fBenable_user_top\fR Enable use of the "scontrol top" command by non\-privileged users. @@ -4843,13 +4853,6 @@ filenames have no path separators and are located adjacent to slurm.conf. Glob patterns (See \fBglob\fR (7)) are not supported. .IP -.TP -\fBenable_job_state_cache\fR -Enables an independent cache of job state details within slurmctld. This allows -processing of `\fBsqueue\fR \-\-only\-job\-state` and replaced RPCs with minimal -impact on other slurmctld operations. -.IP - .TP \fBidle_on_node_suspend\fR Mark nodes as idle, regardless of current state, when suspending nodes with diff --git a/doc/man/man8/slurmd.8 b/doc/man/man8/slurmd.8 index e35ff45dad3..7f4a4a3f605 100644 --- a/doc/man/man8/slurmd.8 +++ b/doc/man/man8/slurmd.8 @@ -77,10 +77,13 @@ NodeName=node1 CPUs=16 RealMemory=30000 Gres=gpu:2" .IP .TP -\fB\-\-conf\-server [:]\fR +\fB\-\-conf\-server [:]\fR Comma\-separated list of controllers, the first being the primary slurmctld. A port can (optionally) be specified for each controller. These hosts are where the slurmd will fetch the configuration from when running in "configless" mode. +\fBNOTE\fR: If specifying an IPv6 address, wrap the in [] to +distinguish the address from the port. This is required even if no port is +specified. .IP .TP diff --git a/slurm.spec b/slurm.spec index aab4cdd9c1e..37b02792e27 100644 --- a/slurm.spec +++ b/slurm.spec @@ -1,7 +1,7 @@ Name: slurm -Version: 24.05.4 +Version: 24.05.5 %define rel 1 -Release: %{rel}%{?dist} +Release: %{rel}.%{gittag}%{?dist}%{?gpu}.ug Summary: Slurm Workload Manager Group: System Environment/Base @@ -15,7 +15,10 @@ URL: https://slurm.schedmd.com/ %global slurm_source_dir %{name}-%{version}-%{rel} %endif -Source: %{slurm_source_dir}.tar.bz2 +# We do not need this, but then duplicate file warning will pop up +# %define _build_id_links none + +Source: %{slurm_source_dir}.tar.gz # build options .rpmmacros options change to default action # ==================== ==================== ======================== @@ -301,7 +304,9 @@ database changes to slurmctld daemons on each cluster Summary: Slurm\'s implementation of the pmi libraries Group: System Environment/Base Requires: %{name}%{?_isa} = %{version}-%{release} +%if ! %{with pmix} Conflicts: pmix-libpmi +%endif %description libpmi Slurm\'s version of libpmi. For systems using Slurm, this version is preferred over the compatibility libraries shipped by the PMIx project. @@ -362,7 +367,13 @@ BuildRequires: http-parser-devel %if %{defined suse_version} BuildRequires: libjson-c-devel %else +%if 0%{?fedora} || 0%{?rhel} > 7 BuildRequires: json-c-devel +Requires: json-c +%else +BuildRequires: json-c12-devel +Requires: json-c12 +%endif %endif %description slurmrestd Provides a REST interface to Slurm. @@ -407,7 +418,7 @@ export QA_RPATHS=0x5 # Strip out some dependencies cat > find-requires.sh <<'EOF' -exec %{__find_requires} "$@" | egrep -v '^libpmix.so|libevent|libnvidia-ml' +exec %{__find_requires} "$@" | egrep -v '^libpmix.so|libevent' EOF chmod +x find-requires.sh %global _use_internal_dependency_generator 0 @@ -594,16 +605,16 @@ rm -rf %{buildroot} %files torque %defattr(-,root,root) -%{_bindir}/pbsnodes -%{_bindir}/qalter -%{_bindir}/qdel -%{_bindir}/qhold -%{_bindir}/qrerun -%{_bindir}/qrls -%{_bindir}/qstat -%{_bindir}/qsub -%{_bindir}/mpiexec -%{_bindir}/generate_pbs_nodefile +#%{_bindir}/pbsnodes +#%{_bindir}/qalter +#%{_bindir}/qdel +#%{_bindir}/qhold +#%{_bindir}/qrerun +#%{_bindir}/qrls +#%{_bindir}/qstat +#%{_bindir}/qsub +#%{_bindir}/mpiexec +#%{_bindir}/generate_pbs_nodefile %{_libdir}/slurm/job_submit_pbs.so %{_libdir}/slurm/spank_pbs.so ############################################################################# diff --git a/src/api/job_step_info.c b/src/api/job_step_info.c index 373f389145c..d1dd90a501d 100644 --- a/src/api/job_step_info.c +++ b/src/api/job_step_info.c @@ -315,8 +315,22 @@ static int _get_stepmgr_steps(void *x, void *arg) slurm_msg_t req_msg; slurm_msg_t_init(&req_msg); slurm_msg_set_r_uid(&req_msg, slurm_conf.slurmd_user_id); - slurm_conf_get_addr(sji->stepmgr, &req_msg.address, - req_msg.flags); + + if (slurm_conf_get_addr(sji->stepmgr, &req_msg.address, req_msg.flags)) + { + /* + * The node isn't in the conf, see if the + * controller has an address for it. + */ + slurm_node_alias_addrs_t *alias_addrs = NULL; + if (!slurm_get_node_alias_addrs(sji->stepmgr, &alias_addrs)) { + add_remote_nodes_to_conf_tbls(alias_addrs->node_list, + alias_addrs->node_addrs); + slurm_free_node_alias_addrs(alias_addrs); + slurm_conf_get_addr(sji->stepmgr, &req_msg.address, + req_msg.flags); + } + } job_step_info_request_msg_t req_data = {0}; req_data.step_id.job_id = sji->job_id; @@ -738,7 +752,8 @@ extern int slurm_job_step_stat(slurm_step_id_t *step_id, memcpy(&req, step_id, sizeof(req)); memcpy(&resp_out->step_id, step_id, sizeof(resp_out->step_id)); - req_msg.protocol_version = use_protocol_ver; + req_msg.protocol_version = MIN(SLURM_PROTOCOL_VERSION, + use_protocol_ver); req_msg.msg_type = REQUEST_JOB_STEP_STAT; req_msg.data = &req; diff --git a/src/common/fetch_config.c b/src/common/fetch_config.c index fceccde29a8..0428f3e51bb 100644 --- a/src/common/fetch_config.c +++ b/src/common/fetch_config.c @@ -47,6 +47,7 @@ #include "src/common/slurm_protocol_pack.h" #include "src/common/slurm_resolv.h" #include "src/common/strlcpy.h" +#include "src/common/util-net.h" #include "src/common/xstring.h" #include "src/common/xmalloc.h" @@ -63,7 +64,8 @@ static char *client_config_files[] = { }; -static void _init_minimal_conf_server_config(List controllers); +static void _init_minimal_conf_server_config(List controllers, bool use_v6, + bool reinit); static int to_parent[2] = {-1, -1}; @@ -114,6 +116,7 @@ static config_response_msg_t *_fetch_parent(pid_t pid) static void _fetch_child(List controllers, uint32_t flags) { config_response_msg_t *config; + ctl_entry_t *ctl = NULL; buf_t *buffer = init_buf(1024 * 1024); int len = 0; @@ -128,9 +131,22 @@ static void _fetch_child(List controllers, uint32_t flags) */ slurm_conf_unlock(); - _init_minimal_conf_server_config(controllers); + ctl = list_peek(controllers); + + if (ctl->has_ipv6 && !ctl->has_ipv4) + _init_minimal_conf_server_config(controllers, true, false); + else + _init_minimal_conf_server_config(controllers, false, false); + config = fetch_config_from_controller(flags); + if (!config && ctl->has_ipv6 && ctl->has_ipv4) { + warning("%s: failed to fetch remote configs via IPv4, retrying with IPv6: %m", + __func__); + _init_minimal_conf_server_config(controllers, true, true); + config = fetch_config_from_controller(flags); + } + if (!config) { error("%s: failed to fetch remote configs: %m", __func__); safe_write(to_parent[1], &len, sizeof(int)); @@ -150,6 +166,16 @@ static void _fetch_child(List controllers, uint32_t flags) _exit(1); } +static int _get_controller_addr_type(void *x, void *arg) +{ + ctl_entry_t *ctl = (ctl_entry_t *) x; + + host_has_addr_family(ctl->hostname, NULL, &ctl->has_ipv4, + &ctl->has_ipv6); + + return SLURM_SUCCESS; +} + extern config_response_msg_t *fetch_config(char *conf_server, uint32_t flags) { char *env_conf_server = getenv("SLURM_CONF_SERVER"); @@ -178,9 +204,21 @@ extern config_response_msg_t *fetch_config(char *conf_server, uint32_t flags) server = strtok_r(tmp, ",", &save_ptr); while (server) { ctl_entry_t *ctl = xmalloc(sizeof(*ctl)); + char *tmp_ptr = NULL; + + if (server[0] == '[') + server++; + strlcpy(ctl->hostname, server, sizeof(ctl->hostname)); - if ((port = xstrchr(ctl->hostname, ':'))) { + if ((tmp_ptr = strchr(ctl->hostname, ']'))) { + *tmp_ptr = '\0'; + tmp_ptr++; + } else { + tmp_ptr = ctl->hostname; + } + + if ((port = xstrchr(tmp_ptr, ':'))) { *port = '\0'; port++; ctl->port = atoi(port); @@ -198,6 +236,8 @@ extern config_response_msg_t *fetch_config(char *conf_server, uint32_t flags) } } + list_for_each(controllers, _get_controller_addr_type, NULL); + /* If the slurm.key file exists, assume we're using auth/slurm */ sack_jwks = get_extra_conf_path("slurm.jwks"); sack_key = get_extra_conf_path("slurm.key"); @@ -331,7 +371,8 @@ static int _print_controllers(void *x, void *arg) return SLURM_SUCCESS; } -static void _init_minimal_conf_server_config(List controllers) +static void _init_minimal_conf_server_config(List controllers, bool use_v6, + bool reinit) { char *conf = NULL, *filename = NULL; int fd; @@ -343,11 +384,17 @@ static void _init_minimal_conf_server_config(List controllers) if (slurm_conf.authinfo) xstrfmtcat(conf, "AuthInfo=%s\n", slurm_conf.authinfo); + if (use_v6) + xstrcat(conf, "CommunicationParameters=EnableIPv6"); + if ((fd = dump_to_memfd("slurm.conf", conf, &filename)) < 0) fatal("%s: could not write temporary config", __func__); xfree(conf); - slurm_init(filename); + if (reinit) + slurm_conf_reinit(filename); + else + slurm_init(filename); close(fd); xfree(filename); diff --git a/src/common/port_mgr.c b/src/common/port_mgr.c index c78abc3956b..dc89241a7c9 100644 --- a/src/common/port_mgr.c +++ b/src/common/port_mgr.c @@ -342,6 +342,9 @@ static int _resv_port_alloc(uint16_t resv_port_cnt, static int last_port_alloc = 0; static int dims = -1; + xassert(!*resv_ports); + xassert(!*resv_port_array); + if (dims == -1) dims = slurmdb_setup_cluster_dims(); @@ -389,6 +392,28 @@ extern int resv_port_step_alloc(step_record_t *step_ptr) int rc; int port_inx; + if (step_ptr->resv_port_array || step_ptr->resv_ports) { + /* + * Both resv_ports and resv_port_array need to be NULL. + * If they are not that could lead to resv_ports never being + * freed on nodes, eventually making those nodes unable to + * schedule jobs since their ports could have been allocated + * without being freed. By setting resv_ports and + * resv_port_array to NULL in job_array_split() guarantees that, + * but try to catch this issue if it happens in future. + */ + error("%pS allocated reserved ports while it already had reserved ports %s", + step_ptr, step_ptr->resv_ports); + + /* + * We can't just call _resv_port_free() because it is not + * guaranteed that the node_bitmap or resv_port_cnt is the same + * from when resv_port_array was allocated. + */ + xfree(step_ptr->resv_port_array); + xfree(step_ptr->resv_ports); + } + rc = _resv_port_alloc(step_ptr->resv_port_cnt, step_ptr->step_node_bitmap, &step_ptr->resv_ports, &step_ptr->resv_port_array, &port_inx); @@ -408,6 +433,29 @@ extern int resv_port_job_alloc(job_record_t *job_ptr) int rc; int port_inx; + if (job_ptr->resv_port_array || job_ptr->resv_ports) { + /* + * Both resv_ports and resv_port_array need to be NULL. + * If they are not that could lead to resv_ports never being + * freed on nodes, eventually making those nodes unable to + * schedule jobs since their ports could have been allocated + * without being freed. By setting resv_ports and + * resv_port_array to NULL in job_array_split() guarantees that, + * but try to catch this issue if it happens in future. + */ + error("%pJ allocated reserved ports while it already had reserved ports %s. Ports may be lost, which will require a restart of the slurmctld daemon to resolve.", + job_ptr, job_ptr->resv_ports); + + /* + * We can't just call _resv_port_free() because it is not + * guaranteed that the node_bitmap or resv_port_cnt is the same + * from when resv_port_array was allocated. A restart of the + * controller will restore any lost ports. + */ + xfree(job_ptr->resv_port_array); + xfree(job_ptr->resv_ports); + } + rc = _resv_port_alloc(job_ptr->resv_port_cnt, job_ptr->node_bitmap, &job_ptr->resv_ports, &job_ptr->resv_port_array, &port_inx); diff --git a/src/common/slurm_protocol_defs.h b/src/common/slurm_protocol_defs.h index 53f5328a461..ecf4b7f5786 100644 --- a/src/common/slurm_protocol_defs.h +++ b/src/common/slurm_protocol_defs.h @@ -798,6 +798,8 @@ typedef struct kill_job_msg { slurm_step_id_t step_id; time_t time; /* slurmctld's time of request */ char *work_dir; + uint32_t nnodes; /* Number of nodes allocated to the job */ + uint16_t *job_node_cpus; /* Number of CPUs required on the nodes for the job */ } kill_job_msg_t; typedef struct reattach_tasks_request_msg { @@ -831,6 +833,7 @@ typedef struct prolog_launch_msg { char **spank_job_env; /* SPANK job environment variables */ uint32_t spank_job_env_size; /* size of spank_job_env */ uint32_t uid; + uint16_t *job_node_cpus; /* Number of CPUs required on the nodes for the job */ char *user_name_deprecated; /* remove two versions after 23.11 */ char *work_dir; /* full pathname of working directory */ uint16_t x11; /* X11 forwarding setup flags */ diff --git a/src/common/slurm_protocol_pack.c b/src/common/slurm_protocol_pack.c index 9146c6f29d6..7e02604ea29 100644 --- a/src/common/slurm_protocol_pack.c +++ b/src/common/slurm_protocol_pack.c @@ -2531,6 +2531,8 @@ _pack_kill_job_msg(kill_job_msg_t * msg, buf_t *buffer, uint16_t protocol_versio packstr(msg->nodes, buffer); packstr_array(msg->spank_job_env, msg->spank_job_env_size, buffer); + pack32(msg->nnodes, buffer); + pack16_array(msg->job_node_cpus, msg->nnodes, buffer); pack_time(msg->start_time, buffer); pack_time(msg->time, buffer); packstr(msg->work_dir, buffer); @@ -2573,6 +2575,10 @@ _unpack_kill_job_msg(kill_job_msg_t ** msg, buf_t *buffer, safe_unpackstr(&tmp_ptr->nodes, buffer); safe_unpackstr_array(&tmp_ptr->spank_job_env, &tmp_ptr->spank_job_env_size, buffer); + safe_unpack32(&tmp_ptr->nnodes, buffer); + safe_unpack16_array(&tmp_ptr->job_node_cpus, + &tmp_ptr->nnodes, + buffer); safe_unpack_time(&tmp_ptr->start_time, buffer); safe_unpack_time(&tmp_ptr->time, buffer); safe_unpackstr(&tmp_ptr->work_dir, buffer); @@ -8245,6 +8251,8 @@ static void _pack_prolog_launch_msg(const slurm_msg_t *smsg, buf_t *buffer) packstr_array(msg->spank_job_env, msg->spank_job_env_size, buffer); + xassert(msg->nnodes > 0); + pack16_array(msg->job_node_cpus, msg->nnodes, buffer); slurm_cred_pack(msg->cred, buffer, smsg->protocol_version); if (msg->job_ptr_buf) { @@ -8278,6 +8286,8 @@ static void _pack_prolog_launch_msg(const slurm_msg_t *smsg, buf_t *buffer) packstr_array(msg->spank_job_env, msg->spank_job_env_size, buffer); + xassert(msg->nnodes > 0); + pack16_array(msg->job_node_cpus, msg->nnodes, buffer); slurm_cred_pack(msg->cred, buffer, smsg->protocol_version); } else if (smsg->protocol_version >= SLURM_MIN_PROTOCOL_VERSION) { gres_prep_pack(msg->job_gres_prep, buffer, @@ -8302,6 +8312,8 @@ static void _pack_prolog_launch_msg(const slurm_msg_t *smsg, buf_t *buffer) packstr_array(msg->spank_job_env, msg->spank_job_env_size, buffer); + xassert(msg->nnodes > 0); + pack16_array(msg->job_node_cpus, msg->nnodes, buffer); slurm_cred_pack(msg->cred, buffer, smsg->protocol_version); packstr(msg->user_name_deprecated, buffer); } @@ -8336,6 +8348,9 @@ static int _unpack_prolog_launch_msg(slurm_msg_t *smsg, buf_t *buffer) safe_unpackstr_array(&msg->spank_job_env, &msg->spank_job_env_size, buffer); + safe_unpack16_array(&msg->job_node_cpus, + &msg->nnodes, + buffer); if (!(msg->cred = slurm_cred_unpack(buffer, smsg->protocol_version))) goto unpack_error; @@ -8377,6 +8392,9 @@ static int _unpack_prolog_launch_msg(slurm_msg_t *smsg, buf_t *buffer) safe_unpackstr_array(&msg->spank_job_env, &msg->spank_job_env_size, buffer); + safe_unpack16_array(&msg->job_node_cpus, + &msg->nnodes, + buffer); if (!(msg->cred = slurm_cred_unpack(buffer, smsg->protocol_version))) goto unpack_error; @@ -8405,6 +8423,9 @@ static int _unpack_prolog_launch_msg(slurm_msg_t *smsg, buf_t *buffer) safe_unpackstr_array(&msg->spank_job_env, &msg->spank_job_env_size, buffer); + safe_unpack16_array(&msg->job_node_cpus, + &msg->nnodes, + buffer); if (!(msg->cred = slurm_cred_unpack(buffer, smsg->protocol_version))) goto unpack_error; diff --git a/src/common/slurm_resolv.h b/src/common/slurm_resolv.h index e0fe650daf6..63de8c74ba7 100644 --- a/src/common/slurm_resolv.h +++ b/src/common/slurm_resolv.h @@ -40,6 +40,8 @@ typedef struct { uint16_t priority; uint16_t port; char hostname[1024]; + bool has_ipv4; + bool has_ipv6; } ctl_entry_t; /* diff --git a/src/common/util-net.c b/src/common/util-net.c index a0fb281e1cb..5720e4b0d1f 100644 --- a/src/common/util-net.c +++ b/src/common/util-net.c @@ -253,6 +253,26 @@ extern char *make_full_path(const char *rpath) return cwd2; } +static struct addrinfo *_xgetaddrinfo(const char *hostname, const char *serv, + const struct addrinfo *hints) +{ + struct addrinfo *result = NULL; + int err; + + err = getaddrinfo(hostname, serv, hints, &result); + if (err == EAI_SYSTEM) { + error_in_daemon("%s: getaddrinfo(%s:%s) failed: %s: %m", + __func__, hostname, serv, gai_strerror(err)); + return NULL; + } else if (err != 0) { + error_in_daemon("%s: getaddrinfo(%s:%s) failed: %s", + __func__, hostname, serv, gai_strerror(err)); + return NULL; + } + + return result; +} + extern struct addrinfo *xgetaddrinfo_port(const char *hostname, uint16_t port) { char serv[6]; @@ -262,9 +282,7 @@ extern struct addrinfo *xgetaddrinfo_port(const char *hostname, uint16_t port) extern struct addrinfo *xgetaddrinfo(const char *hostname, const char *serv) { - struct addrinfo *result = NULL; struct addrinfo hints; - int err; bool v4_enabled = slurm_conf.conf_flags & CONF_FLAG_IPV4_ENABLED; bool v6_enabled = slurm_conf.conf_flags & CONF_FLAG_IPV6_ENABLED; @@ -300,18 +318,39 @@ extern struct addrinfo *xgetaddrinfo(const char *hostname, const char *serv) hints.ai_flags |= AI_CANONNAME; hints.ai_socktype = SOCK_STREAM; - err = getaddrinfo(hostname, serv, &hints, &result); - if (err == EAI_SYSTEM) { - error_in_daemon("%s: getaddrinfo(%s:%s) failed: %s: %m", - __func__, hostname, serv, gai_strerror(err)); - return NULL; - } else if (err != 0) { - error_in_daemon("%s: getaddrinfo(%s:%s) failed: %s", - __func__, hostname, serv, gai_strerror(err)); - return NULL; + return _xgetaddrinfo(hostname, serv, &hints); +} + +extern int host_has_addr_family(const char *hostname, const char *srv, + bool *ipv4, bool *ipv6) +{ + struct addrinfo hints; + struct addrinfo *ai_ptr, *ai_start; + + memset(&hints, 0, sizeof(hints)); + + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV | AI_PASSIVE; + if (hostname) + hints.ai_flags |= AI_CANONNAME; + hints.ai_socktype = SOCK_STREAM; + + ai_start = _xgetaddrinfo(hostname, srv, &hints); + + if (!ai_start) + return SLURM_ERROR; + + *ipv4 = *ipv6 = false; + for (ai_ptr = ai_start; ai_ptr; ai_ptr = ai_ptr->ai_next) { + if (ai_ptr->ai_family == AF_INET6) + *ipv6 = true; + else if (ai_ptr->ai_family == AF_INET) + *ipv4 = true; } - return result; + freeaddrinfo(ai_start); + + return SLURM_SUCCESS; } static int _name_cache_find(void *x, void *y) diff --git a/src/common/util-net.h b/src/common/util-net.h index 7d8c7f9ccfb..c85b9b562d1 100644 --- a/src/common/util-net.h +++ b/src/common/util-net.h @@ -87,6 +87,8 @@ extern struct addrinfo *xgetaddrinfo_port(const char *hostname, uint16_t port); extern char *xgetnameinfo(struct sockaddr *addr, socklen_t addrlen); +extern int host_has_addr_family(const char *hostname, const char *srv, + bool *ipv4, bool *ipv6); /* Functions responsible for cleanup of getnameinfo cache */ extern void getnameinfo_cache_destroy(void *obj); extern void getnameinfo_cache_purge(void); diff --git a/src/plugins/data_parser/v0.0.40/parsers.c b/src/plugins/data_parser/v0.0.40/parsers.c index 54f1ef33636..fe0437de58b 100644 --- a/src/plugins/data_parser/v0.0.40/parsers.c +++ b/src/plugins/data_parser/v0.0.40/parsers.c @@ -8230,7 +8230,7 @@ static const parser_t PARSER_ARRAY(OPENAPI_WARNING)[] = { static const parser_t PARSER_ARRAY(INSTANCE_CONDITION)[] = { add_parse(CSV_STRING_LIST, cluster_list, "cluster", "CSV clusters list"), add_parse(CSV_STRING_LIST, extra_list, "extra", "CSV extra list"), - add_parse(CSV_STRING_LIST, format_list, "format", "CSV format list"), + add_parse(CSV_STRING_LIST, format_list, "format", "Ignored; process JSON manually to control output format"), add_parse(CSV_STRING_LIST, instance_id_list, "instance_id", "CSV instance_id list"), add_parse(CSV_STRING_LIST, instance_type_list, "instance_type", "CSV instance_type list"), add_parse(STRING, node_list, "node_list", "Ranged node string"), @@ -8305,7 +8305,7 @@ static const parser_t PARSER_ARRAY(JOB_CONDITION)[] = { add_flags(JOB_CONDITION_DB_FLAGS, db_flags), add_parse(INT32, exitcode, "exit_code", "Job exit code (numeric)"), add_flags(JOB_CONDITION_FLAGS, flags), - add_parse(CSV_STRING_LIST, format_list, "format", "CSV format list"), + add_parse(CSV_STRING_LIST, format_list, "format", "Ignored; process JSON manually to control output format"), add_parse(GROUP_ID_STRING_LIST, groupid_list, "groups", "CSV group list"), add_parse(CSV_STRING_LIST, jobname_list, "job_name", "CSV job name list"), add_parse(UINT32_NO_VAL, nodes_max, "nodes_max", "Maximum number of nodes"), @@ -8335,7 +8335,7 @@ static const parser_t PARSER_ARRAY(JOB_CONDITION)[] = { static const parser_t PARSER_ARRAY(QOS_CONDITION)[] = { add_parse(CSV_STRING_LIST, description_list, "description", "CSV description list"), add_parse(QOS_ID_STRING_CSV_LIST, id_list, "id", "CSV QOS id list"), - add_parse(CSV_STRING_LIST, format_list, "format", "CSV format list"), + add_parse(CSV_STRING_LIST, format_list, "format", "Ignored; process JSON manually to control output format"), add_parse(QOS_NAME_CSV_LIST, name_list, "name", "CSV QOS name list"), add_parse_bit_flag_array(slurmdb_qos_cond_t, QOS_PREEMPT_MODES, false, preempt_mode, "preempt_mode", "PreemptMode used when jobs in this QOS are preempted"), add_parse(BOOL16, with_deleted, "with_deleted", "Include deleted QOS"), @@ -8394,7 +8394,7 @@ static const parser_t PARSER_ARRAY(ASSOC_CONDITION)[] = { add_parse(CSV_STRING_LIST, acct_list, "account", "CSV accounts list"), add_parse(CSV_STRING_LIST, cluster_list, "cluster", "CSV clusters list"), add_parse(QOS_ID_STRING_CSV_LIST, def_qos_id_list, "default_qos", "CSV QOS list"), - add_parse(CSV_STRING_LIST, format_list, "format", "CSV format list"), + add_parse(CSV_STRING_LIST, format_list, "format", "Ignored; process JSON manually to control output format"), add_parse(ASSOC_ID_STRING_CSV_LIST, id_list, "id", "CSV id list"), add_parse(BOOL16, only_defs, "only_defaults", "Filter to only defaults"), add_parse(CSV_STRING_LIST, parent_acct_list, "parent_account", "CSV names of parent account"), @@ -8462,7 +8462,7 @@ static const parser_t PARSER_ARRAY(OPENAPI_WCKEY_PARAM)[] = { add_parser(slurmdb_wckey_cond_t, mtype, false, field, 0, path, desc) static const parser_t PARSER_ARRAY(WCKEY_CONDITION)[] = { add_parse(CSV_STRING_LIST, cluster_list, "cluster", "CSV cluster name list"), - add_parse(CSV_STRING_LIST, format_list, "format", "CSV format name list"), + add_parse(CSV_STRING_LIST, format_list, "format", "Ignored; process JSON manually to control output format"), add_parse(CSV_STRING_LIST, id_list, "id", "CSV id list"), add_parse(CSV_STRING_LIST, name_list, "name", "CSV name list"), add_parse(BOOL16, only_defs, "only_defaults", "Only query defaults"), @@ -8525,7 +8525,7 @@ static const parser_t PARSER_ARRAY(CLUSTER_CONDITION)[] = { add_parse(STRING_LIST, cluster_list, "cluster", "CSV cluster list"), add_parse(STRING_LIST, federation_list, "federation", "CSV federation list"), add_parse_bit_flag_array(slurmdb_cluster_cond_t, CLUSTER_REC_FLAGS, false, flags, "flags", "Query flags"), - add_parse(STRING_LIST, format_list, "format", "CSV format list"), + add_parse(STRING_LIST, format_list, "format", "Ignored; process JSON manually to control output format"), add_parse(STRING_LIST, rpc_version_list, "rpc_version", "CSV RPC version list"), add_parse(TIMESTAMP, usage_end, "usage_end", "Usage end (UNIX timestamp)"), add_parse(TIMESTAMP, usage_start, "usage_start", "Usage start (UNIX timestamp)"), diff --git a/src/plugins/data_parser/v0.0.41/api.c b/src/plugins/data_parser/v0.0.41/api.c index 5b213dcd49f..7e665ecb43a 100644 --- a/src/plugins/data_parser/v0.0.41/api.c +++ b/src/plugins/data_parser/v0.0.41/api.c @@ -95,7 +95,7 @@ extern int data_parser_p_dump(args_t *args, data_parser_type_t type, void *src, return ESLURM_NOT_SUPPORTED; } - return dump(src, src_bytes, parser, dst, args); + return dump(src, src_bytes, NULL, parser, dst, args); } extern int data_parser_p_parse(args_t *args, data_parser_type_t type, void *dst, diff --git a/src/plugins/data_parser/v0.0.41/parsers.c b/src/plugins/data_parser/v0.0.41/parsers.c index 9d37e8d7dbe..ee8edb9ba21 100644 --- a/src/plugins/data_parser/v0.0.41/parsers.c +++ b/src/plugins/data_parser/v0.0.41/parsers.c @@ -8423,7 +8423,7 @@ static const parser_t PARSER_ARRAY(OPENAPI_WARNING)[] = { static const parser_t PARSER_ARRAY(INSTANCE_CONDITION)[] = { add_parse(CSV_STRING_LIST, cluster_list, "cluster", "CSV clusters list"), add_parse(CSV_STRING_LIST, extra_list, "extra", "CSV extra list"), - add_parse(CSV_STRING_LIST, format_list, "format", "CSV format list"), + add_parse(CSV_STRING_LIST, format_list, "format", "Ignored; process JSON manually to control output format"), add_parse(CSV_STRING_LIST, instance_id_list, "instance_id", "CSV instance_id list"), add_parse(CSV_STRING_LIST, instance_type_list, "instance_type", "CSV instance_type list"), add_parse(STRING, node_list, "node_list", "Ranged node string"), @@ -8501,7 +8501,7 @@ static const parser_t PARSER_ARRAY(JOB_CONDITION)[] = { add_flags(JOB_CONDITION_DB_FLAGS, db_flags), add_parse(INT32, exitcode, "exit_code", "Job exit code (numeric)"), add_flags(JOB_CONDITION_FLAGS, flags), - add_parse(CSV_STRING_LIST, format_list, "format", "CSV format list"), + add_parse(CSV_STRING_LIST, format_list, "format", "Ignored; process JSON manually to control output format"), add_parse(GROUP_ID_STRING_LIST, groupid_list, "groups", "CSV group list"), add_parse(CSV_STRING_LIST, jobname_list, "job_name", "CSV job name list"), add_parse(UINT32_NO_VAL, nodes_max, "nodes_max", "Maximum number of nodes"), @@ -8531,7 +8531,7 @@ static const parser_t PARSER_ARRAY(JOB_CONDITION)[] = { static const parser_t PARSER_ARRAY(QOS_CONDITION)[] = { add_parse(CSV_STRING_LIST, description_list, "description", "CSV description list"), add_parse(QOS_ID_STRING_CSV_LIST, id_list, "id", "CSV QOS id list"), - add_parse(CSV_STRING_LIST, format_list, "format", "CSV format list"), + add_parse(CSV_STRING_LIST, format_list, "format", "Ignored; process JSON manually to control output format"), add_parse(QOS_NAME_CSV_LIST, name_list, "name", "CSV QOS name list"), add_parse_bit_flag_array(slurmdb_qos_cond_t, QOS_PREEMPT_MODES, false, preempt_mode, "preempt_mode", "PreemptMode used when jobs in this QOS are preempted"), add_parse(BOOL16, with_deleted, "with_deleted", "Include deleted QOS"), @@ -8590,7 +8590,7 @@ static const parser_t PARSER_ARRAY(ASSOC_CONDITION)[] = { add_parse(CSV_STRING_LIST, acct_list, "account", "CSV accounts list"), add_parse(CSV_STRING_LIST, cluster_list, "cluster", "CSV clusters list"), add_parse(QOS_ID_STRING_CSV_LIST, def_qos_id_list, "default_qos", "CSV QOS list"), - add_parse(CSV_STRING_LIST, format_list, "format", "CSV format list"), + add_parse(CSV_STRING_LIST, format_list, "format", "Ignored; process JSON manually to control output format"), add_parse(ASSOC_ID_STRING_CSV_LIST, id_list, "id", "CSV id list"), add_parse(BOOL16, only_defs, "only_defaults", "Filter to only defaults"), add_parse(CSV_STRING_LIST, parent_acct_list, "parent_account", "CSV names of parent account"), @@ -8658,7 +8658,7 @@ static const parser_t PARSER_ARRAY(OPENAPI_WCKEY_PARAM)[] = { add_parser(slurmdb_wckey_cond_t, mtype, false, field, 0, path, desc) static const parser_t PARSER_ARRAY(WCKEY_CONDITION)[] = { add_parse(CSV_STRING_LIST, cluster_list, "cluster", "CSV cluster name list"), - add_parse(CSV_STRING_LIST, format_list, "format", "CSV format name list"), + add_parse(CSV_STRING_LIST, format_list, "format", "Ignored; process JSON manually to control output format"), add_parse(CSV_STRING_LIST, id_list, "id", "CSV id list"), add_parse(CSV_STRING_LIST, name_list, "name", "CSV name list"), add_parse(BOOL16, only_defs, "only_defaults", "Only query defaults"), @@ -8716,7 +8716,7 @@ static const parser_t PARSER_ARRAY(CLUSTER_CONDITION)[] = { add_parse(STRING_LIST, cluster_list, "cluster", "CSV cluster list"), add_parse(STRING_LIST, federation_list, "federation", "CSV federation list"), add_parse_bit_flag_array(slurmdb_cluster_cond_t, CLUSTER_REC_FLAGS, false, flags, "flags", "Query flags"), - add_parse(STRING_LIST, format_list, "format", "CSV format list"), + add_parse(STRING_LIST, format_list, "format", "Ignored; process JSON manually to control output format"), add_parse(STRING_LIST, rpc_version_list, "rpc_version", "CSV RPC version list"), add_parse(TIMESTAMP, usage_end, "usage_end", "Usage end (UNIX timestamp)"), add_parse(TIMESTAMP, usage_start, "usage_start", "Usage start (UNIX timestamp)"), diff --git a/src/plugins/data_parser/v0.0.41/parsing.c b/src/plugins/data_parser/v0.0.41/parsing.c index c6db4704e18..ff5bae5b924 100644 --- a/src/plugins/data_parser/v0.0.41/parsing.c +++ b/src/plugins/data_parser/v0.0.41/parsing.c @@ -1244,8 +1244,9 @@ static int _foreach_dump_list(void *obj, void *arg) xassert(args->parser->ptr_offset == NO_VAL); /* we don't know the size of the items in the list */ - if (dump(&obj, NO_VAL, find_parser_by_type(args->parser->list_type), - item, args->args)) + if (dump(&obj, NO_VAL, NULL, + find_parser_by_type(args->parser->list_type), item, + args->args)) return -1; return 0; @@ -1286,7 +1287,8 @@ static int _dump_list(const parser_t *const parser, void *src, data_t *dst, return SLURM_SUCCESS; } -static int _dump_pointer(const parser_t *const parser, void *src, data_t *dst, +static int _dump_pointer(const parser_t *const field_parser, + const parser_t *const parser, void *src, data_t *dst, args_t *args) { const parser_t *pt = find_parser_by_type(parser->pointer_type); @@ -1302,10 +1304,11 @@ static int _dump_pointer(const parser_t *const parser, void *src, data_t *dst, while (pt->pointer_type) pt = find_parser_by_type(pt->pointer_type); - if (parser->allow_null_pointer) { + if (parser->allow_null_pointer || + (field_parser && !field_parser->required)) { xassert(data_get_type(dst) == DATA_TYPE_NULL); } else if ((pt->model == PARSER_MODEL_ARRAY) || - (pt->obj_openapi == OPENAPI_FORMAT_OBJECT)) { + (pt->obj_openapi == OPENAPI_FORMAT_OBJECT)) { /* * OpenAPI clients can't handle a null instead of an * object. Work around by placing an empty dictionary @@ -1327,7 +1330,7 @@ static int _dump_pointer(const parser_t *const parser, void *src, data_t *dst, return SLURM_SUCCESS; } - return dump(*ptr, NO_VAL, pt, dst, args); + return dump(*ptr, NO_VAL, NULL, pt, dst, args); } static int _dump_nt_array(const parser_t *const parser, void *src, data_t *dst, @@ -1345,7 +1348,7 @@ static int _dump_nt_array(const parser_t *const parser, void *src, data_t *dst, return SLURM_SUCCESS; for (int i = 0; !rc && array[i]; i++) { - rc = dump(array[i], NO_VAL, + rc = dump(array[i], NO_VAL, NULL, find_parser_by_type(parser->array_type), data_list_append(dst), args); } @@ -1369,7 +1372,7 @@ static int _dump_nt_array(const parser_t *const parser, void *src, data_t *dst, if (done) break; - rc = dump(ptr, NO_VAL, + rc = dump(ptr, NO_VAL, NULL, find_parser_by_type(parser->array_type), data_list_append(dst), args); } @@ -1518,7 +1521,8 @@ static int _dump_linked(args_t *args, const parser_t *const array, array->ptr_offset, (uintptr_t) dst, array->key, (uintptr_t) dst); - rc = dump(src, NO_VAL, find_parser_by_type(parser->type), dst, args); + rc = dump(src, NO_VAL, parser, find_parser_by_type(parser->type), dst, + args); log_flag(DATA, "END: dumping %s parser %s->%s(0x%" PRIxPTR ") for %s(0x%" PRIxPTR ")->%s(+%zd) for data(0x%" PRIxPTR ")/%s(0x%" PRIxPTR ")", parser->obj_type_string, array->type_string, @@ -1545,8 +1549,9 @@ static void _check_dump(const parser_t *const parser, data_t *dst, args_t *args) } } -extern int dump(void *src, ssize_t src_bytes, const parser_t *const parser, - data_t *dst, args_t *args) +extern int dump(void *src, ssize_t src_bytes, + const parser_t *const field_parser, + const parser_t *const parser, data_t *dst, args_t *args) { int rc; @@ -1617,7 +1622,7 @@ extern int dump(void *src, ssize_t src_bytes, const parser_t *const parser, verify_parser_not_sliced(parser); xassert(data_get_type(dst) == DATA_TYPE_NULL); - rc = _dump_pointer(parser, src, dst, args); + rc = _dump_pointer(field_parser, parser, src, dst, args); break; case PARSER_MODEL_NT_PTR_ARRAY: case PARSER_MODEL_NT_ARRAY: diff --git a/src/plugins/data_parser/v0.0.41/parsing.h b/src/plugins/data_parser/v0.0.41/parsing.h index 94e325dde1a..f1b34a1554e 100644 --- a/src/plugins/data_parser/v0.0.41/parsing.h +++ b/src/plugins/data_parser/v0.0.41/parsing.h @@ -59,11 +59,12 @@ #undef DATA_DUMP #undef DATA_PARSE -extern int dump(void *src, ssize_t src_bytes, const parser_t *const parser, - data_t *dst, args_t *args); -#define DUMP(type, src, dst, args) \ - dump(&src, sizeof(src), find_parser_by_type(DATA_PARSER_##type), dst, \ - args) +extern int dump(void *src, ssize_t src_bytes, + const parser_t *const field_parser, + const parser_t *const parser, data_t *dst, args_t *args); +#define DUMP(type, src, dst, args) \ + dump(&src, sizeof(src), NULL, find_parser_by_type(DATA_PARSER_##type), \ + dst, args) extern int parse(void *dst, ssize_t dst_bytes, const parser_t *const parser, data_t *src, args_t *args, data_t *parent_path); diff --git a/src/plugins/job_submit/pbs/spank_pbs.c b/src/plugins/job_submit/pbs/spank_pbs.c index 3559c90230e..943d253ad9c 100644 --- a/src/plugins/job_submit/pbs/spank_pbs.c +++ b/src/plugins/job_submit/pbs/spank_pbs.c @@ -65,14 +65,28 @@ int slurm_spank_task_init(spank_t sp, int ac, char **av) spank_setenv(sp, "PBS_ARRAY_ID", val, 1); if (spank_getenv(sp, "SLURM_ARRAY_TASK_ID", val, sizeof(val)) == ESPANK_SUCCESS) - spank_setenv(sp, "PBS_ARRAY_INDEX", val, 1); + spank_setenv(sp, "PBS_ARRAYID", val, 1); if (getcwd(val, sizeof(val))) spank_setenv(sp, "PBS_JOBDIR", val, 1); - if (spank_getenv(sp, "SLURM_JOB_ID", val, sizeof(val)) == - ESPANK_SUCCESS) - spank_setenv(sp, "PBS_JOBID", val, 1); + if (spank_getenv(sp, "SLURM_ARRAY_JOB_ID", val, sizeof(val)) == + ESPANK_SUCCESS) { + /* 20 is enough for unit32 (max index is 4M) */ + char aid[20]; + if (spank_getenv(sp, "SLURM_ARRAY_TASK_ID", aid, sizeof(aid)) == + ESPANK_SUCCESS) { + /* 30k val is large enough for job id and array index */ + strncat(val, "[", 1); + strncat(val, aid, sizeof(aid)); + strncat(val, "]", 1); + } + spank_setenv(sp, "PBS_JOBID", val, 1); + } else { + if (spank_getenv(sp, "SLURM_JOB_ID", val, sizeof(val)) == + ESPANK_SUCCESS) + spank_setenv(sp, "PBS_JOBID", val, 1); + } if (spank_getenv(sp, "SLURM_JOB_NAME", val, sizeof(val)) == ESPANK_SUCCESS) @@ -112,6 +126,15 @@ int slurm_spank_task_init(spank_t sp, int ac, char **av) if (spank_getenv(sp, "SYSTEM", val, sizeof(val)) == ESPANK_SUCCESS) spank_setenv(sp, "PBS_O_SYSTEM", val, 1); + if (spank_getenv(sp, "SLURM_JOB_NUM_NODES", val, sizeof(val)) == ESPANK_SUCCESS) + spank_setenv(sp, "PBS_NUM_NODES", val, 1); + + if (spank_getenv(sp, "SLURM_NTASKS", val, sizeof(val)) == ESPANK_SUCCESS) + spank_setenv(sp, "PBS_NP", val, 1); + + if (spank_getenv(sp, "SLURM_NTASKS_PER_NODE", val, sizeof(val)) == ESPANK_SUCCESS) + spank_setenv(sp, "PBS_NUM_PPN", val, 1); + if (spank_getenv(sp, "SLURM_SUBMIT_DIR", val, sizeof(val)) == ESPANK_SUCCESS) spank_setenv(sp, "PBS_O_WORKDIR", val, 1); diff --git a/src/plugins/prep/script/prep_script_slurmd.c b/src/plugins/prep/script/prep_script_slurmd.c index 21ea69c4c32..559892426d6 100644 --- a/src/plugins/prep/script/prep_script_slurmd.c +++ b/src/plugins/prep/script/prep_script_slurmd.c @@ -272,6 +272,9 @@ static char **_build_env(job_env_t *job_env, slurm_cred_t *cred, else setenvf(&env, "SLURM_SCRIPT_CONTEXT", "prolog_slurmd"); + setenvf(&env, "SLURM_JOB_NODE_CPUS", "%d", job_env->job_node_cpus); + setenvf(&env, "SLURM_ACTUAL_NODE_CPUS", "%d", conf->actual_cpus); + if (is_epilog && (job_env->exit_code != INFINITE)) { int exit_code = 0, signal = 0; if (WIFEXITED(job_env->exit_code)) diff --git a/src/sattach/sattach.c b/src/sattach/sattach.c index 3c8f7b10e5d..921b6c7bcdd 100644 --- a/src/sattach/sattach.c +++ b/src/sattach/sattach.c @@ -408,7 +408,8 @@ static int _attach_to_tasks(slurm_step_id_t stepid, slurm_msg_set_r_uid(&msg, SLURM_AUTH_UID_ANY); msg.msg_type = REQUEST_REATTACH_TASKS; msg.data = &reattach_msg; - msg.protocol_version = layout->start_protocol_ver; + msg.protocol_version = MIN(SLURM_PROTOCOL_VERSION, + layout->start_protocol_ver); if (layout->front_end) hosts = layout->front_end; diff --git a/src/slurmctld/controller.c b/src/slurmctld/controller.c index 42185e4d99a..ec4f4e8272c 100644 --- a/src/slurmctld/controller.c +++ b/src/slurmctld/controller.c @@ -817,6 +817,10 @@ int main(int argc, char **argv) if (slurmctld_config.resume_backup && slurmctld_primary) break; + /* The backup is now meant to relinquish control */ + if (slurmctld_config.resume_backup && !slurmctld_primary) + backup_has_control = false; + recover = 2; } diff --git a/src/slurmctld/job_mgr.c b/src/slurmctld/job_mgr.c index d0c02a94447..e99e2d58f21 100644 --- a/src/slurmctld/job_mgr.c +++ b/src/slurmctld/job_mgr.c @@ -3477,6 +3477,8 @@ extern job_record_t *job_array_split(job_record_t *job_ptr) job_ptr_pend->resv_name = xstrdup(job_ptr->resv_name); if (job_ptr->resv_list) job_ptr_pend->resv_list = list_shallow_copy(job_ptr->resv_list); + job_ptr_pend->resv_ports = NULL; + job_ptr_pend->resv_port_array = NULL; job_ptr_pend->resp_host = xstrdup(job_ptr->resp_host); if (job_ptr->select_jobinfo) { job_ptr_pend->select_jobinfo = @@ -15674,6 +15676,10 @@ void batch_requeue_fini(job_record_t *job_ptr) } } + /* Reset the priority (begin and accrue times were reset) */ + if (job_ptr->priority != 0) + set_job_prio(job_ptr); + /* * If a reservation ended and was a repeated (e.g., daily, weekly) * reservation, its ID will be different; make sure diff --git a/src/slurmctld/node_scheduler.c b/src/slurmctld/node_scheduler.c index 1f1794f1c1f..672b9cf590d 100644 --- a/src/slurmctld/node_scheduler.c +++ b/src/slurmctld/node_scheduler.c @@ -323,6 +323,8 @@ extern void deallocate_nodes(job_record_t *job_ptr, bool timeout, hostlist_t *hostlist = NULL; uint16_t use_protocol_version = 0; uint16_t msg_flags = 0; + job_resources_t *job_resrcs_ptr = NULL; + #ifdef HAVE_FRONT_END front_end_record_t *front_end_ptr; #endif @@ -347,6 +349,7 @@ extern void deallocate_nodes(job_record_t *job_ptr, bool timeout, if (!job_ptr->details->prolog_running) hostlist = hostlist_create(NULL); + #ifdef HAVE_FRONT_END if (job_ptr->batch_host && (front_end_ptr = job_ptr->front_end_ptr)) { @@ -495,9 +498,14 @@ extern void deallocate_nodes(job_record_t *job_ptr, bool timeout, agent_args->msg_flags = msg_flags; last_node_update = time(NULL); - kill_job = create_kill_job_msg(job_ptr, use_protocol_version); + kill_job = create_kill_job_msg(job_ptr, use_protocol_version); kill_job->nodes = xstrdup(job_ptr->nodes); + // XXX: verify + job_resrcs_ptr = job_ptr->job_resrcs; + kill_job->nnodes = job_resrcs_ptr->nhosts; + kill_job->job_node_cpus = job_resrcs_ptr->cpus; + agent_args->msg_args = kill_job; set_agent_arg_r_uid(agent_args, SLURM_AUTH_UID_ANY); agent_queue_request(agent_args); @@ -2538,6 +2546,7 @@ extern int select_nodes(job_record_t *job_ptr, bool test_only, { .assoc = READ_LOCK, .qos = WRITE_LOCK, .tres = READ_LOCK }; List gres_list_pre = NULL; bool gres_list_pre_set = false; + job_record_t *tmp_job; xassert(job_ptr); xassert(job_ptr->magic == JOB_MAGIC); @@ -2942,7 +2951,10 @@ extern int select_nodes(job_record_t *job_ptr, bool test_only, job_end_time_reset(job_ptr); - (void) job_array_post_sched(job_ptr); + tmp_job = job_array_post_sched(job_ptr); + if (tmp_job && (tmp_job != job_ptr) && (orig_resv_port_cnt == NO_VAL16)) + tmp_job->resv_port_cnt = orig_resv_port_cnt; + if (bb_g_job_begin(job_ptr) != SLURM_SUCCESS) { /* Leave job queued, something is hosed */ error_code = ESLURM_INVALID_BURST_BUFFER_REQUEST; @@ -3101,7 +3113,6 @@ extern int select_nodes(job_record_t *job_ptr, bool test_only, } if (error_code != SLURM_SUCCESS) { - FREE_NULL_BITMAP(job_ptr->node_bitmap); if (gres_list_pre_set && (job_ptr->gres_list_req != gres_list_pre)) { FREE_NULL_LIST(job_ptr->gres_list_req); @@ -3114,6 +3125,7 @@ extern int select_nodes(job_record_t *job_ptr, bool test_only, resv_port_job_free(job_ptr); xfree(job_ptr->resv_ports); } + FREE_NULL_BITMAP(job_ptr->node_bitmap); } else FREE_NULL_LIST(gres_list_pre); @@ -3345,6 +3357,10 @@ extern void launch_prolog(job_record_t *job_ptr) xassert(job_ptr->job_resrcs); job_resrcs_ptr = job_ptr->job_resrcs; + + prolog_msg_ptr->nnodes = job_resrcs_ptr->nhosts; + prolog_msg_ptr->job_node_cpus = job_resrcs_ptr->cpus; + setup_cred_arg(&cred_arg, job_ptr); cred_arg.step_id.job_id = job_ptr->job_id; cred_arg.step_id.step_id = SLURM_EXTERN_CONT; diff --git a/src/slurmd/slurmd/cred_context.c b/src/slurmd/slurmd/cred_context.c index 998c24cf838..8df077ffc65 100644 --- a/src/slurmd/slurmd/cred_context.c +++ b/src/slurmd/slurmd/cred_context.c @@ -124,7 +124,14 @@ static job_state_t *_find_job_state(uint32_t jobid) static void _clear_expired_job_states(void) { - time_t now = time(NULL); + time_t now; + + if (!cred_job_list) { + warning("No cred_job_list, unable to clear expired job states"); + return; + } + + now = time(NULL); list_delete_all(cred_job_list, _list_find_expired_job_state, &now); } @@ -140,7 +147,14 @@ static int _list_find_expired_cred_state(void *x, void *key) static void _clear_expired_credential_states(void) { - time_t now = time(NULL); + time_t now; + + if (!cred_state_list) { + warning("No cred_state_list, unable to clear expired credential states"); + return; + } + + now = time(NULL); list_delete_all(cred_state_list, _list_find_expired_cred_state, &now); } diff --git a/src/slurmd/slurmd/req.c b/src/slurmd/slurmd/req.c index a06abed1a30..9b01205998d 100644 --- a/src/slurmd/slurmd/req.c +++ b/src/slurmd/slurmd/req.c @@ -40,8 +40,10 @@ \*****************************************************************************/ #include "config.h" +#include #define _GNU_SOURCE /* for setresuid() */ +#define __USE_XOPEN_EXTENDED #include #include @@ -2467,6 +2469,31 @@ static void _notify_result_rpc_prolog(prolog_launch_msg_t *req, int rc) } } +static int _get_node_inx(char *hostlist) +{ + char *host; + int node_inx = -1; + hostset_t *hset; + + if (!conf->node_name) + return node_inx; + + if ((hset = hostset_create(hostlist))) { + int inx = 0; + while ((host = hostset_shift(hset))) { + if (!strcmp(host, conf->node_name)) { + node_inx = inx; + free(host); + break; + } + inx++; + free(host); + } + hostset_destroy(hset); + } + return node_inx; +} + static void _rpc_prolog(slurm_msg_t *msg) { int rc = SLURM_SUCCESS; @@ -2519,6 +2546,7 @@ static void _rpc_prolog(slurm_msg_t *msg) if (!(slurm_conf.prolog_flags & PROLOG_FLAG_RUN_IN_JOB)) { int node_id = 0; job_env_t job_env; + int node_inx = 0; #ifndef HAVE_FRONT_END /* It is always 0 for front end systems */ @@ -2540,7 +2568,11 @@ static void _rpc_prolog(slurm_msg_t *msg) job_env.uid = req->uid; job_env.gid = req->gid; - rc = run_prolog(&job_env, req->cred); + node_inx = _get_node_inx(req->nodes); + debug("_rpc_prolog: _get_node_inx returned %d", node_inx); + job_env.job_node_cpus = (node_inx >= 0 ? req->job_node_cpus[node_inx] : 0); + + rc = run_prolog(&job_env, req->cred); _free_job_env(&job_env); if (rc) { int term_sig = 0, exit_status = 0; @@ -5538,6 +5570,7 @@ _rpc_terminate_job(slurm_msg_t *msg) if (!(slurm_conf.prolog_flags & PROLOG_FLAG_RUN_IN_JOB)) { int node_id = 0; job_env_t job_env; + int node_inx = -1; #ifndef HAVE_FRONT_END /* It is always 0 for front end systems */ node_id = nodelist_find(req->nodes, conf->node_name); @@ -5557,6 +5590,9 @@ _rpc_terminate_job(slurm_msg_t *msg) job_env.uid = req->job_uid; job_env.gid = req->job_gid; + node_inx = _get_node_inx(req->nodes); + job_env.job_node_cpus = (node_inx >= 0 ? req->job_node_cpus[node_inx] : 0); + debug2("Setting job_env.job_cpu_nodes to %d", job_env.job_node_cpus); _wait_for_job_running_prolog(job_env.jobid); rc = run_epilog(&job_env, req->cred); _free_job_env(&job_env); diff --git a/src/slurmd/slurmd/slurmd.h b/src/slurmd/slurmd/slurmd.h index 93d20ab9a66..bcb06c06a1d 100644 --- a/src/slurmd/slurmd/slurmd.h +++ b/src/slurmd/slurmd/slurmd.h @@ -69,6 +69,7 @@ typedef struct { uid_t uid; gid_t gid; char *work_dir; + uint16_t job_node_cpus; /* Number of CPUs used by the job on this node */ } job_env_t; /* diff --git a/src/slurmd/slurmstepd/req.c b/src/slurmd/slurmstepd/req.c index e7da47c75dd..d558c6aa4fb 100644 --- a/src/slurmd/slurmstepd/req.c +++ b/src/slurmd/slurmstepd/req.c @@ -607,8 +607,6 @@ static int _handle_job_step_get_info(int fd, stepd_step_rec_t *step, uid_t uid) buffer); slurm_send_node_msg(msg.conn_fd, &response_msg); FREE_NULL_BUFFER(buffer); - - slurm_send_rc_msg(&msg, SLURM_SUCCESS); slurm_free_msg_members(&msg); done: @@ -752,9 +750,10 @@ static int _handle_step_layout(int fd, stepd_step_rec_t *step, uid_t uid) step_layout); slurm_send_node_msg(msg.conn_fd, &response_msg); slurm_step_layout_destroy(step_layout); + } else { + slurm_send_rc_msg(&msg, rc); } - slurm_send_rc_msg(&msg, rc); slurm_free_msg_members(&msg); done: @@ -789,6 +788,8 @@ static int _handle_job_sbcast_cred(int fd, stepd_step_rec_t *step, uid_t uid) slurm_send_node_msg(msg.conn_fd, &response_msg); slurm_free_sbcast_cred_msg(job_info_resp_msg); + slurm_free_msg_members(&msg); + return rc; resp: slurm_send_rc_msg(&msg, rc); @@ -839,6 +840,8 @@ static int _handle_het_job_alloc_info(int fd, stepd_step_rec_t *step, uid_t uid) resp_list); slurm_send_node_msg(msg.conn_fd, &response_msg); FREE_NULL_LIST(resp_list); + slurm_free_msg_members(&msg); + return rc; resp: slurm_send_rc_msg(&msg, rc); @@ -2163,31 +2166,39 @@ _handle_completion(int fd, stepd_step_rec_t *step, uid_t uid) goto rwfail; FREE_NULL_BUFFER(buffer); - if (job_step_ptr && do_stepmgr) { - int rem = 0; - uint32_t max_rc; - slurm_step_id_t temp_id = { - .job_id = job_step_ptr->job_id, - .step_het_comp = NO_VAL, - .step_id = step_id - }; - - step_complete_msg_t req = { - .range_first = first, - .range_last = last, - .step_id = temp_id, - .step_rc = step_rc, - .jobacct = jobacct - }; - - step_partial_comp(&req, uid, true, &rem, &max_rc); - - safe_write(fd, &rc, sizeof(int)); - safe_write(fd, &errnum, sizeof(int)); - - jobacctinfo_destroy(jobacct); - - return SLURM_SUCCESS; + if (do_stepmgr) { + slurm_mutex_lock(&stepmgr_mutex); + if (job_step_ptr) { + int rem = 0; + uint32_t max_rc; + slurm_step_id_t temp_id = { + .job_id = job_step_ptr->job_id, + .step_het_comp = NO_VAL, + .step_id = step_id + }; + + step_complete_msg_t req = { + .range_first = first, + .range_last = last, + .step_id = temp_id, + .step_rc = step_rc, + .jobacct = jobacct + }; + + step_partial_comp(&req, uid, true, &rem, &max_rc); + + safe_write(fd, &rc, sizeof(int)); + safe_write(fd, &errnum, sizeof(int)); + + jobacctinfo_destroy(jobacct); + + rc = SLURM_SUCCESS; + } else { + error("Asked to complete a stepmgr step but we don't have a job_step_ptr. This should never happen."); + rc = SLURM_ERROR; + } + slurm_mutex_unlock(&stepmgr_mutex); + return rc; } /* diff --git a/src/slurmd/slurmstepd/slurmstepd.c b/src/slurmd/slurmstepd/slurmstepd.c index 494fb35dc09..09f20b2eb99 100644 --- a/src/slurmd/slurmstepd/slurmstepd.c +++ b/src/slurmd/slurmstepd/slurmstepd.c @@ -881,10 +881,31 @@ _init_from_slurmd(int sock, char **argv, slurm_addr_t **_cli, if (task_msg->job_ptr && !xstrcmp(conf->node_name, task_msg->job_ptr->batch_host)) { + slurm_addr_t *node_addrs; + /* only allow one stepd to be stepmgr. */ job_step_ptr = task_msg->job_ptr; job_step_ptr->part_ptr = task_msg->part_ptr; job_node_array = task_msg->job_node_array; + + /* + * job_record doesn't pack its node_addrs array, so get + * it from the cred. + */ + if (task_msg->cred && + (node_addrs = slurm_cred_get( + task_msg->cred, + CRED_DATA_JOB_NODE_ADDRS))) { + add_remote_nodes_to_conf_tbls( + job_step_ptr->nodes, node_addrs); + + job_step_ptr->node_addrs = + xcalloc(job_step_ptr->node_cnt, + sizeof(slurm_addr_t)); + memcpy(job_step_ptr->node_addrs, node_addrs, + job_step_ptr->node_cnt * + sizeof(slurm_addr_t)); + } } break; diff --git a/testsuite/README b/testsuite/README index cd2f8b0ef8a..fe8aeb6d6cc 100644 --- a/testsuite/README +++ b/testsuite/README @@ -592,7 +592,6 @@ test21.35 Validate DenyOnLimit QoS flag is enforced on QoS and Associations. test21.36 Validate that sacctmgr lost jobs fixes lost jobs. test21.37 sacctmgr show and clear stats test21.38 sacctmgr modify limits for nested accounts with multiple users -test21.39 sacctmgr create qos/account job and then delete account/qos test21.40 Test association plus partition/job QoS unique node limits enforced test21.41 sacctmgr update job set newwckey= test21.42 Test if headers returned by sacctmgr show can be used as format= specifiers @@ -793,6 +792,7 @@ test_102_# Testing of sacctmgr options. test_102_1 /commands/sacctmgr/test_federation.py test_102_2 /commands/sacctmgr/test_--usage.py test_102_3 /commands/sacctmgr/test_--json.py +test_102_5 sacctmgr create qos/account job and then delete account/qos test_103_# Testing of salloc options. ======================================= diff --git a/testsuite/expect/test15.4 b/testsuite/expect/test15.4 index 400e8af1bb9..f0862350f5b 100755 --- a/testsuite/expect/test15.4 +++ b/testsuite/expect/test15.4 @@ -53,7 +53,6 @@ set login_grp_info [get_my_id] # # Submit a slurm job that will execute 'id' # -set timeout $max_job_delay spawn $salloc -N1 -t1 $srun $bin_id expect { -re "Granted job allocation ($number)" { @@ -73,15 +72,12 @@ expect { } } -if {$got_job_grps == 0} { - fail "Did not get user info from slurm job" -} +subtest {$got_job_grps != 0} "Verify we were able to get user info from slurm job" + # # Confirm the user id and group id in the slurm job matches that # of the local 'id' execution. # -if {$login_grp_info ne $job_grp_info} { - fail "Login and slurm user info mismatch" -} +subtest {$login_grp_info eq $job_grp_info} "Verify user info from login and job match" "$login_grp_info != $job_grp_info" diff --git a/testsuite/expect/test21.39 b/testsuite/expect/test21.39 deleted file mode 100755 index 4131a478fc4..00000000000 --- a/testsuite/expect/test21.39 +++ /dev/null @@ -1,442 +0,0 @@ -#!/usr/bin/env expect -############################################################################ -# Purpose: Test of Slurm functionality -# sacctmgr create qos/account job and then delete account/qos -############################################################################ -# Copyright (C) SchedMD LLC. -# All rights reserved. -# -# This file is part of Slurm, a resource management program. -# For details, see . -# 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. -# -# 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. -############################################################################ -source ./globals -source ./globals_accounting - -set part_name "${test_name}_part" -set ta1 "${test_name}-account.1" -set ta2 "${test_name}-account.2" -set tu1 [get_my_user_name] -set tq1 "${test_name}-qos.1" -set tq2 "${test_name}-qos.2" -set job_id_1 0 - -# account options -array set acct_1 {} -array set acct_2 {} - -# user options -array set user_req_1 {} -set user_req_1(Account) $ta1 -set user_req_1(Qos) "$tq1,$tq2" -array set user_req_2 {} -set user_req_2(Account) $ta2 -set user_req_2(Qos) "$tq1,$tq2" - -# qos options -array set qos_1 {} -array set qos_2 {} - -set access_err 0 - -# Get the location of the slurm.conf file -set config_dir [get_conf_path] -set config_file $config_dir/slurm.conf - -# TODO: This test is just too error-prone to be useful (t21393) -skip "This test is temporally disabled to avoid testsuite noise (t21393)" - -# -# Verify preconditions -# -if {[get_config_param "AccountingStorageType"] ne "accounting_storage/slurmdbd"} { - skip "This test can't be run without a usable AccountStorageType" -} - -if {![param_contains [get_config_param "AccountingStorageEnforce"] "associations"]} { - skip "This test can't be run without AccountingStorageEnforce=associations" -} - -if {![param_contains [get_config_param "AccountingStorageEnforce"] "qos"]} { - skip "This test can't be run without AccountingStorageEnforce=qos" -} - -if {[param_contains [get_config_param "JobContainerType"] "*tmpfs"]} { - skip "This test can't be run with JobContainerType=job_container/tmpfs" -} - -if {[param_contains [get_config_param "SlurmctldParameters"] "enable_stepmgr"]} { - skip "This test can't be run with SlurmctldParameters=enable_stepmgr. Needs PrologFlags=contain" -} - -if {[get_admin_level] ne "Administrator"} { - skip "This test can't be run without being an Accounting administrator.\nUse: sacctmgr mod user \$USER set admin=admin" -} - -proc check_rc { rc } { - if {$rc != 0} { - fail "Subcommand failed with return code $rc" - } -} - -# Create test assoc and accounts -proc create_accounts {} { - global ta1 ta2 tq1 tq2 tu1 user_req_1 user_req_2 - global qos_1 qos_2 - log_debug "Create account and QOS" - - - # Create test assoc and accounts - check_rc [add_qos $tq1 [array get qos_1]] - check_rc [add_qos $tq2 [array get qos_2]] - check_rc [add_acct $ta1 [array get acct_1]] - check_rc [add_acct $ta2 [array get acct_2]] - check_rc [add_user $tu1 [array get user_req_1]] - check_rc [add_user $tu1 [array get user_req_2]] -} - -# Cleanup test assoc and accounts -proc cleanup_accounts {} { - global ta1 ta2 tq1 tq2 - - wait_for_account_done $ta1,$ta2 - - log_debug "Remove QOS: $tq1,$tq2" - remove_qos $tq1,$tq2 - - log_debug "Remove account: $ta1,$ta2" - remove_acct "" $ta1,$ta2 -} - -proc cleanup { } { - global job_id_1 config_file part_name - - cancel_job $job_id_1 - wait_for_part_done $part_name - - cleanup_accounts - - restore_conf $config_file - reconfigure -} - -proc test_salloc { qos account num_nodes } { - global salloc number part_name bin_bash - - set rc -12345 - set job_id 0 - - spawn $salloc -p$part_name --exclusive -q$qos -A$account -N$num_nodes $bin_bash - expect { - -re "allocation ($number)" { - set job_id $expect_out(1,string) - } - -re "error" { - fail "salloc job was not submitted" - } - timeout { - fail "salloc not responding" - } - } - - if { $job_id == 0 } { - fail "Submit failure" - } - - return $job_id -} - -proc check_job_reason { job_id state why } { - global scontrol re_word_str re_word_str - - set found_why "" - set state_found "" - - log_user 0 - spawn $scontrol show job $job_id - expect { - -re "State=($re_word_str)" { - set state_found $expect_out(1,string) - exp_continue - } - -re "Reason=($re_word_str)" { - set found_why $expect_out(1,string) - exp_continue - } - timeout { - fail "scontrol not responding" - } - eof { - lassign [wait] pid spawnid os_error_flag rc - } - } - - log_user 1 - if { $state_found != $state } { - fail "Job ($job_id) state found was $state_found, expected $state" - } - - set found_reason 0 - foreach iwhy $why { - if { $found_why == $iwhy } { - set found_reason 1 - break - } - } - - if { !$found_reason } { - fail "Job ($job_id) scontrol returned Reason=$found_why instead of Reason='$why'" - } - - log_debug "Found jobid $job_id with correct state '$state' and reason '$found_why'" - return 0 -} - -proc run_test { run_qos } { - global scontrol tq1 tq2 ta1 ta2 part_name - # TODO: Temporary globals for the extra debug info for bug10604 - global squeue sinfo sacctmgr - - if { $run_qos } { - set qos $tq1 - set acct $ta2 - set update_line "qos=$tq2" - set reason "InvalidQOS" - } else { - set qos $tq1 - set acct $ta1 - set update_line "account=$ta2" - set reason "InvalidAccount" - } - - set job_id_1 [test_salloc $qos $acct 1] - set job_id_2 [test_salloc $qos $acct 1] - # TODO Temporary debug to troubleshoot bug 10604 (revert once fixed) - # check_rc [wait_for_job $job_id_1 "RUNNING"] - set rc [wait_for_job $job_id_1 "RUNNING"] - if {$rc} { - log_warn "Job never started, extra debug information for bug10604 below before the actual fail:" - run_command "$squeue" - run_command "$sinfo" - run_command "$scontrol show job" - run_command "$scontrol show node" - run_command "$scontrol show partition" - run_command "$sacctmgr show qos -p" - fail "Subcommand failed with exit code $rc" - } - - if { $run_qos } { - # Remove test qos - if [remove_qos $qos] { - log_debug "We hit the race trying to get the job running before it hits the database before we removed the qos. This can be expected, trying again." - wait_for_part_done $part_name - return 1 - } - } else { - # Remove test acct - if [remove_acct "" $acct] { - log_debug "We hit the race trying to get the job running before it hits the database before we removed the account. This can be expected, trying again." - wait_for_part_done $part_name - return 1 - } - } - - # Verify jobs state and reason - check_job_reason $job_id_1 "RUNNING" [list "None"] - check_job_reason $job_id_2 "PENDING" [list "$reason"] - - # Update pending job to make it runnable, updating the running job isn't - # possible but it tests other code if you are watching the log file - spawn $scontrol update jobid=$job_id_1 $update_line - spawn $scontrol update jobid=$job_id_2 $update_line - - sleep 5 - # Check reasons after account alter - check_job_reason $job_id_1 "RUNNING" [list "None"] - check_job_reason $job_id_2 "PENDING" [list "Resources" "None"] - - # Cleanup jobs - wait_for_part_done $part_name - - return 0 -} - -cleanup_accounts -create_accounts - -# -# Copy slurm.conf file -# -save_conf $config_file - -# Comment out PrologFlags in the slurm.conf -exec $bin_sed -i {s/^\(PrologFlags=\)/#\1/gI} $config_file - -# TODO: Temporarily increase logging to debug bug 10604 (remove once fixed) -run_command -none "$bin_echo SlurmctldDebug=debug3 >> $config_file" -# Allow the test's existing reconfigure call to establish these values - -reconfigure -fail - -delete_part $part_name - -if [create_part $part_name 1] { - fail "Unable to create partition ($part_name)" -} - -for {set i 0} {$i < 2} {incr i} { - set cnt 0 - set rc 1 - # First lets test against removing an account and then qos - # from a running and pending job since partition only has 1 so - # one of these should run and the second should pend - while { $rc && ($cnt < 10) } { - set rc [run_test $i] - incr cnt - } - if { $rc } { - set credential [expr {$i == 0 ? "account" : "qos"}] - fail "Too many ($cnt) failures trying to remove $credential from job" - } -} - -log_info "Testing that PD jobs that lost their QOS will not lunch until restored" - -# Return to clean slate -cleanup_accounts - -# Add test qos -run_command -fail "$sacctmgr -vi add qos $tq1" - -# Add test account -run_command -fail "$sacctmgr -vi add account name=$ta1 set qos=normal,$tq1" - -# Add test user to account -run_command -fail "$sacctmgr -vi add user name=$tu1 account=$ta1" - -# Check what we just added to make sure it is there -if {![regexp -line "^\\S+\\|$ta1\\|$tu1\\|normal,$tq1" [run_command_output "$sacctmgr -n -P show assoc format=cluster,account,user,qos"]]} { - fail "Association with the right account, user and qos was not found" -} - -# Submit a delayed job that wants to run in the test qos -regexp {Submitted batch job (\d+)} [run_command_output -fail "$sbatch --begin=now+2 --qos=$tq1 -A $ta1 --wrap \"$srun sleep 10\" -o none -e none"] {} job_id_1 - -# The first job should get queued -wait_for_job -fail $job_id_1 "PENDING" - -# Remove the test qos out from under the job -run_command "$sacctmgr -vi modify account name=$ta1 user=$tu1 set qos=normal" - -# Submit a second delayed job requesting the test qos. It should be rejected -if {![run_command_status -none "$sbatch --begin=now+2 --qos=$tq1 -A $ta1 --wrap \"$srun sleep 10\" -o none -e none"]} { - fail "Job submitted with unassociated qos should have failed" -} else { - log_debug "The preceding job failure was expected" -} - -# Wait for the first job to pend with Reason=InvalidQOS -set condition_matched false -wait_for {$condition_matched} { - set scontrol_out [run_command_output -fail "$scontrol -o show job $job_id_1"] - regexp {JobState=([^ ]+) Reason=([^ ]+)} $scontrol_out {} job_state reason - if {$job_state eq "PENDING" && $reason eq "InvalidQOS"} { - set condition_matched true - } -} -subtest {$condition_matched} "Job ($job_id_1) should be PD with Reason=InvalidQOS" "JobState=$job_state, Reason=$reason" - -# Add back the test qos -run_command -fail "$sacctmgr -vi modify account name=$ta1 user=$tu1 set qos=normal,$tq1" - -# Wait for the first job to begin running -wait_for_job -fail $job_id_1 "RUNNING" - -log_info "Testing that PD jobs that lost their Account will not resume until restored" - -# Return to clean slate -cleanup_accounts - -# Add test qos -run_command -fail "$sacctmgr -vi add qos $tq1" - -# Add test accounts -run_command -fail "$sacctmgr -vi add account name=$ta1 set qos=normal,$tq1" -run_command -fail "$sacctmgr -vi add account name=$ta2 set qos=normal,$tq1" - -# Add test user to accounts -run_command -fail "$sacctmgr -vi add user name=$tu1 account=$ta1" -run_command -fail "$sacctmgr -vi add user name=$tu1 account=$ta2" - -# Check what we just added to make sure it is there -if {![regexp -line "^\\S+\\|$ta1\\|$tu1\\|normal,$tq1" [run_command_output "$sacctmgr -n -P show assoc format=cluster,account,user,qos"]]} { - fail "QOS ($tq1) has not been added to account ($ta1)" -} -if {![regexp -line "^\\S+\\|$ta2\\|$tu1\\|normal,$tq1" [run_command_output "$sacctmgr -n -P show assoc format=cluster,account,user,qos"]]} { - fail "QOS ($tq1) has not been added to account ($ta2)" -} - -# We may have to try this multiple times since we are relying on a race -# condition. If the idle job gets written to the job accounting records -# before the account is deleted, the account deletion will fail saying -# that it cannot delete an account with active jobs. -wait_for -fail {$delete_account_status == 0} { - # Submit a delayed job that wants to run with the test qos and test account 2 - regexp {Submitted batch job (\d+)} [run_command_output -fail "$sbatch --begin=now+2 --qos=$tq1 -A $ta2 --wrap \"$srun sleep 10\" -o none -e none"] {} job_id_1 - - # The first job should get queued - wait_for_job -fail $job_id_1 "PENDING" - - # Remove the test account out from under the job - set delete_account_results [run_command "$sacctmgr -vi delete account name=$ta2"] - set delete_account_status [dict get $delete_account_results exit_code] - set delete_account_output [dict get $delete_account_results output] - - # Mitigation if the account deletion fails - if {$delete_account_status != 0} { - if [regexp {Error with request: Job\(s\) active} $delete_account_output] { - cancel_job $job_id_1 - } else { - fail "Failure deleting account ($ta2): $delete_account_output" - } - } -} - -# Submit a second delayed job requesting test account 2. It should be rejected -if {![run_command_status -none "$sbatch --begin=now+2 --qos=$tq1 -A $ta2 --wrap \"$srun sleep 10\" -o none -e none"]} { - fail "Job submitted with unassociated account should have failed" -} else { - log_debug "The preceding job failure was expected" -} - -# Wait for the first job to pend with Reason=InvalidAccount -set condition_matched false -wait_for {$condition_matched} { - set scontrol_out [run_command_output -fail "$scontrol -o show job $job_id_1"] - regexp {JobState=([^ ]+) Reason=([^ ]+)} $scontrol_out {} job_state reason - if {$job_state eq "PENDING" && $reason eq "InvalidAccount"} { - set condition_matched true - } -} -subtest {$condition_matched} "Job ($job_id_1) should be PD with Reason=InvalidAccount" "JobState=$job_state, Reason=$reason" - -# Add back the test account -run_command -fail "$sacctmgr -vi add account name=$ta2 set qos=normal,$tq1" -run_command -fail "$sacctmgr -vi add user name=$tu1 account=$ta2" - -# Wait for the first job to begin running -wait_for_job -fail $job_id_1 "RUNNING" diff --git a/testsuite/python/lib/atf.py b/testsuite/python/lib/atf.py index 83c04f29371..304716256ec 100644 --- a/testsuite/python/lib/atf.py +++ b/testsuite/python/lib/atf.py @@ -546,6 +546,49 @@ def start_slurmctld(clean=False, quiet=False): pytest.fail(f"Slurmctld is not running") +def start_slurmdbd(clean=False, quiet=False): + """Starts the Slurm DB daemon (slurmdbd). + + This function may only be used in auto-config mode. + + Args: + clean (boolean): If True, clears previous slurmdbd state. + quiet (boolean): If True, logging is performed at the TRACE log level. + + Returns: + None + """ + if not properties["auto-config"]: + require_auto_config("wants to start slurmdbd") + + logging.debug("Starting slurmdbd...") + + if ( + run_command_exit( + "sacctmgr show cluster", user=properties["slurm-user"], quiet=quiet + ) + != 0 + ): + # Start slurmdbd + results = run_command( + f"{properties['slurm-sbin-dir']}/slurmdbd", + user=properties["slurm-user"], + quiet=quiet, + ) + if results["exit_code"] != 0: + pytest.fail( + f"Unable to start slurmdbd (rc={results['exit_code']}): {results['stderr']}" + ) + + # Verify that slurmdbd is running + if not repeat_command_until( + "sacctmgr show cluster", lambda results: results["exit_code"] == 0 + ): + pytest.fail(f"Slurmdbd is not running") + else: + logging.debug("Slurmdbd started successfully") + + def start_slurm(clean=False, quiet=False): """Starts all applicable Slurm daemons. @@ -574,28 +617,7 @@ def start_slurm(clean=False, quiet=False): get_config_parameter("AccountingStorageType", live=False, quiet=quiet) == "accounting_storage/slurmdbd" ): - if ( - run_command_exit( - "sacctmgr show cluster", user=properties["slurm-user"], quiet=quiet - ) - != 0 - ): - # Start slurmdbd - results = run_command( - f"{properties['slurm-sbin-dir']}/slurmdbd", - user=properties["slurm-user"], - quiet=quiet, - ) - if results["exit_code"] != 0: - pytest.fail( - f"Unable to start slurmdbd (rc={results['exit_code']}): {results['stderr']}" - ) - - # Verify that slurmdbd is running - if not repeat_command_until( - "sacctmgr show cluster", lambda results: results["exit_code"] == 0 - ): - pytest.fail(f"Slurmdbd is not running") + start_slurmdbd(clean, quiet) # Remove unnecessary default node0 from config to avoid being used or reserved output = run_command_output( @@ -694,6 +716,43 @@ def stop_slurmctld(quiet=False): pytest.fail("Slurmctld is still running") +def stop_slurmdbd(quiet=False): + """Stops the Slurm DB daemon (slurmdbd). + + This function may only be used in auto-config mode. + + Args: + quiet (boolean): If True, logging is performed at the TRACE log level. + + Returns: + None + """ + + if not properties["auto-config"]: + require_auto_config("wants to stop slurmdbd") + + logging.debug("Stopping slurmdbd...") + + # Stop slurmdbd + results = run_command( + "sacctmgr shutdown", user=properties["slurm-user"], quiet=quiet + ) + if results["exit_code"] != 0: + failures.append( + f"Command \"sacctmgr shutdown\" failed with rc={results['exit_code']}" + ) + + # Verify that slurmdbd is not running (we might have to wait for rollups to complete) + if not repeat_until( + lambda: pids_from_exe(f"{properties['slurm-sbin-dir']}/slurmdbd"), + lambda pids: len(pids) == 0, + timeout=60, + ): + failures.append("Slurmdbd is still running") + else: + logging.debug("Slurmdbd stopped successfully") + + def stop_slurm(fatal=True, quiet=False): """Stops all applicable Slurm daemons. @@ -728,22 +787,7 @@ def stop_slurm(fatal=True, quiet=False): get_config_parameter("AccountingStorageType", live=False, quiet=quiet) == "accounting_storage/slurmdbd" ): - # Stop slurmdbd - results = run_command( - "sacctmgr shutdown", user=properties["slurm-user"], quiet=quiet - ) - if results["exit_code"] != 0: - failures.append( - f"Command \"sacctmgr shutdown\" failed with rc={results['exit_code']}" - ) - - # Verify that slurmdbd is not running (we might have to wait for rollups to complete) - if not repeat_until( - lambda: pids_from_exe(f"{properties['slurm-sbin-dir']}/slurmdbd"), - lambda pids: len(pids) == 0, - timeout=60, - ): - failures.append("Slurmdbd is still running") + stop_slurmdbd(quiet) # Stop slurmctld and slurmds results = run_command( diff --git a/testsuite/python/tests/test_102_5.py b/testsuite/python/tests/test_102_5.py new file mode 100644 index 00000000000..2490fa66cfb --- /dev/null +++ b/testsuite/python/tests/test_102_5.py @@ -0,0 +1,337 @@ +############################################################################ +# Copyright (C) SchedMD LLC. +############################################################################ +import atf +import pytest + +# Global variables +qos1 = "qos1" +qos2 = "qos2" +acct1 = "acct1" +acct2 = "acct2" +acct = "acct" + + +@pytest.fixture(scope="module", autouse=True) +def setup(): + """Test setup with required configurations.""" + atf.require_auto_config("Manually creating and deleting qoses and accounts") + atf.require_config_parameter("AccountingStorageType", "accounting_storage/slurmdbd") + atf.require_config_parameter_includes("AccountingStorageEnforce", "associations") + atf.require_config_parameter_includes("AccountingStorageEnforce", "qos") + atf.require_slurm_running() + + +@pytest.fixture(scope="function", autouse=True) +def setup_db(): + # Create test QOS and account + atf.run_command( + f"sacctmgr -i add qos {qos1},{qos2}", + user=atf.properties["slurm-user"], + fatal=True, + ) + atf.run_command( + f"sacctmgr -i add account {acct},{acct1},{acct2}", + user=atf.properties["slurm-user"], + fatal=True, + ) + atf.run_command( + f"sacctmgr -i add user {atf.get_user_name()} DefaultAccount={acct} account={acct1},{acct2} qos=normal,{qos1},{qos2}", + user=atf.properties["slurm-user"], + fatal=True, + ) + yield + + atf.cancel_all_jobs(fatal=True) + + atf.run_command( + f"sacctmgr -i remove user {atf.get_user_name()} {acct1},{acct2}", + user=atf.properties["slurm-user"], + quiet=True, + ) + atf.run_command( + f"sacctmgr -i remove account {acct1},{acct2}", + user=atf.properties["slurm-user"], + quiet=True, + ) + atf.run_command( + f"sacctmgr -i remove qos {qos1},{qos2}", + user=atf.properties["slurm-user"], + quiet=True, + ) + + +def submit_job_with(extra_params, xfail=False, fatal=False): + """Submit a job with specified extra params.""" + return atf.submit_job_sbatch( + f"{extra_params} -N1 --wrap='sleep 300'", xfail=xfail, fatal=fatal + ) + + +def test_qos_removal_single(): + """Test removing a QOS in use: + - Verify that running jobs with that QOS keep running. + - Verify that pending jobs are updated to InvalidQOS + - Verify that new jobs cannot use the removed QOS. + """ + # Stop slurmdbd to avoid the job info being saved in the DB + atf.stop_slurmdbd(quiet=True) + + # Submit a blocking job + job_id1 = submit_job_with(f"--qos={qos1} --exclusive", fatal=True) + assert atf.wait_for_job_state( + job_id1, "RUNNING" + ), f"Job {job_id1} never started running" + + # Submit another job in the same node to be blocked (due exclusive) + node = atf.get_job_parameter(job_id1, "NodeList") + job_id2 = submit_job_with(f"--qos={qos1} -w {node}", fatal=True) + assert atf.wait_for_job_state( + job_id2, "PENDING" + ), f"Job {job_id2} should be pending" + + # Stop slurmctld before starting slurmdbd to keep the jobs info out of + # the DB, only in slurmctld for the moment. + atf.stop_slurmctld(quiet=True) + atf.start_slurmdbd(quiet=True) + + # Remove the QOS from the DB. + # Note that slurmdbd won't have the QOS or the jobs using it, while + # slurmctld knows the jobs and still thinks that the QOS exists. + atf.run_command( + f"sacctmgr -i remove qos {qos1}", + user=atf.properties["slurm-user"], + fatal=True, + ) + + # Start slurmctld and verify job states/reasons are the expected now that + # the QOS doesn't exists anymore. + atf.start_slurmctld(quiet=True) + + # Running job should continue running + assert atf.wait_for_job_state( + job_id1, "RUNNING", desired_reason="None" + ), f"Previously running job {job_id1} should stay RUNNING with 'None' reason" + + # Pending job should be marked with InvalidQOS + assert atf.wait_for_job_state( + job_id2, "PENDING", desired_reason="InvalidQOS" + ), f"Pending job {job_id2} should be PENDING with InvalidQOS reason" + + # Try to submit a new job with removed QOS - should be rejected + assert ( + submit_job_with(f"--qos={qos1}", xfail=True) == 0 + ), f"Job submission with removed QOS {qos1} should have failed" + + +def test_qos_removal_multiple(): + """Test QOS removal when user has multiple QOS access: + - Verify that running jobs with removed QOS keep running. + - Verify that pending jobs with removed QOS are updated to InvalidQOS. + - Verify that jobs with remaining QOS stay valid. + - Verify that new jobs cannot use the removed QOS. + - Verify that new job can use the remaining QOS. + """ + + # Stop slurmdbd to avoid the job info being saved in the DB + atf.stop_slurmdbd(quiet=True) + + # Submit a blocking job + job_id1 = submit_job_with(f"--qos={qos1} --exclusive", fatal=True) + assert atf.wait_for_job_state( + job_id1, "RUNNING" + ), f"Job {job_id1} never started running" + + # Submit two more jobs in the same node to be blocked (due exclusive) + node = atf.get_job_parameter(job_id1, "NodeList") + job_id2 = submit_job_with(f"--qos={qos1} -w {node}", fatal=True) + job_id3 = submit_job_with(f"--qos={qos2} -w {node}", fatal=True) + + # Verify both jobs are pending + assert atf.wait_for_job_state( + job_id2, "PENDING" + ), f"Job {job_id2} should be pending" + assert atf.wait_for_job_state( + job_id3, "PENDING" + ), f"Job {job_id3} should be pending" + + # Stop slurmctld before starting slurmdbd to keep the jobs info out of + # the DB, only in slurmctld for the moment. + atf.stop_slurmctld(quiet=True) + atf.start_slurmdbd(quiet=True) + + # Remove the QOS from the DB. + # Note that slurmdbd won't have the QOS or the jobs using it, while + # slurmctld knows the jobs and still thinks that the QOS exists. + atf.run_command( + f"sacctmgr -i remove qos {qos1}", + user=atf.properties["slurm-user"], + fatal=True, + ) + + # Start slurmctld and verify job states/reasons are the expected now that + # the QOS doesn't exists anymore. + atf.start_slurmctld(quiet=True) + + # Running job should continue running + assert atf.wait_for_job_state( + job_id1, "RUNNING", desired_reason="None" + ), f"Previously running job {job_id1} should stay RUNNING with 'None' reason" + + # Pending job with removed QOS should be marked with InvalidQOS + assert atf.wait_for_job_state( + job_id2, "PENDING", desired_reason="InvalidQOS" + ), f"Pending job {job_id2} should be PENDING with InvalidQOS reason" + + # Pending job with remaining QOS should stay valid + assert atf.wait_for_job_state( + job_id3, "PENDING" + ), f"Job {job_id3} should be PENDING" + assert ( + atf.get_job_parameter(job_id3, "Reason") != "InvalidQOS" + ), f"Job {job_id3} using qos2 should not have InvalidQOS reason" + + # Try to submit a new job with removed QOS - should be rejected + assert ( + submit_job_with(f"--qos={qos1}", xfail=True) == 0 + ), f"Job submission with removed QOS {qos1} should have failed" + + # Submit a job with remaining QOS - should succeed + assert ( + submit_job_with(f"--qos={qos2}") != 0 + ), f"Job submission with valid QOS {qos2} should have succeeded" + + +def test_account_removal_single(): + """Test removing an account in use: + - Verify that running jobs with that account keep running. + - Verify that pending jobs are updated to InvalidAccount. + - Verify that new jobs cannot use the removed account. + """ + + # Stop slurmdbd to avoid the job info being saved in the DB + atf.stop_slurmdbd(quiet=True) + + # Submit a blocking job + job_id1 = submit_job_with(f"--account={acct1} --exclusive", fatal=True) + assert atf.wait_for_job_state( + job_id1, "RUNNING" + ), f"Job {job_id1} never started running" + + # Submit another job in the same node to be blocked (due exclusive) + node = atf.get_job_parameter(job_id1, "NodeList") + job_id2 = submit_job_with(f"--account={acct1} -w {node}", fatal=True) + assert atf.wait_for_job_state( + job_id2, "PENDING" + ), f"Job {job_id2} should be pending" + + # Stop slurmctld before starting slurmdbd to keep the jobs info out of + # the DB, only in slurmctld for the moment. + atf.stop_slurmctld(quiet=True) + atf.start_slurmdbd(quiet=True) + + # Remove the account from the DB. + # Note that slurmdbd won't have the account or the jobs using it, while + # slurmctld knows the jobs and still thinks that the account exists. + atf.run_command( + f"sacctmgr -i remove account {acct1}", + user=atf.properties["slurm-user"], + fatal=True, + ) + + # Start slurmctld and verify job states/reasons are the expected now that + # the account doesn't exists anymore. + atf.start_slurmctld(quiet=True) + + # Running job should continue running + assert atf.wait_for_job_state( + job_id1, "RUNNING", desired_reason="None" + ), f"Previously running job {job_id1} should stay RUNNING with 'None' reason" + + # Pending job should be marked with InvalidAccount + assert atf.wait_for_job_state( + job_id2, "PENDING", desired_reason="InvalidAccount" + ), f"Pending job {job_id2} should be PENDING with InvalidAccount reason" + + # Try to submit a new job with removed account - should be rejected + assert ( + submit_job_with(f"--account={acct1}", xfail=True) == 0 + ), f"Job submission with removed account {acct1} should have failed" + + +def test_account_removal_multiple(): + """Test removing an account when user has multiple account access: + - Verify that running jobs with removed account keep running. + - Verify that pending jobs with removed account are updated to InvalidAccount. + - Verify that jobs with remaining account stay valid. + - Verify that new jobs cannot use the removed account. + - Verify that new jobs can use the remaining account. + """ + + # Stop slurmdbd to avoid the job info being saved in the DB + atf.stop_slurmdbd(quiet=True) + + # Submit a blocking job + job_id1 = submit_job_with(f"--account={acct1} --exclusive", fatal=True) + assert atf.wait_for_job_state( + job_id1, "RUNNING" + ), f"Job {job_id1} never started running" + + # Submit two more jobs in the same node to be blocked (due exclusive) + node = atf.get_job_parameter(job_id1, "NodeList") + job_id2 = submit_job_with(f"--account={acct1} -w {node}", fatal=True) + job_id3 = submit_job_with(f"--account={acct2} -w {node}", fatal=True) + + # Verify both jobs are pending + assert atf.wait_for_job_state( + job_id2, "PENDING" + ), f"Job {job_id2} should be pending" + assert atf.wait_for_job_state( + job_id3, "PENDING" + ), f"Job {job_id3} should be pending" + + # Stop slurmctld before starting slurmdbd to keep the jobs info out of + # the DB, only in slurmctld for the moment. + atf.stop_slurmctld(quiet=True) + atf.start_slurmdbd(quiet=True) + + # Remove the account from the DB. + # Note that slurmdbd won't have the account or the jobs using it, while + # slurmctld knows the jobs and still thinks that the account exists. + atf.run_command( + f"sacctmgr -i remove account {acct1}", + user=atf.properties["slurm-user"], + fatal=True, + ) + + # Start slurmctld and verify job states/reasons are the expected now that + # the account doesn't exists anymore. + atf.start_slurmctld(quiet=True) + + # Running job should continue running + assert atf.wait_for_job_state( + job_id1, "RUNNING", desired_reason="None" + ), f"Previously running job {job_id1} should stay RUNNING with 'None' reason" + + # Pending job with removed account should be marked with InvalidAccount + assert atf.wait_for_job_state( + job_id2, "PENDING", desired_reason="InvalidAccount" + ), f"Pending job {job_id2} should be PENDING with InvalidAccount reason" + + # Pending job with remaining account should stay valid + assert atf.wait_for_job_state( + job_id3, "PENDING" + ), f"Job {job_id3} should be PENDING" + assert ( + atf.get_job_parameter(job_id3, "Reason") != "InvalidAccount" + ), f"Job {job_id3} using acct2 should not have InvalidAccount reason" + + # Try to submit a new job with removed account - should be rejected + assert ( + submit_job_with(f"--account={acct1}", xfail=True) == 0 + ), f"Job submission with removed account {acct1} should have failed" + + # Submit a job with remaining account - should succeed + assert ( + submit_job_with(f"--account={acct2}") != 0 + ), f"Job submission with valid account {acct2} should have succeeded" diff --git a/testsuite/python/tests/test_112_41.py b/testsuite/python/tests/test_112_41.py index 1d529b1fc7c..8d197dfa399 100644 --- a/testsuite/python/tests/test_112_41.py +++ b/testsuite/python/tests/test_112_41.py @@ -928,8 +928,6 @@ def test_db_config(slurmdb): assert len(resp.errors) == 0 -# TODO: Remove xfail once bug 21341 is fixed -@pytest.mark.xfail def test_jobs(slurm, slurmdb): from openapi_client.models.v0041_job_submit_req import V0041JobSubmitReq from openapi_client.models.v0041_job_desc_msg import V0041JobDescMsg @@ -1001,9 +999,11 @@ def test_jobs(slurm, slurmdb): resp = slurm.slurm_v0041_post_job(str(jobid), v0041_job_desc_msg=job) assert not len(resp.warnings) assert not len(resp.errors) - for result in resp.results: - assert result.job_id == jobid - assert result.error_code == 0 + # Not all changes populate "results" field + if resp.results is not None: + for result in resp.results: + assert result.job_id == jobid + assert result.error_code == 0 resp = slurm.slurm_v0041_get_job(str(jobid)) assert len(resp.warnings) == 0 @@ -1043,8 +1043,8 @@ def test_jobs(slurm, slurmdb): assert len(resp.errors) == 0 assert resp.jobs for job in resp.jobs: - if job.name == "allocation": - # job hasn't settled at slurmdbd yet + if job.name != "updated test job": + # job change hasn't settled at slurmdbd yet requery = True else: requery = False