69c70935c73ae72fc607f8f4bf3efa64b090faf7
[csit.git] / resources / libraries / bash / function / common.sh
1 # Copyright (c) 2018 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 set -exuo pipefail
15
16 # This library defines functions used by multiple entry scripts.
17 # Keep functions ordered alphabetically, please.
18
19 # TODO: Add a link to bash style guide.
20 # TODO: Consider putting every die into a {} block,
21 #   the code might become more readable (but longer).
22
23
24 function activate_virtualenv () {
25
26     set -exuo pipefail
27
28     # Arguments:
29     # - ${1} - Non-empty path to existing directory for creating virtualenv in.
30     # Variables read:
31     # - CSIT_DIR - Path to existing root of local CSIT git repository.
32     # Variables set:
33     # - ENV_DIR - Path to the created virtualenv subdirectory.
34     # Variables exported:
35     # - PYTHONPATH - CSIT_DIR, as CSIT Python scripts usually need this.
36     # Functions called:
37     # - die - Print to stderr and exit.
38
39     # TODO: Do we really need to have ENV_DIR available as a global variable?
40
41     if [[ "${1-}" == "" ]]; then
42         die "Root location of virtualenv to create is not specified."
43     fi
44     ENV_DIR="${1}/env"
45     rm -rf "${ENV_DIR}" || die "Failed to clean previous virtualenv."
46
47     pip install --upgrade virtualenv || {
48         die "Virtualenv package install failed."
49     }
50     virtualenv "${ENV_DIR}" || {
51         die "Virtualenv creation failed."
52     }
53     set +u
54     source "${ENV_DIR}/bin/activate" || die "Virtualenv activation failed."
55     set -u
56     pip install -r "${CSIT_DIR}/requirements.txt" || {
57         die "CSIT requirements installation failed."
58     }
59
60     # Most CSIT Python scripts assume PYTHONPATH is set and exported.
61     export PYTHONPATH="${CSIT_DIR}" || die "Export failed."
62 }
63
64
65 function check_download_dir () {
66
67     set -exuo pipefail
68
69     # Fail if there are no files visible in ${DOWNLOAD_DIR}.
70     # TODO: Do we need this as a function, if it is (almost) a one-liner?
71     #
72     # Variables read:
73     # - DOWNLOAD_DIR - Path to directory pybot takes the build to test from.
74     # Directories read:
75     # - ${DOWNLOAD_DIR} - Has to be non-empty to proceed.
76     # Functions called:
77     # - die - Print to stderr and exit.
78
79     if [[ ! "$(ls -A "${DOWNLOAD_DIR}")" ]]; then
80         die "No artifacts downloaded!"
81     fi
82 }
83
84
85 function cleanup_topo () {
86
87     set -exuo pipefail
88
89     # Variables read:
90     # - WORKING_TOPOLOGY - Path to topology yaml file of the reserved testbed.
91     # - PYTHON_SCRIPTS_DIR - Path to directory holding the reservation script.
92
93     python "${PYTHON_SCRIPTS_DIR}/topo_cleanup.py" -t "${WORKING_TOPOLOGY}"
94     # Not using "|| die" as some callers might want to ignore errors,
95     # e.g. in teardowns, such as unreserve.
96 }
97
98
99 function common_dirs () {
100
101     set -exuo pipefail
102
103     # Variables set:
104     # - BASH_FUNCTION_DIR - Path to existing directory this file is located in.
105     # - CSIT_DIR - Path to existing root of local CSIT git repository.
106     # - TOPOLOGIES_DIR - Path to existing directory with available tpologies.
107     # - RESOURCES_DIR - Path to existing CSIT subdirectory "resources".
108     # - TOOLS_DIR - Path to existing resources subdirectory "tools".
109     # - PYTHON_SCRIPTS_DIR - Path to existing tools subdirectory "scripts".
110     # - ARCHIVE_DIR - Path to created CSIT subdirectory "archive".
111     # - DOWNLOAD_DIR - Path to created CSIT subdirectory "download_dir".
112     # Functions called:
113     # - die - Print to stderr and exit.
114
115     BASH_FUNCTION_DIR="$(dirname "$(readlink -e "${BASH_SOURCE[0]}")")" || {
116         die "Some error during localizing this source directory."
117     }
118     # Current working directory could be in a different repo, e.g. VPP.
119     pushd "${BASH_FUNCTION_DIR}" || die "Pushd failed"
120     CSIT_DIR="$(readlink -e "$(git rev-parse --show-toplevel)")" || {
121         die "Readlink or git rev-parse failed."
122     }
123     popd || die "Popd failed."
124     TOPOLOGIES_DIR="$(readlink -e "${CSIT_DIR}/topologies/available")" || {
125         die "Readlink failed."
126     }
127     RESOURCES_DIR="$(readlink -e "${CSIT_DIR}/resources")" || {
128         die "Readlink failed."
129     }
130     TOOLS_DIR="$(readlink -e "${RESOURCES_DIR}/tools")" || {
131         die "Readlink failed."
132     }
133     PYTHON_SCRIPTS_DIR="$(readlink -e "${TOOLS_DIR}/scripts")" || {
134         die "Readlink failed."
135     }
136
137     ARCHIVE_DIR="$(readlink -f "${CSIT_DIR}/archive")" || {
138         die "Readlink failed."
139     }
140     mkdir -p "${ARCHIVE_DIR}" || die "Mkdir failed."
141     DOWNLOAD_DIR="$(readlink -f "${CSIT_DIR}/download_dir")" || {
142         die "Readlink failed."
143     }
144     mkdir -p "${DOWNLOAD_DIR}" || die "Mkdir failed."
145 }
146
147
148 function compose_pybot_arguments () {
149
150     set -exuo pipefail
151
152     # Variables read:
153     # - WORKING_TOPOLOGY - Path to topology yaml file of the reserved testbed.
154     # - DUT - CSIT test/ subdirectory, set while processing tags.
155     # - TAGS - Array variable holding selected tag boolean expressions.
156     # - TOPOLOGIES_TAGS - Tag boolean expression filtering tests for topology.
157     # Variables set:
158     # - PYBOT_ARGS - String holding part of all arguments for pybot.
159     # - EXPANDED_TAGS - Array of strings pybot arguments compiled from tags.
160
161     # No explicit check needed with "set -u".
162     PYBOT_ARGS=("--loglevel" "TRACE" "--variable" "TOPOLOGY_PATH:${WORKING_TOPOLOGY}")
163     PYBOT_ARGS+=("--suite" "tests.${DUT}.perf")
164
165     EXPANDED_TAGS=()
166     for tag in "${TAGS[@]}"; do
167         if [[ ${tag} == "!"* ]]; then
168             EXPANDED_TAGS+=("--exclude" "${tag#$"!"}")
169         else
170             EXPANDED_TAGS+=("--include" "${TOPOLOGIES_TAGS}AND${tag}")
171         fi
172     done
173 }
174
175
176 function copy_archives () {
177
178     set -exuo pipefail
179
180     # Variables read:
181     # - WORKSPACE - Jenkins workspace, copy only if the value is not empty.
182     #   Can be unset, then it speeds up manual testing.
183     # - ARCHIVE_DIR - Path to directory with content to be copied.
184     # Directories updated:
185     # - ${WORKSPACE}/archives/ - Created if does not exist.
186     #   Content of ${ARCHIVE_DIR}/ is copied here.
187     # Functions called:
188     # - die - Print to stderr and exit.
189
190     # We will create additional archive if workspace variable is set.
191     # This way if script is running in jenkins all will be
192     # automatically archived to logs.fd.io.
193     if [[ -n "${WORKSPACE-}" ]]; then
194         mkdir -p "${WORKSPACE}/archives/" || die "Archives dir create failed."
195         cp -rf "${ARCHIVE_DIR}"/* "${WORKSPACE}/archives" || die "Copy failed."
196     fi
197 }
198
199
200 function die () {
201     # Print the message to standard error end exit with error code specified
202     # by the second argument.
203     #
204     # Hardcoded values:
205     # - The default error message.
206     # Arguments:
207     # - ${1} - The whole error message, be sure to quote. Optional
208     # - ${2} - the code to exit with, default: 1.
209
210     set -x
211     set +eu
212     warn "${1:-Unspecified run-time error occurred!}"
213     exit "${2:-1}"
214 }
215
216
217 function die_on_pybot_error () {
218
219     set -exuo pipefail
220
221     # Source this fragment if you want to abort on any failed test case.
222     #
223     # Variables read:
224     # - PYBOT_EXIT_STATUS - Set by a pybot running fragment.
225     # Functions called:
226     # - die - Print to stderr and exit.
227
228     if [[ "${PYBOT_EXIT_STATUS}" != "0" ]]; then
229         die "${PYBOT_EXIT_STATUS}" "Test failures are present!"
230     fi
231 }
232
233
234 function get_test_code () {
235
236     set -exuo pipefail
237
238     # Arguments:
239     # - ${1} - Optional, argument of entry script (or empty as unset).
240     #   Test code value to override job name from environment.
241     # Variables read:
242     # - JOB_NAME - String affecting test selection, default if not argument.
243     # Variables set:
244     # - TEST_CODE - The test selection string from environment or argument.
245     # - NODENESS - Node multiplicity of desired testbed.
246     # - FLAVOR - Node flavor string, usually describing the processor.
247
248     TEST_CODE="${1-}" || die "Reading optional argument failed, somehow."
249     if [[ -z "${TEST_CODE}" ]]; then
250         TEST_CODE="${JOB_NAME-}" || die "Reading job name failed, somehow."
251     fi
252
253     case "${TEST_CODE}" in
254         *"2n-skx"*)
255             NODENESS="2n"
256             FLAVOR="skx"
257             ;;
258         *"3n-skx"*)
259             NODENESS="3n"
260             FLAVOR="skx"
261             ;;
262         *)
263             # Fallback to 3-node Haswell by default (backward compatibility)
264             NODENESS="3n"
265             FLAVOR="hsw"
266             ;;
267     esac
268 }
269
270
271 function get_test_tag_string () {
272
273     set -exuo pipefail
274
275     # Variables read:
276     # - GERRIT_EVENT_TYPE - Event type set by gerrit, can be unset.
277     # - GERRIT_EVENT_COMMENT_TEXT - Comment text, read for "comment-added" type.
278     # Variables set:
279     # - TEST_TAG_STRING - The string following "perftest" in gerrit comment,
280     #   or empty.
281
282     # TODO: ci-management scripts no longer need to perform this.
283
284     trigger=""
285     if [[ "${GERRIT_EVENT_TYPE-}" == "comment-added" ]]; then
286         # On parsing error, ${trigger} stays empty.
287         trigger="$(echo "${GERRIT_EVENT_COMMENT_TEXT}" \
288             | grep -oE '(perftest$|perftest[[:space:]].+$)')" || true
289     fi
290     # Set test tags as string.
291     TEST_TAG_STRING="${trigger#$"perftest"}"
292 }
293
294
295 function reserve_testbed () {
296
297     set -exuo pipefail
298
299     # Reserve physical testbed, perform cleanup, register trap to unreserve.
300     #
301     # Variables read:
302     # - TOPOLOGIES - Array of paths to topology yaml to attempt reservation on.
303     # - PYTHON_SCRIPTS_DIR - Path to directory holding the reservation script.
304     # Variables set:
305     # - WORKING_TOPOLOGY - Path to topology yaml file of the reserved testbed.
306     # Functions called:
307     # - die - Print to stderr and exit.
308     # Traps registered:
309     # - EXIT - Calls cancel_all for ${WORKING_TOPOLOGY}.
310
311     while true; do
312         for topo in "${TOPOLOGIES[@]}"; do
313             set +e
314             python "${PYTHON_SCRIPTS_DIR}/topo_reservation.py" -t "${topo}"
315             result="$?"
316             set -e
317             if [[ "${result}" == "0" ]]; then
318                 WORKING_TOPOLOGY="${topo}"
319                 echo "Reserved: ${WORKING_TOPOLOGY}"
320                 trap "untrap_and_unreserve_testbed" EXIT || {
321                     message="TRAP ATTEMPT AND UNRESERVE FAILED, FIX MANUALLY."
322                     untrap_and_unreserve_testbed "${message}" || {
323                         die "Teardown should have died, not failed."
324                     }
325                     die "Trap attempt failed, unreserve succeeded. Aborting."
326                 }
327                 cleanup_topo || {
328                     die "Testbed cleanup failed."
329                 }
330                 break
331             fi
332         done
333
334         if [[ -n "${WORKING_TOPOLOGY-}" ]]; then
335             # Exit the infinite while loop if we made a reservation.
336             break
337         fi
338
339         # Wait ~3minutes before next try.
340         sleep_time="$[ ( $RANDOM % 20 ) + 180 ]s" || {
341             die "Sleep time calculation failed."
342         }
343         echo "Sleeping ${sleep_time}"
344         sleep "${sleep_time}" || die "Sleep failed."
345     done
346 }
347
348
349 function run_pybot () {
350
351     set -exuo pipefail
352
353     # Currently, VPP-1361 causes occasional test failures.
354     # If real result is more important than time, we can retry few times.
355     # TODO: We should be retrying on test case level instead.
356
357     # Arguments:
358     # - ${1} - Optional number of pybot invocations to try to avoid failures.
359     #   Default: 1.
360     # Variables read:
361     # - CSIT_DIR - Path to existing root of local CSIT git repository.
362     # - ARCHIVE_DIR - Path to store robot result files in.
363     # - PYBOT_ARGS, EXPANDED_TAGS - See compose_pybot_arguments.sh
364     # Variables set:
365     # - PYBOT_EXIT_STATUS - Exit status of most recent pybot invocation.
366     # Functions called:
367     # - die - Print to stderr and exit.
368
369     # Set ${tries} as an integer variable, to fail on non-numeric input.
370     local -i "tries" || die "Setting type of variable failed."
371     tries="${1:-1}" || die "Argument evaluation failed."
372     all_options=("--outputdir" "${ARCHIVE_DIR}" "${PYBOT_ARGS[@]}")
373     all_options+=("${EXPANDED_TAGS[@]}")
374
375     while true; do
376         if [[ "${tries}" -le 0 ]]; then
377             break
378         else
379             tries="$((${tries} - 1))"
380         fi
381         pushd "${CSIT_DIR}" || die "Change directory operation failed."
382         set +e
383         # TODO: Make robot tests not require "$(pwd)" == "${CSIT_DIR}".
384         pybot "${all_options[@]}" "${CSIT_DIR}/tests/"
385         PYBOT_EXIT_STATUS="$?"
386         set -e
387         popd || die "Change directory operation failed."
388         if [[ "${PYBOT_EXIT_STATUS}" == "0" ]]; then
389             break
390         fi
391     done
392 }
393
394
395 function select_tags () {
396
397     set -exuo pipefail
398
399     # Variables read:
400     # - WORKING_TOPOLOGY - Path to topology yaml file of the reserved testbed.
401     # - TEST_CODE - String affecting test selection, usually jenkins job name.
402     # - TEST_TAG_STRING - String selecting tags, from gerrit comment.
403     #   Can be unset.
404     # - TOPOLOGIES_DIR - Path to existing directory with available tpologies.
405     # Variables set:
406     # - TAGS - Array of processed tag boolean expressions.
407
408     # NIC SELECTION
409     # All topologies NICs
410     available=$(grep -hoPR "model: \K.*" "${TOPOLOGIES_DIR}"/* | sort -u)
411     # Selected topology NICs
412     reserved=$(grep -hoPR "model: \K.*" "${WORKING_TOPOLOGY}" | sort -u)
413     # All topologies NICs - Selected topology NICs
414     exclude_nics=($(comm -13 <(echo "${reserved}") <(echo "${available}")))
415
416     case "${TEST_CODE}" in
417         # Select specific performance tests based on jenkins job type variable.
418         *"ndrpdr-weekly"* )
419             test_tag_array=("ndrpdrAND64bAND1c"
420                             "ndrpdrAND78bAND1c")
421             ;;
422         *"mrr-daily"* | *"mrr-weekly"* )
423             test_tag_array=(# vic
424                             "mrrANDnic_cisco-vic-1227AND64b"
425                             "mrrANDnic_cisco-vic-1385AND64b"
426                             # memif
427                             "mrrANDmemifANDethAND64b"
428                             "mrrANDmemifANDethANDimix"
429                             # crypto
430                             "mrrANDipsecAND64b"
431                             # ip4 base
432                             "mrrANDip4baseAND64b"
433                             # ip4 scale FIB 2M
434                             "mrrANDip4fwdANDfib_2mAND64b"
435                             # ip4 scale FIB 200k
436                             "mrrANDip4fwdANDfib_200kANDnic_intel-*710AND64b"
437                             # ip4 scale FIB 20k
438                             "mrrANDip4fwdANDfib_20kANDnic_intel-*710AND64b"
439                             # ip4 scale ACL
440                             "mrrANDip4fwdANDacl1AND10k_flowsAND64b"
441                             "mrrANDip4fwdANDacl50AND10k_flowsAND64b"
442                             # ip4 scale NAT44
443                             "mrrANDip4fwdANDnat44ANDbaseAND64b"
444                             "mrrANDip4fwdANDnat44ANDsrc_user_4000AND64b"
445                             # ip4 features
446                             "mrrANDip4fwdANDfeatureANDnic_intel-*710AND64b"
447                             # TODO: Remove when tags in
448                             # tests/vpp/perf/ip4/*-ipolicemarkbase-*.robot
449                             # are fixed
450                             "mrrANDip4fwdANDpolice_markANDnic_intel-*710AND64b"
451                             # ip4 tunnels
452                             "mrrANDip4fwdANDencapANDip6unrlayANDip4ovrlayANDnic_intel-x520-da2AND64b"
453                             "mrrANDip4fwdANDencapANDnic_intel-*710AND64b"
454                             "mrrANDl2ovrlayANDencapANDnic_intel-*710AND64b"
455                             # ip6 base
456                             "mrrANDip6baseANDethAND78b"
457                             # ip6 features
458                             "mrrANDip6fwdANDfeatureANDnic_intel-*710AND78b"
459                             # ip6 scale FIB 2M
460                             "mrrANDip6fwdANDfib_2mANDnic_intel-*710AND78b"
461                             # ip6 scale FIB 200k
462                             "mrrANDip6fwdANDfib_200kANDnic_intel-*710AND78b"
463                             # ip6 scale FIB 20k
464                             "mrrANDip6fwdANDfib_20kANDnic_intel-*710AND78b"
465                             # ip6 tunnels
466                             "mrrANDip6fwdANDencapANDnic_intel-x520-da2AND78b"
467                             # l2xc base
468                             "mrrANDl2xcfwdANDbaseAND64b"
469                             # l2xc scale ACL
470                             "mrrANDl2xcANDacl1AND10k_flowsAND64b"
471                             "mrrANDl2xcANDacl50AND10k_flowsAND64b"
472                             # l2xc scale FIB 2M
473                             "mrrANDl2xcANDfib_2mAND64b"
474                             # l2xc scale FIB 200k
475                             "mrrANDl2xcANDfib_200kANDnic_intel-*710AND64b"
476                             # l2xc scale FIB 20k
477                             "mrrANDl2xcANDfib_20kANDnic_intel-*710AND64b"
478                             # l2bd base
479                             "mrrANDl2bdmaclrnANDbaseAND64b"
480                             # l2bd scale ACL
481                             "mrrANDl2bdmaclrnANDacl1AND10k_flowsAND64b"
482                             "mrrANDl2bdmaclrnANDacl50AND10k_flowsAND64b"
483                             # l2bd scale FIB 2M
484                             "mrrANDl2bdmaclrnANDfib_1mAND64b"
485                             # l2bd scale FIB 200k
486                             "mrrANDl2bdmaclrnANDfib_100kANDnic_intel-*710AND64b"
487                             # l2bd scale FIB 20k
488                             "mrrANDl2bdmaclrnANDfib_10kANDnic_intel-*710AND64b"
489                             # l2 patch base
490                             "mrrANDl2patchAND64b"
491                             # srv6
492                             "mrrANDsrv6ANDnic_intel-x520-da2AND78b"
493                             # vts
494                             "mrrANDvtsANDnic_intel-x520-da2AND114b"
495                             # vm vhost l2xc base
496                             "mrrANDvhostANDl2xcfwdANDbaseAND64b"
497                             "mrrANDvhostANDl2xcfwdANDbaseANDimix"
498                             # vm vhost l2bd base
499                             "mrrANDvhostANDl2bdmaclrnANDbaseAND64b"
500                             "mrrANDvhostANDl2bdmaclrnANDbaseANDimix"
501                             # vm vhost ip4 base
502                             "mrrANDvhostANDip4fwdANDbaseAND64b"
503                             "mrrANDvhostANDip4fwdANDbaseANDimix"
504                             # Exclude
505                             "!mrrANDip6baseANDdot1qAND78b"
506                             "!vhost_256ANDnic_intel-x520-da2"
507                             "!vhostANDnic_intel-xl710"
508                             "!cfs_opt"
509                             "!lbond_dpdk")
510             ;;
511         * )
512             if [[ -z "${TEST_TAG_STRING-}" ]]; then
513                 # If nothing is specified, we will run pre-selected tests by
514                 # following tags.
515                 test_tag_array=("mrrANDnic_intel-x710AND1cAND64bANDip4base"
516                                 "mrrANDnic_intel-x710AND1cAND78bANDip6base"
517                                 "mrrANDnic_intel-x710AND1cAND64bANDl2bdbase"
518                                 "mrrANDnic_intel-x710AND1cAND64bANDl2xcbase"
519                                 "!dot1q")
520             else
521                 # If trigger contains tags, split them into array.
522                 test_tag_array=(${TEST_TAG_STRING//:/ })
523             fi
524             ;;
525     esac
526
527     # Blacklisting certain tags per topology.
528     case "${TEST_CODE}" in
529         *"3n-hsw"*)
530             test_tag_array+=("!drv_avf")
531             ;;
532         *"2n-skx"*)
533             test_tag_array+=("!ipsechw")
534             ;;
535         *"3n-skx"*)
536             test_tag_array+=("!ipsechw")
537             ;;
538         *)
539             # Default to 3n-hsw due to compatibility.
540             test_tag_array+=("!drv_avf")
541             ;;
542     esac
543
544     # We will add excluded NICs.
545     test_tag_array+=("${exclude_nics[@]/#/!NIC_}")
546
547     TAGS=()
548
549     # We will prefix with perftest to prevent running other tests
550     # (e.g. Functional).
551     prefix="perftestAND"
552     if [[ "${TEST_CODE}" == "vpp-"* ]]; then
553         # Automatic prefixing for VPP jobs to limit the NIC used and
554         # traffic evaluation to MRR.
555         prefix="${prefix}mrrANDnic_intel-x710AND"
556     fi
557     for tag in "${test_tag_array[@]}"; do
558         if [[ ${tag} == "!"* ]]; then
559             # Exclude tags are not prefixed.
560             TAGS+=("${tag}")
561         else
562             TAGS+=("${prefix}${tag}")
563         fi
564     done
565 }
566
567
568 function select_topology () {
569
570     set -exuo pipefail
571
572     # Variables read:
573     # - NODENESS - Node multiplicity of testbed, either "2n" or "3n".
574     # - FLAVOR - Node flavor string, currently either "hsw" or "skx".
575     # - CSIT_DIR - Path to existing root of local CSIT git repository.
576     # - TOPOLOGIES_DIR - Path to existing directory with available tpologies.
577     # Variables set:
578     # - TOPOLOGIES - Array of paths to suitable topology yaml files.
579     # - TOPOLOGIES_TAGS - Tag expression selecting tests for the topology.
580     # Functions called:
581     # - die - Print to stderr and exit.
582
583     case_text="${NODENESS}_${FLAVOR}"
584     case "${case_text}" in
585         "3n_hsw")
586             TOPOLOGIES=(
587                         "${TOPOLOGIES_DIR}/lf_3n_hsw_testbed1.yaml"
588                         "${TOPOLOGIES_DIR}/lf_3n_hsw_testbed2.yaml"
589                         "${TOPOLOGIES_DIR}/lf_3n_hsw_testbed3.yaml"
590                        )
591             TOPOLOGIES_TAGS="3_node_single_link_topo"
592             ;;
593         "2n_skx")
594             TOPOLOGIES=(
595                         "${TOPOLOGIES_DIR}/lf_2n_skx_testbed21.yaml"
596                         #"${TOPOLOGIES_DIR}/lf_2n_skx_testbed22.yaml"
597                         #"${TOPOLOGIES_DIR}/lf_2n_skx_testbed23.yaml"
598                         "${TOPOLOGIES_DIR}/lf_2n_skx_testbed24.yaml"
599                        )
600             TOPOLOGIES_TAGS="2_node_*_link_topo"
601             ;;
602         "3n_skx")
603             TOPOLOGIES=(
604                         "${TOPOLOGIES_DIR}/lf_3n_skx_testbed31.yaml"
605                         "${TOPOLOGIES_DIR}/lf_3n_skx_testbed32.yaml"
606                        )
607             TOPOLOGIES_TAGS="3_node_*_link_topo"
608             ;;
609         *)
610             # No falling back to 3n_hsw default, that should have been done
611             # by the function which has set NODENESS and FLAVOR.
612             die "Unknown specification: ${case_text}"
613     esac
614
615     if [[ -z "${TOPOLOGIES-}" ]]; then
616         die "No applicable topology found!"
617     fi
618 }
619
620
621 function untrap_and_unreserve_testbed () {
622     # Use this as a trap function to ensure testbed does not remain reserved.
623     # Perhaps call directly before script exit, to free testbed for other jobs.
624     # This function is smart enough to avoid multiple unreservations (so safe).
625     # Topo cleanup is executed (call it best practice), ignoring failures.
626     #
627     # Hardcoded values:
628     # - default message to die with if testbed might remain reserved.
629     # Arguments:
630     # - ${1} - Message to die with if unreservation fails. Default hardcoded.
631     # Variables read (by inner function):
632     # - WORKING_TOPOLOGY - Path to topology yaml file of the reserved testbed.
633     # - PYTHON_SCRIPTS_DIR - Path to directory holding Python scripts.
634     # Variables written:
635     # - WORKING_TOPOLOGY - Set to empty string on successful unreservation.
636     # Trap unregistered:
637     # - EXIT - Failure to untrap is reported, but ignored otherwise.
638     # Functions called:
639     # - die - Print to stderr and exit.
640
641     set -xo pipefail
642     set +eu  # We do not want to exit early in a "teardown" function.
643     trap - EXIT || echo "Trap deactivation failed, continuing anyway."
644     wt="${WORKING_TOPOLOGY}"  # Just to avoid too long lines.
645     if [[ -z "${wt-}" ]]; then
646         set -eu
647         warn "Testbed looks unreserved already. Trap removal failed before?"
648     else
649         cleanup_topo || true
650         python "${PYTHON_SCRIPTS_DIR}/topo_reservation.py" -c -t "${wt}" || {
651             die "${1:-FAILED TO UNRESERVE, FIX MANUALLY.}" 2
652         }
653         WORKING_TOPOLOGY=""
654         set -eu
655     fi
656 }
657
658
659 function warn () {
660     # Print the message to standard error.
661     #
662     # Arguments:
663     # - ${@} - The text of the message.
664
665     echo "$@" >&2
666 }